From 62d570f3d909023be634d70f7ccb8d2c982592ae Mon Sep 17 00:00:00 2001 From: Jukka Rissanen Date: Sat, 9 Dec 2023 09:02:22 +0200 Subject: [PATCH 1/9] [nrf fromtree] net: Move trickle files to lib The trickle algorithm files are clearly a library so move them under lib/ directory. Signed-off-by: Jukka Rissanen (cherry picked from commit b6d9ed095d7bc395be602072db2679d201735113) --- subsys/net/ip/CMakeLists.txt | 1 - subsys/net/ip/Kconfig | 14 -------------- subsys/net/lib/CMakeLists.txt | 1 + subsys/net/lib/Kconfig | 2 ++ subsys/net/lib/trickle/CMakeLists.txt | 7 +++++++ subsys/net/lib/trickle/Kconfig | 18 ++++++++++++++++++ subsys/net/{ip => lib/trickle}/trickle.c | 0 7 files changed, 28 insertions(+), 15 deletions(-) create mode 100644 subsys/net/lib/trickle/CMakeLists.txt create mode 100644 subsys/net/lib/trickle/Kconfig rename subsys/net/{ip => lib/trickle}/trickle.c (100%) diff --git a/subsys/net/ip/CMakeLists.txt b/subsys/net/ip/CMakeLists.txt index b9493b894a7..3a93e5020d4 100644 --- a/subsys/net/ip/CMakeLists.txt +++ b/subsys/net/ip/CMakeLists.txt @@ -45,7 +45,6 @@ zephyr_library_sources_ifdef(CONFIG_NET_ROUTE route.c) zephyr_library_sources_ifdef(CONFIG_NET_STATISTICS net_stats.c) zephyr_library_sources_ifdef(CONFIG_NET_TCP tcp.c) zephyr_library_sources_ifdef(CONFIG_NET_TEST_PROTOCOL tp.c) -zephyr_library_sources_ifdef(CONFIG_NET_TRICKLE trickle.c) zephyr_library_sources_ifdef(CONFIG_NET_UDP udp.c) zephyr_library_sources_ifdef(CONFIG_NET_PROMISCUOUS_MODE promiscuous.c) diff --git a/subsys/net/ip/Kconfig b/subsys/net/ip/Kconfig index fffa028b628..a5a6b7ba01e 100644 --- a/subsys/net/ip/Kconfig +++ b/subsys/net/ip/Kconfig @@ -758,20 +758,6 @@ config NET_SLIP_TAP communicate via the SLIP driver. See net-tools project at https://github.com/zephyrproject-rtos/net-tools for more details. -config NET_TRICKLE - bool "Trickle library" - help - Normally this is enabled automatically if needed, - so say 'n' if unsure. - -if NET_TRICKLE -module = NET_TRICKLE -module-dep = NET_LOG -module-str = Log level for Trickle algorithm -module-help = Enables Trickle library output debug messages -source "subsys/net/Kconfig.template.log_config.net" -endif # NET_TRICKLE - endif # NET_RAW_MODE config NET_PKT_RX_COUNT diff --git a/subsys/net/lib/CMakeLists.txt b/subsys/net/lib/CMakeLists.txt index 756adb41341..5e4eae7e028 100644 --- a/subsys/net/lib/CMakeLists.txt +++ b/subsys/net/lib/CMakeLists.txt @@ -14,6 +14,7 @@ add_subdirectory_ifdef(CONFIG_TLS_CREDENTIALS tls_credentials) add_subdirectory_ifdef(CONFIG_NET_CAPTURE capture) add_subdirectory_ifdef(CONFIG_NET_ZPERF zperf) add_subdirectory_ifdef(CONFIG_NET_SHELL shell) +add_subdirectory_ifdef(CONFIG_NET_TRICKLE trickle) if (CONFIG_DNS_RESOLVER OR CONFIG_MDNS_RESPONDER diff --git a/subsys/net/lib/Kconfig b/subsys/net/lib/Kconfig index 5df4a445885..b70105d5f9b 100644 --- a/subsys/net/lib/Kconfig +++ b/subsys/net/lib/Kconfig @@ -39,6 +39,8 @@ menu "Network additional services" source "subsys/net/lib/capture/Kconfig" +source "subsys/net/lib/trickle/Kconfig" + source "subsys/net/lib/zperf/Kconfig" endmenu diff --git a/subsys/net/lib/trickle/CMakeLists.txt b/subsys/net/lib/trickle/CMakeLists.txt new file mode 100644 index 00000000000..b9cf222aab5 --- /dev/null +++ b/subsys/net/lib/trickle/CMakeLists.txt @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: Apache-2.0 + +zephyr_library() + +zephyr_library_sources( + trickle.c + ) diff --git a/subsys/net/lib/trickle/Kconfig b/subsys/net/lib/trickle/Kconfig new file mode 100644 index 00000000000..edc4f8fcf56 --- /dev/null +++ b/subsys/net/lib/trickle/Kconfig @@ -0,0 +1,18 @@ +# Trickle Library for Zephyr + +# Copyright (c) 2016 Intel Corporation. +# SPDX-License-Identifier: Apache-2.0 + +config NET_TRICKLE + bool "Trickle library" + help + Normally this is enabled automatically if needed, + so say 'n' if unsure. + +if NET_TRICKLE +module = NET_TRICKLE +module-dep = NET_LOG +module-str = Log level for Trickle algorithm +module-help = Enables Trickle library output debug messages +source "subsys/net/Kconfig.template.log_config.net" +endif # NET_TRICKLE diff --git a/subsys/net/ip/trickle.c b/subsys/net/lib/trickle/trickle.c similarity index 100% rename from subsys/net/ip/trickle.c rename to subsys/net/lib/trickle/trickle.c From 4919470e204c347f4c811ea59b047d5382fa79a4 Mon Sep 17 00:00:00 2001 From: Jukka Rissanen Date: Thu, 16 Nov 2023 17:06:12 +0200 Subject: [PATCH 2/9] [nrf fromtree] net: sockets: Create a socket service API The socket service provides a similar functionality as what initd provides in Linux. It listens user registered sockets for any activity and then launches a k_work for it. This way each application does not need to create a thread to listen a blocking socket. Signed-off-by: Jukka Rissanen (cherry picked from commit eff5d028728ccc8291f9d83a724517288e940435) --- .../linker/common-rom/common-rom-net.ld | 4 + include/zephyr/net/socket_service.h | 246 +++++++++++++++ subsys/net/lib/sockets/CMakeLists.txt | 1 + subsys/net/lib/sockets/Kconfig | 25 ++ subsys/net/lib/sockets/sockets_service.c | 291 ++++++++++++++++++ 5 files changed, 567 insertions(+) create mode 100644 include/zephyr/net/socket_service.h create mode 100644 subsys/net/lib/sockets/sockets_service.c diff --git a/include/zephyr/linker/common-rom/common-rom-net.ld b/include/zephyr/linker/common-rom/common-rom-net.ld index 71c1c1e089f..18360212edc 100644 --- a/include/zephyr/linker/common-rom/common-rom-net.ld +++ b/include/zephyr/linker/common-rom/common-rom-net.ld @@ -21,3 +21,7 @@ #if defined(CONFIG_COAP_SERVER) ITERABLE_SECTION_ROM(coap_service, 4) #endif + +#if defined(CONFIG_NET_SOCKETS_SERVICE) + ITERABLE_SECTION_RAM(net_socket_service_desc, 4) +#endif diff --git a/include/zephyr/net/socket_service.h b/include/zephyr/net/socket_service.h new file mode 100644 index 00000000000..16da94d238b --- /dev/null +++ b/include/zephyr/net/socket_service.h @@ -0,0 +1,246 @@ +/** + * @file + * @brief BSD Socket service API + * + * API can be used to install a k_work that is called + * if there is data received to a socket. + */ + +/* + * Copyright (c) 2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_NET_SOCKET_SERVICE_H_ +#define ZEPHYR_INCLUDE_NET_SOCKET_SERVICE_H_ + +/** + * @brief BSD socket service API + * @defgroup bsd_socket_service BSD socket service API + * @ingroup networking + * @{ + */ + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * This struct contains information which socket triggered + * calls to the callback function. + */ +struct net_socket_service_event { + /** k_work that is done when there is desired activity in file descriptor. */ + struct k_work work; + /** Callback to be called for desired socket activity */ + k_work_handler_t callback; + /** Socket information that triggered this event. */ + struct zsock_pollfd event; + /** User data */ + void *user_data; + /** Service back pointer */ + struct net_socket_service_desc *svc; +}; + +/** + * Main structure holding socket service configuration information. + * The k_work item is created so that when there is data coming + * to those fds, the k_work callback is then called. + * The workqueue can be set NULL in which case system workqueue is used. + * The service descriptor should be created at built time, and then used + * as a parameter to register the sockets to be monitored. + * User should create needed sockets and then setup the poll struct and + * then register the sockets to be monitored at runtime. + */ +struct net_socket_service_desc { +#if CONFIG_NET_SOCKETS_LOG_LEVEL >= LOG_LEVEL_DBG + /** + * Owner name. This can be used in debugging to see who has + * registered this service. + */ + const char *owner; +#endif + /** Workqueue where the work is submitted. */ + struct k_work_q *work_q; + /** Pointer to the list of services that we are listening */ + struct net_socket_service_event *pev; + /** Length of the pollable socket array for this service. */ + int pev_len; + /** Where are my pollfd entries in the global list */ + int *idx; +}; + +#define __z_net_socket_svc_get_name(_svc_id) __z_net_socket_service_##_svc_id +#define __z_net_socket_svc_get_idx(_svc_id) __z_net_socket_service_idx##_svc_id +#define __z_net_socket_svc_get_owner __FILE__ ":" STRINGIFY(__LINE__) + +extern void net_socket_service_callback(struct k_work *work); + +#if CONFIG_NET_SOCKETS_LOG_LEVEL >= LOG_LEVEL_DBG +#define NET_SOCKET_SERVICE_OWNER .owner = __z_net_socket_svc_get_owner, +#else +#define NET_SOCKET_SERVICE_OWNER +#endif + +#define NET_SOCKET_SERVICE_CALLBACK_MODE(_flag) \ + IF_ENABLED(_flag, \ + (.work = Z_WORK_INITIALIZER(net_socket_service_callback),)) + +#define __z_net_socket_service_define(_name, _work_q, _cb, _count, _async, ...) \ + static int __z_net_socket_svc_get_idx(_name); \ + static struct net_socket_service_event \ + __z_net_socket_svc_get_name(_name)[_count] = { \ + [0 ... ((_count) - 1)] = { \ + .event.fd = -1, /* Invalid socket */ \ + NET_SOCKET_SERVICE_CALLBACK_MODE(_async) \ + .callback = _cb, \ + } \ + }; \ + COND_CODE_0(NUM_VA_ARGS_LESS_1(__VA_ARGS__), (), __VA_ARGS__) \ + const STRUCT_SECTION_ITERABLE(net_socket_service_desc, _name) = { \ + NET_SOCKET_SERVICE_OWNER \ + .work_q = (_work_q), \ + .pev = __z_net_socket_svc_get_name(_name), \ + .pev_len = (_count), \ + .idx = &__z_net_socket_svc_get_idx(_name), \ + } + +/** + * @brief Statically define a network socket service. + * The user callback is called asynchronously for this service meaning that + * the service API will not wait until the user callback returns before continuing + * with next socket service. + * + * The socket service can be accessed outside the module where it is defined using: + * + * @code extern struct net_socket_service_desc ; @endcode + * + * @note This macro cannot be used together with a static keyword. + * If such a use-case is desired, use NET_SOCKET_SERVICE_ASYNC_DEFINE_STATIC + * instead. + * + * @param name Name of the service. + * @param work_q Pointer to workqueue where the work is done. Can be null in which case + * system workqueue is used. + * @param cb Callback function that is called for socket activity. + * @param count How many pollable sockets is needed for this service. + */ +#define NET_SOCKET_SERVICE_ASYNC_DEFINE(name, work_q, cb, count) \ + __z_net_socket_service_define(name, work_q, cb, count, 1) + +/** + * @brief Statically define a network socket service in a private (static) scope. + * The user callback is called asynchronously for this service meaning that + * the service API will not wait until the user callback returns before continuing + * with next socket service. + * + * @param name Name of the service. + * @param work_q Pointer to workqueue where the work is done. Can be null in which case + * system workqueue is used. + * @param cb Callback function that is called for socket activity. + * @param count How many pollable sockets is needed for this service. + */ +#define NET_SOCKET_SERVICE_ASYNC_DEFINE_STATIC(name, work_q, cb, count) \ + __z_net_socket_service_define(name, work_q, cb, count, 1, static) + +/** + * @brief Statically define a network socket service. + * The user callback is called synchronously for this service meaning that + * the service API will wait until the user callback returns before continuing + * with next socket service. + * + * The socket service can be accessed outside the module where it is defined using: + * + * @code extern struct net_socket_service_desc ; @endcode + * + * @note This macro cannot be used together with a static keyword. + * If such a use-case is desired, use NET_SOCKET_SERVICE_SYNC_DEFINE_STATIC + * instead. + * + * @param name Name of the service. + * @param work_q Pointer to workqueue where the work is done. Can be null in which case + * system workqueue is used. + * @param cb Callback function that is called for socket activity. + * @param count How many pollable sockets is needed for this service. + */ +#define NET_SOCKET_SERVICE_SYNC_DEFINE(name, work_q, cb, count) \ + __z_net_socket_service_define(name, work_q, cb, count, 0) + +/** + * @brief Statically define a network socket service in a private (static) scope. + * The user callback is called synchronously for this service meaning that + * the service API will wait until the user callback returns before continuing + * with next socket service. + * + * @param name Name of the service. + * @param work_q Pointer to workqueue where the work is done. Can be null in which case + * system workqueue is used. + * @param cb Callback function that is called for socket activity. + * @param count How many pollable sockets is needed for this service. + */ +#define NET_SOCKET_SERVICE_SYNC_DEFINE_STATIC(name, work_q, cb, count) \ + __z_net_socket_service_define(name, work_q, cb, count, 0, static) + +/** + * @brief Register pollable sockets. + * + * @param service Pointer to a service description. + * @param fds Socket array to poll. + * @param len Length of the socket array. + * @param user_data User specific data. + * + * @retval 0 No error + * @retval -ENOENT Service is not found. + * @retval -ENINVAL Invalid parameter. + */ +__syscall int net_socket_service_register(const struct net_socket_service_desc *service, + struct zsock_pollfd *fds, int len, void *user_data); + +/** + * @brief Unregister pollable sockets. + * + * @param service Pointer to a service description. + * + * @retval 0 No error + * @retval -ENOENT Service is not found. + * @retval -ENINVAL Invalid parameter. + */ +static inline int net_socket_service_unregister(const struct net_socket_service_desc *service) +{ + return net_socket_service_register(service, NULL, 0, NULL); +} + +/** + * @typedef net_socket_service_cb_t + * @brief Callback used while iterating over socket services. + * + * @param svc Pointer to current socket service. + * @param user_data A valid pointer to user data or NULL + */ +typedef void (*net_socket_service_cb_t)(const struct net_socket_service_desc *svc, + void *user_data); + +/** + * @brief Go through all the socket services and call callback for each service. + * + * @param cb User-supplied callback function to call + * @param user_data User specified data + */ +void net_socket_service_foreach(net_socket_service_cb_t cb, void *user_data); + +#ifdef __cplusplus +} +#endif + +#include + +/** + * @} + */ + +#endif /* ZEPHYR_INCLUDE_NET_SOCKET_SERVICE_H_ */ diff --git a/subsys/net/lib/sockets/CMakeLists.txt b/subsys/net/lib/sockets/CMakeLists.txt index 7ffd6dc476b..253cb4a182f 100644 --- a/subsys/net/lib/sockets/CMakeLists.txt +++ b/subsys/net/lib/sockets/CMakeLists.txt @@ -26,6 +26,7 @@ zephyr_library_sources_ifdef(CONFIG_NET_SOCKETS_SOCKOPT_TLS sockets_tls.c zephyr_library_sources_ifdef(CONFIG_NET_SOCKETS_OFFLOAD socket_offload.c) zephyr_library_sources_ifdef(CONFIG_NET_SOCKETS_OFFLOAD_DISPATCHER socket_dispatcher.c) zephyr_library_sources_ifdef(CONFIG_NET_SOCKETS_OBJ_CORE socket_obj_core.c) +zephyr_library_sources_ifdef(CONFIG_NET_SOCKETS_SERVICE sockets_service.c) if(CONFIG_NET_SOCKETS_NET_MGMT) zephyr_library_sources(sockets_net_mgmt.c) diff --git a/subsys/net/lib/sockets/Kconfig b/subsys/net/lib/sockets/Kconfig index b2da30c1e48..40d86b9498f 100644 --- a/subsys/net/lib/sockets/Kconfig +++ b/subsys/net/lib/sockets/Kconfig @@ -73,6 +73,31 @@ config NET_SOCKET_MAX_SEND_WAIT The maximum time a socket is waiting for a blocked connection before returning an ENOBUFS error. +config NET_SOCKETS_SERVICE + bool "Socket service support [EXPERIMENTAL]" + select EXPERIMENTAL + select EVENTFD + help + The socket service can monitor multiple sockets and save memory + by only having one thread listening socket data. If data is received + in the monitored socket, a user supplied work is called. + Note that you need to set CONFIG_NET_SOCKETS_POLL_MAX high enough + so that enough sockets entries can be serviced. This depends on + system needs as multiple services can be activated at the same time + depending on network configuration. + +config NET_SOCKETS_SERVICE_STACK_SIZE + int "Stack size for the thread handling socket services" + default 1200 + depends on NET_SOCKETS_SERVICE + help + Set the internal stack size for the thread that polls sockets. + +config NET_SOCKETS_SERVICE_INIT_PRIO + int "Startup priority for the network socket service" + default 95 + depends on NET_SOCKETS_SERVICE + config NET_SOCKETS_SOCKOPT_TLS bool "TCP TLS socket option support [EXPERIMENTAL]" imply TLS_CREDENTIALS diff --git a/subsys/net/lib/sockets/sockets_service.c b/subsys/net/lib/sockets/sockets_service.c new file mode 100644 index 00000000000..d253ece629c --- /dev/null +++ b/subsys/net/lib/sockets/sockets_service.c @@ -0,0 +1,291 @@ +/* + * Copyright (c) 2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +LOG_MODULE_REGISTER(net_sock_svc, CONFIG_NET_SOCKETS_LOG_LEVEL); + +#include +#include +#include + +static int init_socket_service(void); +static bool init_done; + +static K_MUTEX_DEFINE(lock); +static K_CONDVAR_DEFINE(wait_start); + +STRUCT_SECTION_START_EXTERN(net_socket_service_desc); +STRUCT_SECTION_END_EXTERN(net_socket_service_desc); + +static struct service { + /* The +1 is for triggering events from register function */ + struct zsock_pollfd events[1 + CONFIG_NET_SOCKETS_POLL_MAX]; + int count; +} ctx; + +#define get_idx(svc) (*(svc->idx)) + +void net_socket_service_foreach(net_socket_service_cb_t cb, void *user_data) +{ + STRUCT_SECTION_FOREACH(net_socket_service_desc, svc) { + cb(svc, user_data); + } +} + +static void cleanup_svc_events(const struct net_socket_service_desc *svc) +{ + for (int i = 0; i < svc->pev_len; i++) { + ctx.events[get_idx(svc) + i].fd = -1; + svc->pev[i].event.fd = -1; + svc->pev[i].event.events = 0; + } +} + +int z_impl_net_socket_service_register(const struct net_socket_service_desc *svc, + struct zsock_pollfd *fds, int len, + void *user_data) +{ + int i, ret = -ENOENT; + + k_mutex_lock(&lock, K_FOREVER); + + if (!init_done) { + (void)k_condvar_wait(&wait_start, &lock, K_FOREVER); + } + + if (STRUCT_SECTION_START(net_socket_service_desc) > svc || + STRUCT_SECTION_END(net_socket_service_desc) <= svc) { + goto out; + } + + if (fds == NULL) { + cleanup_svc_events(svc); + } else { + if (len > svc->pev_len) { + NET_DBG("Too many file descriptors, " + "max is %d for service %p", + svc->pev_len, svc); + ret = -ENOMEM; + goto out; + } + + for (i = 0; i < len; i++) { + svc->pev[i].event = fds[i]; + svc->pev[i].user_data = user_data; + } + + for (i = 0; i < svc->pev_len; i++) { + ctx.events[get_idx(svc) + i] = svc->pev[i].event; + } + } + + /* Tell the thread to re-read the variables */ + eventfd_write(ctx.events[0].fd, 1); + ret = 0; + +out: + k_mutex_unlock(&lock); + + return ret; +} + +static struct net_socket_service_desc *find_svc_and_event( + struct zsock_pollfd *pev, + struct net_socket_service_event **event) +{ + STRUCT_SECTION_FOREACH(net_socket_service_desc, svc) { + for (int i = 0; i < svc->pev_len; i++) { + if (svc->pev[i].event.fd == pev->fd) { + *event = &svc->pev[i]; + return svc; + } + } + } + + return NULL; +} + +/* We do not set the user callback to our work struct because we need to + * hook into the flow and restore the global poll array so that the next poll + * round will not notice it and call the callback again while we are + * servicing the callback. + */ +void net_socket_service_callback(struct k_work *work) +{ + struct net_socket_service_event *pev = + CONTAINER_OF(work, struct net_socket_service_event, work); + struct net_socket_service_desc *svc = pev->svc; + struct net_socket_service_event ev = *pev; + + ev.callback(&ev.work); + + /* Copy back the socket fd to the global array because we marked + * it as -1 when triggering the work. + */ + for (int i = 0; i < svc->pev_len; i++) { + ctx.events[get_idx(svc) + i] = svc->pev[i].event; + } +} + +static int call_work(struct zsock_pollfd *pev, struct k_work_q *work_q, + struct k_work *work) +{ + int ret = 0; + + /* Mark the global fd non pollable so that we do not + * call the callback second time. + */ + pev->fd = -1; + + if (work->handler == NULL) { + /* Synchronous call */ + net_socket_service_callback(work); + } else { + if (work_q != NULL) { + ret = k_work_submit_to_queue(work_q, work); + } else { + ret = k_work_submit(work); + } + + k_yield(); + } + + return ret; + +} + +static int trigger_work(struct zsock_pollfd *pev) +{ + struct net_socket_service_event *event; + struct net_socket_service_desc *svc; + + svc = find_svc_and_event(pev, &event); + if (svc == NULL) { + return -ENOENT; + } + + event->svc = svc; + + /* Copy the triggered event to our event so that we know what + * was actually causing the event. + */ + event->event = *pev; + + return call_work(pev, svc->work_q, &event->work); +} + +static void socket_service_thread(void) +{ + int ret, i, fd, count = 0; + eventfd_t value; + + STRUCT_SECTION_COUNT(net_socket_service_desc, &ret); + if (ret == 0) { + NET_INFO("No socket services found, service disabled."); + goto fail; + } + + /* Create contiguous poll event array to enable socket polling */ + STRUCT_SECTION_FOREACH(net_socket_service_desc, svc) { + get_idx(svc) = count + 1; + count += svc->pev_len; + } + + if ((count + 1) > ARRAY_SIZE(ctx.events)) { + NET_WARN("You have %d services to monitor but " + "%d poll entries configured.", + count + 1, ARRAY_SIZE(ctx.events)); + NET_WARN("Consider increasing value of %s to %d", + "CONFIG_NET_SOCKETS_POLL_MAX", count + 1); + } + + NET_DBG("Monitoring %d socket entries", count); + + ctx.count = count + 1; + + /* Create an eventfd that can be used to trigger events during polling */ + fd = eventfd(0, 0); + if (fd < 0) { + fd = -errno; + NET_ERR("eventfd failed (%d)", fd); + goto out; + } + + init_done = true; + k_condvar_broadcast(&wait_start); + + ctx.events[0].fd = fd; + ctx.events[0].events = ZSOCK_POLLIN; + +restart: + i = 1; + + k_mutex_lock(&lock, K_FOREVER); + + /* Copy individual events to the big array */ + STRUCT_SECTION_FOREACH(net_socket_service_desc, svc) { + for (int j = 0; j < svc->pev_len; j++) { + ctx.events[get_idx(svc) + j] = svc->pev[j].event; + } + } + + k_mutex_unlock(&lock); + + while (true) { + ret = zsock_poll(ctx.events, count + 1, -1); + if (ret < 0) { + ret = -errno; + NET_ERR("poll failed (%d)", ret); + goto out; + } + + if (ret == 0) { + /* should not happen because timeout is -1 */ + break; + } + + if (ret > 0 && ctx.events[0].revents) { + eventfd_read(ctx.events[0].fd, &value); + NET_DBG("Received restart event."); + goto restart; + } + + for (i = 1; i < (count + 1); i++) { + if (ctx.events[i].fd < 0) { + continue; + } + + if (ctx.events[i].revents > 0) { + ret = trigger_work(&ctx.events[i]); + if (ret < 0) { + NET_DBG("Triggering work failed (%d)", ret); + } + } + } + } + +out: + NET_DBG("Socket service thread stopped"); + init_done = false; + + return; + +fail: + k_condvar_broadcast(&wait_start); +} + +K_THREAD_DEFINE(socket_service_monitor, CONFIG_NET_SOCKETS_SERVICE_STACK_SIZE, + socket_service_thread, NULL, NULL, NULL, + K_LOWEST_APPLICATION_THREAD_PRIO, 0, 0); + +static int init_socket_service(void) +{ + k_thread_name_set(socket_service_monitor, "net_socket_service"); + + return 0; +} + +SYS_INIT(init_socket_service, APPLICATION, CONFIG_NET_SOCKETS_SERVICE_INIT_PRIO); From 41e222c710451e38dc958f42aa85e967e81b3394 Mon Sep 17 00:00:00 2001 From: Robert Lubos Date: Thu, 18 Jan 2024 12:55:25 +0100 Subject: [PATCH 3/9] [nrf fromtree] net: socket_service: Add underscore in the idx variable name Nothing critical, but it just looks better if the service name is separated from the prefix, i.e: _z_net_socket_service_idx_udp_service_async vs current: _z_net_socket_service_idxudp_service_async Signed-off-by: Robert Lubos (cherry picked from commit 3111b801d90ff9d109e678421097da01f45685e0) --- include/zephyr/net/socket_service.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/zephyr/net/socket_service.h b/include/zephyr/net/socket_service.h index 16da94d238b..a4e21f00a33 100644 --- a/include/zephyr/net/socket_service.h +++ b/include/zephyr/net/socket_service.h @@ -76,7 +76,7 @@ struct net_socket_service_desc { }; #define __z_net_socket_svc_get_name(_svc_id) __z_net_socket_service_##_svc_id -#define __z_net_socket_svc_get_idx(_svc_id) __z_net_socket_service_idx##_svc_id +#define __z_net_socket_svc_get_idx(_svc_id) __z_net_socket_service_idx_##_svc_id #define __z_net_socket_svc_get_owner __FILE__ ":" STRINGIFY(__LINE__) extern void net_socket_service_callback(struct k_work *work); From 541bc659c3dbf5f0aa1784a3a788915c49c70859 Mon Sep 17 00:00:00 2001 From: Robert Lubos Date: Thu, 18 Jan 2024 12:57:49 +0100 Subject: [PATCH 4/9] [nrf fromtree] net: socket_service: Fix iterable section location The iterable section should be located in ROM and not RAM, this caused crashes on multiple platforms running socket services. Fixes #67762 Signed-off-by: Robert Lubos (cherry picked from commit 8ad0e5763f844e1cff81b21ebc54c1cc6101ff41) --- include/zephyr/linker/common-rom/common-rom-net.ld | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/zephyr/linker/common-rom/common-rom-net.ld b/include/zephyr/linker/common-rom/common-rom-net.ld index 18360212edc..2aa46dfecf5 100644 --- a/include/zephyr/linker/common-rom/common-rom-net.ld +++ b/include/zephyr/linker/common-rom/common-rom-net.ld @@ -23,5 +23,5 @@ #endif #if defined(CONFIG_NET_SOCKETS_SERVICE) - ITERABLE_SECTION_RAM(net_socket_service_desc, 4) + ITERABLE_SECTION_ROM(net_socket_service_desc, 4) #endif From eac5db035161083b448a4a8ba9223a16da478289 Mon Sep 17 00:00:00 2001 From: Robert Lubos Date: Tue, 16 Jan 2024 17:37:59 +0100 Subject: [PATCH 5/9] [nrf fromtree] net: arp: Make arp_update() function externally visible So that it's possible to register ARP entries manually. Needed for DHCP server implementation, which in unicast mode needs to reply to an IP address that is not registered on the peer interface yet (hence no ARP reply will be sent). It's needed to add an ARP entry manually in that case, as hardware address is already known at that point. Signed-off-by: Robert Lubos (cherry picked from commit 1d14f13d75eb31104eb84d5d00e835cf82fb8377) --- subsys/net/l2/ethernet/arp.c | 34 +++++++++++++++++----------------- subsys/net/l2/ethernet/arp.h | 4 ++++ 2 files changed, 21 insertions(+), 17 deletions(-) diff --git a/subsys/net/l2/ethernet/arp.c b/subsys/net/l2/ethernet/arp.c index 39a06ed64a0..6be7255b19b 100644 --- a/subsys/net/l2/ethernet/arp.c +++ b/subsys/net/l2/ethernet/arp.c @@ -457,11 +457,11 @@ static void arp_gratuitous(struct net_if *iface, } } -static void arp_update(struct net_if *iface, - struct in_addr *src, - struct net_eth_addr *hwaddr, - bool gratuitous, - bool force) +void net_arp_update(struct net_if *iface, + struct in_addr *src, + struct net_eth_addr *hwaddr, + bool gratuitous, + bool force) { struct arp_entry *entry; struct net_pkt *pkt; @@ -647,10 +647,10 @@ enum net_verdict net_arp_input(struct net_pkt *pkt, /* If the IP address is in our cache, * then update it here. */ - arp_update(net_pkt_iface(pkt), - (struct in_addr *)arp_hdr->src_ipaddr, - &arp_hdr->src_hwaddr, - true, false); + net_arp_update(net_pkt_iface(pkt), + (struct in_addr *)arp_hdr->src_ipaddr, + &arp_hdr->src_hwaddr, + true, false); break; } } @@ -689,10 +689,10 @@ enum net_verdict net_arp_input(struct net_pkt *pkt, net_sprint_ll_addr((uint8_t *)&arp_hdr->src_hwaddr, arp_hdr->hwlen)); - arp_update(net_pkt_iface(pkt), - (struct in_addr *)arp_hdr->src_ipaddr, - &arp_hdr->src_hwaddr, - false, true); + net_arp_update(net_pkt_iface(pkt), + (struct in_addr *)arp_hdr->src_ipaddr, + &arp_hdr->src_hwaddr, + false, true); dst_hw_addr = &arp_hdr->src_hwaddr; } else { @@ -711,10 +711,10 @@ enum net_verdict net_arp_input(struct net_pkt *pkt, case NET_ARP_REPLY: if (net_ipv4_is_my_addr((struct in_addr *)arp_hdr->dst_ipaddr)) { - arp_update(net_pkt_iface(pkt), - (struct in_addr *)arp_hdr->src_ipaddr, - &arp_hdr->src_hwaddr, - false, false); + net_arp_update(net_pkt_iface(pkt), + (struct in_addr *)arp_hdr->src_ipaddr, + &arp_hdr->src_hwaddr, + false, false); } break; diff --git a/subsys/net/l2/ethernet/arp.h b/subsys/net/l2/ethernet/arp.h index 28cafe5f20a..46589cbc1f7 100644 --- a/subsys/net/l2/ethernet/arp.h +++ b/subsys/net/l2/ethernet/arp.h @@ -67,6 +67,9 @@ int net_arp_foreach(net_arp_cb_t cb, void *user_data); void net_arp_clear_cache(struct net_if *iface); void net_arp_init(void); +void net_arp_update(struct net_if *iface, struct in_addr *src, + struct net_eth_addr *hwaddr, bool gratuitous, + bool force); /** * @} @@ -83,6 +86,7 @@ void net_arp_init(void); #define net_arp_foreach(...) 0 #define net_arp_init(...) #define net_arp_clear_pending(...) 0 +#define net_arp_update(...) #endif /* CONFIG_NET_ARP */ From ffe11061a64a08250b7157fbc56c3e0e5d09a0bd Mon Sep 17 00:00:00 2001 From: Robert Lubos Date: Tue, 16 Jan 2024 17:38:16 +0100 Subject: [PATCH 6/9] [nrf fromtree] net: if: Add function to obtain IPv4 netmask Add a helper function to obtain IPv4 netmask configured on an interface. Signed-off-by: Robert Lubos (cherry picked from commit db80ed3e8df1d662f5e750b5629e5e6e7c1bddd5) --- include/zephyr/net/net_if.h | 9 +++++++++ subsys/net/ip/net_if.c | 21 +++++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/include/zephyr/net/net_if.h b/include/zephyr/net/net_if.h index 788c3316391..f1e5be67cf7 100644 --- a/include/zephyr/net/net_if.h +++ b/include/zephyr/net/net_if.h @@ -2217,6 +2217,15 @@ struct in_addr *net_if_ipv4_get_ll(struct net_if *iface, struct in_addr *net_if_ipv4_get_global_addr(struct net_if *iface, enum net_addr_state addr_state); +/** + * @brief Get IPv4 netmask of an interface. + * + * @param iface Interface to use. + * + * @return The netmask set on the interface, unspecified address if not found. + */ +struct in_addr net_if_ipv4_get_netmask(struct net_if *iface); + /** * @brief Set IPv4 netmask for an interface. * diff --git a/subsys/net/ip/net_if.c b/subsys/net/ip/net_if.c index 00c9c74495d..1a8199ad62c 100644 --- a/subsys/net/ip/net_if.c +++ b/subsys/net/ip/net_if.c @@ -3532,6 +3532,27 @@ static inline int z_vrfy_net_if_ipv4_addr_lookup_by_index( #include #endif +struct in_addr net_if_ipv4_get_netmask(struct net_if *iface) +{ + struct in_addr netmask = { 0 }; + + net_if_lock(iface); + + if (net_if_config_ipv4_get(iface, NULL) < 0) { + goto out; + } + + if (!iface->config.ip.ipv4) { + goto out; + } + + netmask = iface->config.ip.ipv4->netmask; +out: + net_if_unlock(iface); + + return netmask; +} + void net_if_ipv4_set_netmask(struct net_if *iface, const struct in_addr *netmask) { From 7b6b1634b7a23c393c5456f402cee68e13ed25b9 Mon Sep 17 00:00:00 2001 From: Robert Lubos Date: Tue, 16 Jan 2024 17:38:56 +0100 Subject: [PATCH 7/9] [nrf fromtree] net: dhcpv4: Implement DHCPv4 server Add basic socket-based implementation of DHCPv4 sever. Signed-off-by: Robert Lubos (cherry picked from commit 1e08bbd5434f82e7a7849677e7dd10416d71488b) --- include/zephyr/net/dhcpv4_server.h | 118 ++ subsys/net/ip/dhcpv4.h | 11 + subsys/net/ip/net_core.c | 2 + subsys/net/lib/CMakeLists.txt | 1 + subsys/net/lib/Kconfig | 2 + subsys/net/lib/dhcpv4/CMakeLists.txt | 11 + subsys/net/lib/dhcpv4/Kconfig | 46 + subsys/net/lib/dhcpv4/dhcpv4_server.c | 1439 +++++++++++++++++++++++++ 8 files changed, 1630 insertions(+) create mode 100644 include/zephyr/net/dhcpv4_server.h create mode 100644 subsys/net/lib/dhcpv4/CMakeLists.txt create mode 100644 subsys/net/lib/dhcpv4/Kconfig create mode 100644 subsys/net/lib/dhcpv4/dhcpv4_server.c diff --git a/include/zephyr/net/dhcpv4_server.h b/include/zephyr/net/dhcpv4_server.h new file mode 100644 index 00000000000..18c4af114cd --- /dev/null +++ b/include/zephyr/net/dhcpv4_server.h @@ -0,0 +1,118 @@ +/** @file + * @brief DHCPv4 Server API + */ + +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_NET_DHCPV4_SERVER_H_ +#define ZEPHYR_INCLUDE_NET_DHCPV4_SERVER_H_ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief DHCPv4 server + * @defgroup dhcpv4_server DHCPv4 server + * @ingroup networking + * @{ + */ + +/** @cond INTERNAL_HIDDEN */ + +struct net_if; + +#define DHCPV4_CLIENT_ID_MAX_SIZE 20 + +enum dhcpv4_server_addr_state { + DHCPV4_SERVER_ADDR_FREE, + DHCPV4_SERVER_ADDR_RESERVED, + DHCPV4_SERVER_ADDR_ALLOCATED, + DHCPV4_SERVER_ADDR_DECLINED, +}; + +struct dhcpv4_client_id { + uint8_t buf[DHCPV4_CLIENT_ID_MAX_SIZE]; + uint8_t len; +}; + +struct dhcpv4_addr_slot { + enum dhcpv4_server_addr_state state; + struct dhcpv4_client_id client_id; + struct in_addr addr; + uint32_t lease_time; + k_timepoint_t expiry; +}; + +/** @endcond */ + +/** + * @brief Start DHCPv4 server instance on an iface + * + * @details Start DHCPv4 server on a given interface. The server will start + * listening for DHCPv4 Discover/Request messages on the interface and assign + * IPv4 addresses from the configured address pool accordingly. + * + * @param iface A valid pointer on an interface + * @param base_addr First IPv4 address from the DHCPv4 address pool. The number + * of addresses in the pool is configured statically with Kconfig + * (CONFIG_NET_DHCPV4_SERVER_ADDR_COUNT). + * + * @return 0 on success, a negative error code otherwise. + */ +int net_dhcpv4_server_start(struct net_if *iface, struct in_addr *base_addr); + +/** + * @brief Stop DHCPv4 server instance on an iface + * + * @details Stop DHCPv4 server on a given interface. DHCPv4 requests will no + * longer be handled on the interface, and all of the allocations are cleared. + * + * @param iface A valid pointer on an interface + * + * @return 0 on success, a negative error code otherwise. + */ +int net_dhcpv4_server_stop(struct net_if *iface); + +/** + * @typedef net_dhcpv4_lease_cb_t + * @brief Callback used while iterating over active DHCPv4 address leases + * + * @param iface Pointer to the network interface + * @param lease Pointer to the DHPCv4 address lease slot + * @param user_data A valid pointer to user data or NULL + */ +typedef void (*net_dhcpv4_lease_cb_t)(struct net_if *iface, + struct dhcpv4_addr_slot *lease, + void *user_data); + +/** + * @brief Iterate over all DHCPv4 address leases on a given network interface + * and call callback for each lease. In case no network interface is provided + * (NULL interface pointer), will iterate over all interfaces running DHCPv4 + * server instance. + * + * @param iface Pointer to the network interface, can be NULL + * @param cb User-supplied callback function to call + * @param user_data User specified data + */ +int net_dhcpv4_server_foreach_lease(struct net_if *iface, + net_dhcpv4_lease_cb_t cb, + void *user_data); + +/** + * @} + */ + +#ifdef __cplusplus +} +#endif + +#endif /* ZEPHYR_INCLUDE_NET_DHCPV4_SERVER_H_ */ diff --git a/subsys/net/ip/dhcpv4.h b/subsys/net/ip/dhcpv4.h index a274e3a6aea..712d361ac80 100644 --- a/subsys/net/ip/dhcpv4.h +++ b/subsys/net/ip/dhcpv4.h @@ -62,6 +62,7 @@ struct dhcp_msg { #define DHCPV4_OPTIONS_REQ_LIST 55 #define DHCPV4_OPTIONS_RENEWAL 58 #define DHCPV4_OPTIONS_REBINDING 59 +#define DHCPV4_OPTIONS_CLIENT_ID 61 #define DHCPV4_OPTIONS_END 255 /* Useful size macros */ @@ -142,4 +143,14 @@ static inline bool net_dhcpv4_accept_unicast(struct net_pkt *pkt) #endif /* CONFIG_NET_DHCPV4 && CONFIG_NET_DHCPV4_ACCEPT_UNICAST */ +#if defined(CONFIG_NET_DHCPV4_SERVER) + +void net_dhcpv4_server_init(void); + +#else + +#define net_dhcpv4_server_init() + +#endif /* CONFIG_NET_DHCPV4_SERVER */ + #endif /* __INTERNAL_DHCPV4_H */ diff --git a/subsys/net/ip/net_core.c b/subsys/net/ip/net_core.c index baa8cd695c5..3187b40a66b 100644 --- a/subsys/net/ip/net_core.c +++ b/subsys/net/ip/net_core.c @@ -495,6 +495,8 @@ static inline int services_init(void) return status; } + net_dhcpv4_server_init(); + dns_init_resolver(); websocket_init(); diff --git a/subsys/net/lib/CMakeLists.txt b/subsys/net/lib/CMakeLists.txt index 5e4eae7e028..e3319f6cd8d 100644 --- a/subsys/net/lib/CMakeLists.txt +++ b/subsys/net/lib/CMakeLists.txt @@ -15,6 +15,7 @@ add_subdirectory_ifdef(CONFIG_NET_CAPTURE capture) add_subdirectory_ifdef(CONFIG_NET_ZPERF zperf) add_subdirectory_ifdef(CONFIG_NET_SHELL shell) add_subdirectory_ifdef(CONFIG_NET_TRICKLE trickle) +add_subdirectory_ifdef(CONFIG_NET_DHCPV4_SERVER dhcpv4) if (CONFIG_DNS_RESOLVER OR CONFIG_MDNS_RESPONDER diff --git a/subsys/net/lib/Kconfig b/subsys/net/lib/Kconfig index b70105d5f9b..6cfa0445241 100644 --- a/subsys/net/lib/Kconfig +++ b/subsys/net/lib/Kconfig @@ -39,6 +39,8 @@ menu "Network additional services" source "subsys/net/lib/capture/Kconfig" +source "subsys/net/lib/dhcpv4/Kconfig" + source "subsys/net/lib/trickle/Kconfig" source "subsys/net/lib/zperf/Kconfig" diff --git a/subsys/net/lib/dhcpv4/CMakeLists.txt b/subsys/net/lib/dhcpv4/CMakeLists.txt new file mode 100644 index 00000000000..348b6c99bb7 --- /dev/null +++ b/subsys/net/lib/dhcpv4/CMakeLists.txt @@ -0,0 +1,11 @@ +# Copyright (c) 2024 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: Apache-2.0 + +zephyr_library() + +zephyr_library_include_directories(${ZEPHYR_BASE}/subsys/net/ip) + +zephyr_library_sources( + dhcpv4_server.c + ) diff --git a/subsys/net/lib/dhcpv4/Kconfig b/subsys/net/lib/dhcpv4/Kconfig new file mode 100644 index 00000000000..801d536a5a1 --- /dev/null +++ b/subsys/net/lib/dhcpv4/Kconfig @@ -0,0 +1,46 @@ +# DHCPv4 server implementation for Zephyr + +# Copyright (c) 2024 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: Apache-2.0 + +config NET_DHCPV4_SERVER + bool "DHCPv4 server" + depends on NET_IPV4 && NET_UDP + select NET_SOCKETS + select NET_SOCKETS_SERVICE + +if NET_DHCPV4_SERVER + +module = NET_DHCPV4_SERVER +module-dep = NET_LOG +module-str = Log level for DHCPv4 server +module-help = Enables DHCPv4 server output debug messages +source "subsys/net/Kconfig.template.log_config.net" + +config NET_DHCPV4_SERVER_INSTANCES + int "Maximum number of DHCPv4 server instances" + default 1 + help + Maximum number of DHCPv4 server instances supported by the system. + Each network interface that wants to act as a DHCPv4 server requires + a separate instance. + +config NET_DHCPV4_SERVER_ADDR_COUNT + int "Number of IPv4 addresses that can be assigned by the server" + default 4 + help + Maximum number of IPv4 addresses that can be assigned by the DHCPv4 + server instance. The base IPv4 address in the address pool is provided + at runtime, during server initialization. Consecutive addresses in the + pool have the lowest address octet incremented. + +config NET_DHCPV4_SERVER_ADDR_LEASE_TIME + int "Lease time for IPv4 addresses assigned by the server (seconds)" + default 86400 + help + Lease time in seconds for IPv4 addresses assigned by the server. + The lease time determines when the IPv4 address lease expires if the + client does not renew it. + +endif # NET_DHCPV4_SERVER diff --git a/subsys/net/lib/dhcpv4/dhcpv4_server.c b/subsys/net/lib/dhcpv4/dhcpv4_server.c new file mode 100644 index 00000000000..714d036ddde --- /dev/null +++ b/subsys/net/lib/dhcpv4/dhcpv4_server.c @@ -0,0 +1,1439 @@ +/** @file + * @brief DHCPv4 server implementation + */ + +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +LOG_MODULE_REGISTER(net_dhcpv4_server, CONFIG_NET_DHCPV4_SERVER_LOG_LEVEL); + +#include "dhcpv4.h" +#include "net_private.h" +#include "../../l2/ethernet/arp.h" + +#define DHCPV4_OPTIONS_MSG_TYPE_SIZE 3 +#define DHCPV4_OPTIONS_IP_LEASE_TIME_SIZE 6 +#define DHCPV4_OPTIONS_SERVER_ID_SIZE 6 +#define DHCPV4_OPTIONS_SUBNET_MASK_SIZE 6 +#define DHCPV4_OPTIONS_CLIENT_ID_MIN_SIZE 2 + +#define ADDRESS_RESERVED_TIMEOUT K_SECONDS(5) + +/* RFC 1497 [17] */ +static const uint8_t magic_cookie[4] = { 0x63, 0x82, 0x53, 0x63 }; + +struct dhcpv4_server_ctx { + struct net_if *iface; + int sock; + struct k_work_delayable timeout_work; + struct dhcpv4_addr_slot addr_pool[CONFIG_NET_DHCPV4_SERVER_ADDR_COUNT]; + struct in_addr server_addr; + struct in_addr netmask; +}; + +static struct dhcpv4_server_ctx server_ctx[CONFIG_NET_DHCPV4_SERVER_INSTANCES]; +static struct zsock_pollfd fds[CONFIG_NET_DHCPV4_SERVER_INSTANCES]; +static K_MUTEX_DEFINE(server_lock); + +#define DHCPV4_MAX_PARAMETERS_REQUEST_LEN 16 + +struct dhcpv4_parameter_request_list { + uint8_t list[DHCPV4_MAX_PARAMETERS_REQUEST_LEN]; + uint8_t count; +}; + +static void dhcpv4_server_timeout_recalc(struct dhcpv4_server_ctx *ctx) +{ + k_timepoint_t next = sys_timepoint_calc(K_FOREVER); + k_timeout_t timeout; + + for (int i = 0; i < ARRAY_SIZE(ctx->addr_pool); i++) { + struct dhcpv4_addr_slot *slot = &ctx->addr_pool[i]; + + if (slot->state == DHCPV4_SERVER_ADDR_RESERVED || + slot->state == DHCPV4_SERVER_ADDR_ALLOCATED) { + if (sys_timepoint_cmp(slot->expiry, next) < 0) { + next = slot->expiry; + } + } + } + + timeout = sys_timepoint_timeout(next); + + if (K_TIMEOUT_EQ(timeout, K_FOREVER)) { + LOG_DBG("No more addresses, canceling timer"); + k_work_cancel_delayable(&ctx->timeout_work); + } else { + k_work_reschedule(&ctx->timeout_work, timeout); + } +} + +/* Option parsing. */ + +static uint8_t *dhcpv4_find_option(uint8_t *data, size_t datalen, + uint8_t *optlen, uint8_t opt_code) +{ + uint8_t *opt = NULL; + + while (datalen > 0) { + uint8_t code; + uint8_t len; + + code = *data; + + /* Two special cases (fixed sized options) */ + if (code == 0) { + data++; + datalen--; + continue; + } + + if (code == DHCPV4_OPTIONS_END) { + break; + } + + /* Length field should now follow. */ + if (datalen < 2) { + break; + } + + len = *(data + 1); + + if (datalen < len + 2) { + break; + } + + if (code == opt_code) { + /* Found the option. */ + opt = data + 2; + *optlen = len; + break; + } + + data += len + 2; + datalen -= len + 2; + } + + return opt; +} + +static int dhcpv4_find_message_type_option(uint8_t *data, size_t datalen, + uint8_t *msgtype) +{ + uint8_t *opt; + uint8_t optlen; + + opt = dhcpv4_find_option(data, datalen, &optlen, + DHCPV4_OPTIONS_MSG_TYPE); + if (opt == NULL) { + return -ENOENT; + } + + if (optlen != 1) { + return -EINVAL; + } + + *msgtype = *opt; + + return 0; +} + +static int dhcpv4_find_server_id_option(uint8_t *data, size_t datalen, + struct in_addr *server_id) +{ + uint8_t *opt; + uint8_t optlen; + + opt = dhcpv4_find_option(data, datalen, &optlen, + DHCPV4_OPTIONS_SERVER_ID); + if (opt == NULL) { + return -ENOENT; + } + + if (optlen != sizeof(struct in_addr)) { + return -EINVAL; + } + + memcpy(server_id, opt, sizeof(struct in_addr)); + + return 0; +} + +static int dhcpv4_find_client_id_option(uint8_t *data, size_t datalen, + uint8_t *client_id, uint8_t *len) +{ + uint8_t *opt; + uint8_t optlen; + + opt = dhcpv4_find_option(data, datalen, &optlen, + DHCPV4_OPTIONS_CLIENT_ID); + if (opt == NULL) { + return -ENOENT; + } + + if (optlen < DHCPV4_OPTIONS_CLIENT_ID_MIN_SIZE) { + return -EINVAL; + } + + if (optlen > *len) { + LOG_ERR("Not enough memory for DHCPv4 client identifier."); + return -ENOMEM; + } + + memcpy(client_id, opt, optlen); + *len = optlen; + + return 0; +} + +static int dhcpv4_find_requested_ip_option(uint8_t *data, size_t datalen, + struct in_addr *requested_ip) +{ + uint8_t *opt; + uint8_t optlen; + + opt = dhcpv4_find_option(data, datalen, &optlen, + DHCPV4_OPTIONS_REQ_IPADDR); + if (opt == NULL) { + return -ENOENT; + } + + if (optlen != sizeof(struct in_addr)) { + return -EINVAL; + } + + memcpy(requested_ip, opt, sizeof(struct in_addr)); + + return 0; +} + +static int dhcpv4_find_ip_lease_time_option(uint8_t *data, size_t datalen, + uint32_t *lease_time) +{ + uint8_t *opt; + uint8_t optlen; + + opt = dhcpv4_find_option(data, datalen, &optlen, + DHCPV4_OPTIONS_LEASE_TIME); + if (opt == NULL) { + return -ENOENT; + } + + if (optlen != sizeof(uint32_t)) { + return -EINVAL; + } + + *lease_time = sys_get_be32(opt); + + return 0; +} + +static int dhcpv4_find_parameter_request_list_option( + uint8_t *data, size_t datalen, + struct dhcpv4_parameter_request_list *params) +{ + uint8_t *opt; + uint8_t optlen; + + opt = dhcpv4_find_option(data, datalen, &optlen, + DHCPV4_OPTIONS_REQ_LIST); + if (opt == NULL) { + return -ENOENT; + } + + if (optlen > sizeof(params->list)) { + /* Best effort here, copy as much as we can. */ + optlen = sizeof(params->list); + } + + memcpy(params->list, opt, optlen); + params->count = optlen; + + return 0; +} + +/* Option encoding. */ + +static uint8_t *dhcpv4_encode_magic_cookie(uint8_t *buf, size_t *buflen) +{ + if (buf == NULL || *buflen < SIZE_OF_MAGIC_COOKIE) { + return NULL; + } + + memcpy(buf, magic_cookie, SIZE_OF_MAGIC_COOKIE); + + *buflen -= SIZE_OF_MAGIC_COOKIE; + + return buf + SIZE_OF_MAGIC_COOKIE; +} + +static uint8_t *dhcpv4_encode_ip_lease_time_option(uint8_t *buf, size_t *buflen, + uint32_t lease_time) +{ + if (buf == NULL || *buflen < DHCPV4_OPTIONS_IP_LEASE_TIME_SIZE) { + return NULL; + } + + buf[0] = DHCPV4_OPTIONS_LEASE_TIME; + buf[1] = sizeof(lease_time); + sys_put_be32(lease_time, &buf[2]); + + *buflen -= DHCPV4_OPTIONS_IP_LEASE_TIME_SIZE; + + return buf + DHCPV4_OPTIONS_IP_LEASE_TIME_SIZE; +} + +static uint8_t *dhcpv4_encode_message_type_option(uint8_t *buf, size_t *buflen, + uint8_t msgtype) +{ + if (buf == NULL || *buflen < DHCPV4_OPTIONS_MSG_TYPE_SIZE) { + return NULL; + } + + buf[0] = DHCPV4_OPTIONS_MSG_TYPE; + buf[1] = 1; + buf[2] = msgtype; + + *buflen -= DHCPV4_OPTIONS_MSG_TYPE_SIZE; + + return buf + DHCPV4_OPTIONS_MSG_TYPE_SIZE; +} + +static uint8_t *dhcpv4_encode_server_id_option(uint8_t *buf, size_t *buflen, + struct in_addr *server_id) +{ + if (buf == NULL || *buflen < DHCPV4_OPTIONS_SERVER_ID_SIZE) { + return NULL; + } + + buf[0] = DHCPV4_OPTIONS_SERVER_ID; + buf[1] = sizeof(struct in_addr); + memcpy(&buf[2], server_id->s4_addr, sizeof(struct in_addr)); + + *buflen -= DHCPV4_OPTIONS_SERVER_ID_SIZE; + + return buf + DHCPV4_OPTIONS_SERVER_ID_SIZE; +} + +static uint8_t *dhcpv4_encode_subnet_mask_option(uint8_t *buf, size_t *buflen, + struct in_addr *mask) +{ + if (buf == NULL || *buflen < DHCPV4_OPTIONS_SUBNET_MASK_SIZE) { + return NULL; + } + + buf[0] = DHCPV4_OPTIONS_SUBNET_MASK; + buf[1] = sizeof(struct in_addr); + memcpy(&buf[2], mask->s4_addr, sizeof(struct in_addr)); + + *buflen -= DHCPV4_OPTIONS_SUBNET_MASK_SIZE; + + return buf + DHCPV4_OPTIONS_SUBNET_MASK_SIZE; +} + +static uint8_t *dhcpv4_encode_end_option(uint8_t *buf, size_t *buflen) +{ + if (buf == NULL || *buflen < 1) { + return NULL; + } + + buf[0] = DHCPV4_OPTIONS_END; + + *buflen -= 1; + + return buf + 1; +} + +/* Response handlers. */ + +static uint8_t *dhcpv4_encode_header(uint8_t *buf, size_t *buflen, + struct dhcp_msg *msg, + struct in_addr *yiaddr) +{ + struct dhcp_msg *reply_msg = (struct dhcp_msg *)buf; + + if (buf == NULL || *buflen < sizeof(struct dhcp_msg)) { + return NULL; + } + + reply_msg->op = DHCPV4_MSG_BOOT_REPLY; + reply_msg->htype = msg->htype; + reply_msg->hlen = msg->hlen; + reply_msg->hops = 0; + reply_msg->xid = msg->xid; + reply_msg->secs = 0; + reply_msg->flags = msg->flags; + memcpy(reply_msg->ciaddr, msg->ciaddr, sizeof(reply_msg->ciaddr)); + if (yiaddr != NULL) { + memcpy(reply_msg->yiaddr, yiaddr, sizeof(struct in_addr)); + } else { + memset(reply_msg->yiaddr, 0, sizeof(reply_msg->ciaddr)); + } + memset(reply_msg->siaddr, 0, sizeof(reply_msg->siaddr)); + memcpy(reply_msg->giaddr, msg->giaddr, sizeof(reply_msg->giaddr)); + memcpy(reply_msg->chaddr, msg->chaddr, sizeof(reply_msg->chaddr)); + + *buflen -= sizeof(struct dhcp_msg); + + return buf + sizeof(struct dhcp_msg); +} + +static uint8_t *dhcpv4_encode_string(uint8_t *buf, size_t *buflen, char *str, + size_t max_len) +{ + if (buf == NULL || *buflen < max_len) { + return NULL; + } + + memset(buf, 0, max_len); + + if (str == NULL) { + goto out; + } + + strncpy(buf, str, max_len - 1); + + out: + *buflen -= max_len; + + return buf + max_len; +} + +static uint8_t *dhcpv4_encode_sname(uint8_t *buf, size_t *buflen, char *sname) +{ + return dhcpv4_encode_string(buf, buflen, sname, SIZE_OF_SNAME); +} + +static uint8_t *dhcpv4_encode_file(uint8_t *buf, size_t *buflen, char *file) +{ + return dhcpv4_encode_string(buf, buflen, file, SIZE_OF_FILE); +} + +static uint8_t *dhcpv4_encode_requested_params( + uint8_t *buf, size_t *buflen, + struct dhcpv4_server_ctx *ctx, + struct dhcpv4_parameter_request_list *params) +{ + for (uint8_t i = 0; i < params->count; i++) { + switch (params->list[i]) { + case DHCPV4_OPTIONS_SUBNET_MASK: + buf = dhcpv4_encode_subnet_mask_option( + buf, buflen, &ctx->netmask); + if (buf == NULL) { + goto out; + } + break; + + /* Others - just ignore. */ + default: + break; + } + } + +out: + return buf; +} + +static int dhcpv4_send(struct dhcpv4_server_ctx *ctx, enum net_dhcpv4_msg_type type, + uint8_t *reply, size_t len, struct dhcp_msg *msg, + struct in_addr *yiaddr) +{ + struct sockaddr_in dst_addr = { + .sin_family = AF_INET, + .sin_port = htons(DHCPV4_CLIENT_PORT), + }; + struct in_addr giaddr; /* Relay agent address */ + struct in_addr ciaddr; /* Client address */ + int ret; + + memcpy(&giaddr, msg->giaddr, sizeof(giaddr)); + memcpy(&ciaddr, msg->ciaddr, sizeof(ciaddr)); + + /* Select destination address as described in ch. 4.1. */ + if (!net_ipv4_is_addr_unspecified(&giaddr)) { + /* If the 'giaddr' field in a DHCP message from a client is + * non-zero, the server sends any return messages to the + * 'DHCP server' port on the BOOTP relay agent whose address + * appears in 'giaddr'. + */ + dst_addr.sin_addr = giaddr; + dst_addr.sin_port = htons(DHCPV4_SERVER_PORT); + } else if (type == NET_DHCPV4_MSG_TYPE_NAK) { + /* In all cases, when 'giaddr' is zero, the server broadcasts + * any DHCPNAK messages to 0xffffffff. + */ + dst_addr.sin_addr = *net_ipv4_broadcast_address(); + } else if (!net_ipv4_is_addr_unspecified(&ciaddr)) { + /* If the 'giaddr' field is zero and the 'ciaddr' field is + * nonzero, then the server unicasts DHCPOFFER and DHCPACK + * messages to the address in 'ciaddr'. + */ + dst_addr.sin_addr = ciaddr; + } else if (ntohs(msg->flags) & DHCPV4_MSG_BROADCAST) { + /* If 'giaddr' is zero and 'ciaddr' is zero, and the broadcast + * bit is set, then the server broadcasts DHCPOFFER and DHCPACK + * messages to 0xffffffff. + */ + dst_addr.sin_addr = *net_ipv4_broadcast_address(); + } else if (yiaddr != NULL) { + /* If the broadcast bit is not set and 'giaddr' is zero and + * 'ciaddr' is zero, then the server unicasts DHCPOFFER and + * DHCPACK messages to the client's hardware address and 'yiaddr' + * address. + */ + struct net_eth_addr hwaddr; + + memcpy(&hwaddr, msg->chaddr, sizeof(hwaddr)); + net_arp_update(ctx->iface, yiaddr, &hwaddr, false, true); + dst_addr.sin_addr = *yiaddr; + } else { + NET_ERR("Unspecified destination address."); + return -EDESTADDRREQ; + } + + ret = zsock_sendto(ctx->sock, reply, len, 0, (struct sockaddr *)&dst_addr, + sizeof(dst_addr)); + if (ret < 0) { + return -errno; + } + + return 0; +} + +static int dhcpv4_send_offer(struct dhcpv4_server_ctx *ctx, struct dhcp_msg *msg, + struct in_addr *addr, uint32_t lease_time, + struct dhcpv4_parameter_request_list *params) +{ + uint8_t reply[NET_IPV4_MTU]; + uint8_t *buf = reply; + size_t buflen = sizeof(reply); + size_t reply_len = 0; + int ret; + + buf = dhcpv4_encode_header(buf, &buflen, msg, addr); + buf = dhcpv4_encode_sname(buf, &buflen, NULL); + buf = dhcpv4_encode_file(buf, &buflen, NULL); + buf = dhcpv4_encode_magic_cookie(buf, &buflen); + buf = dhcpv4_encode_ip_lease_time_option(buf, &buflen, lease_time); + buf = dhcpv4_encode_message_type_option(buf, &buflen, + NET_DHCPV4_MSG_TYPE_OFFER); + buf = dhcpv4_encode_server_id_option(buf, &buflen, &ctx->server_addr); + buf = dhcpv4_encode_requested_params(buf, &buflen, ctx, params); + buf = dhcpv4_encode_end_option(buf, &buflen); + + if (buf == NULL) { + LOG_ERR("Failed to encode %s message", "Offer"); + return -ENOMEM; + } + + reply_len = sizeof(reply) - buflen; + + ret = dhcpv4_send(ctx, NET_DHCPV4_MSG_TYPE_OFFER, reply, reply_len, + msg, addr); + if (ret < 0) { + LOG_ERR("Failed to send %s message, %d", "Offer", ret); + return ret; + } + + return 0; +} + +static int dhcpv4_send_ack(struct dhcpv4_server_ctx *ctx, struct dhcp_msg *msg, + struct in_addr *addr, uint32_t lease_time, + struct dhcpv4_parameter_request_list *params, + bool inform) +{ + uint8_t reply[NET_IPV4_MTU]; + uint8_t *buf = reply; + size_t buflen = sizeof(reply); + size_t reply_len = 0; + int ret; + + buf = dhcpv4_encode_header(buf, &buflen, msg, inform ? NULL : addr); + buf = dhcpv4_encode_sname(buf, &buflen, NULL); + buf = dhcpv4_encode_file(buf, &buflen, NULL); + buf = dhcpv4_encode_magic_cookie(buf, &buflen); + if (!inform) { + buf = dhcpv4_encode_ip_lease_time_option(buf, &buflen, lease_time); + } + buf = dhcpv4_encode_message_type_option(buf, &buflen, + NET_DHCPV4_MSG_TYPE_ACK); + buf = dhcpv4_encode_server_id_option(buf, &buflen, &ctx->server_addr); + buf = dhcpv4_encode_requested_params(buf, &buflen, ctx, params); + buf = dhcpv4_encode_end_option(buf, &buflen); + + if (buf == NULL) { + LOG_ERR("Failed to encode %s message", "ACK"); + return -ENOMEM; + } + + reply_len = sizeof(reply) - buflen; + + ret = dhcpv4_send(ctx, NET_DHCPV4_MSG_TYPE_ACK, reply, reply_len, msg, + addr); + if (ret < 0) { + LOG_ERR("Failed to send %s message, %d", "ACK", ret); + return ret; + } + + return 0; +} + +static int dhcpv4_send_nak(struct dhcpv4_server_ctx *ctx, struct dhcp_msg *msg) +{ + uint8_t reply[NET_IPV4_MTU]; + uint8_t *buf = reply; + size_t buflen = sizeof(reply); + size_t reply_len = 0; + int ret; + + buf = dhcpv4_encode_header(buf, &buflen, msg, NULL); + buf = dhcpv4_encode_sname(buf, &buflen, NULL); + buf = dhcpv4_encode_file(buf, &buflen, NULL); + buf = dhcpv4_encode_magic_cookie(buf, &buflen); + buf = dhcpv4_encode_message_type_option(buf, &buflen, + NET_DHCPV4_MSG_TYPE_NAK); + buf = dhcpv4_encode_server_id_option(buf, &buflen, &ctx->server_addr); + buf = dhcpv4_encode_end_option(buf, &buflen); + + if (buf == NULL) { + LOG_ERR("Failed to encode %s message", "NAK"); + return -ENOMEM; + } + + reply_len = sizeof(reply) - buflen; + + ret = dhcpv4_send(ctx, NET_DHCPV4_MSG_TYPE_NAK, reply, reply_len, msg, + NULL); + if (ret < 0) { + LOG_ERR("Failed to send %s message, %d", "NAK", ret); + return ret; + } + + return 0; +} + +/* Message handlers. */ + +static int dhcpv4_get_client_id(struct dhcp_msg *msg, uint8_t *options, + uint8_t optlen, struct dhcpv4_client_id *client_id) +{ + int ret; + + client_id->len = sizeof(client_id->buf); + + ret = dhcpv4_find_client_id_option(options, optlen, client_id->buf, + &client_id->len); + if (ret == 0) { + return 0; + } + + /* No Client Id option or too long to use, fallback to hardware address. */ + if (msg->hlen > sizeof(msg->chaddr)) { + LOG_ERR("Malformed chaddr length."); + return -EINVAL; + } + + client_id->buf[0] = msg->htype; + client_id->buf[1] = msg->hlen; + memcpy(client_id->buf + 2, msg->chaddr, msg->hlen); + client_id->len = msg->hlen + 2; + + return 0; +} + +static uint32_t dhcpv4_get_lease_time(uint8_t *options, uint8_t optlen) +{ + uint32_t lease_time; + + if (dhcpv4_find_ip_lease_time_option(options, optlen, + &lease_time) == 0) { + return lease_time; + } + + return CONFIG_NET_DHCPV4_SERVER_ADDR_LEASE_TIME; +} + +static void dhcpv4_handle_discover(struct dhcpv4_server_ctx *ctx, + struct dhcp_msg *msg, uint8_t *options, + uint8_t optlen) +{ + struct dhcpv4_parameter_request_list params = { 0 }; + struct dhcpv4_addr_slot *selected = NULL; + struct dhcpv4_client_id client_id; + int ret; + + ret = dhcpv4_get_client_id(msg, options, optlen, &client_id); + if (ret < 0) { + return; + } + + (void)dhcpv4_find_parameter_request_list_option(options, optlen, ¶ms); + + /* Address pool and address selection algorithm as + * described in 4.3.1 + */ + + /* 1. Check for current bindings */ + for (int i = 0; i < ARRAY_SIZE(ctx->addr_pool); i++) { + struct dhcpv4_addr_slot *slot = &ctx->addr_pool[i]; + + if ((slot->state == DHCPV4_SERVER_ADDR_RESERVED || + slot->state == DHCPV4_SERVER_ADDR_ALLOCATED) && + slot->client_id.len == client_id.len && + memcmp(slot->client_id.buf, client_id.buf, + client_id.len) == 0) { + /* Got match in current bindings. */ + selected = slot; + break; + } + } + + /* 2. Skipped, for now expired/released entries are forgotten. */ + + /* 3. Check Requested IP Address option. */ + if (selected == NULL) { + struct in_addr requested_ip; + + ret = dhcpv4_find_requested_ip_option(options, optlen, + &requested_ip); + if (ret == 0) { + for (int i = 0; i < ARRAY_SIZE(ctx->addr_pool); i++) { + struct dhcpv4_addr_slot *slot = + &ctx->addr_pool[i]; + + if (net_ipv4_addr_cmp(&slot->addr, + &requested_ip) && + slot->state == DHCPV4_SERVER_ADDR_FREE) { + /* Requested address is free. */ + selected = slot; + break; + } + } + } + } + + /* 4. Allocate new address from pool, if available. */ + if (selected == NULL) { + struct in_addr giaddr; + + memcpy(&giaddr, msg->giaddr, sizeof(giaddr)); + if (!net_ipv4_is_addr_unspecified(&giaddr)) { + /* Only addresses in local subnet supproted for now. */ + return; + } + + for (int i = 0; i < ARRAY_SIZE(ctx->addr_pool); i++) { + struct dhcpv4_addr_slot *slot = &ctx->addr_pool[i]; + + if (slot->state == DHCPV4_SERVER_ADDR_FREE) { + /* Requested address is free. */ + selected = slot; + break; + } + } + } + + if (selected == NULL) { + LOG_ERR("No free address found in address pool"); + } else { + uint32_t lease_time = dhcpv4_get_lease_time(options, optlen); + + if (dhcpv4_send_offer(ctx, msg, &selected->addr, lease_time, + ¶ms) < 0) { + return; + } + + LOG_DBG("DHCPv4 processing Discover - reserved %s", + net_sprint_ipv4_addr(&selected->addr)); + + selected->state = DHCPV4_SERVER_ADDR_RESERVED; + selected->client_id.len = client_id.len; + memcpy(selected->client_id.buf, client_id.buf, client_id.len); + selected->lease_time = lease_time; + selected->expiry = sys_timepoint_calc(ADDRESS_RESERVED_TIMEOUT); + dhcpv4_server_timeout_recalc(ctx); + } +} + +static void dhcpv4_handle_request(struct dhcpv4_server_ctx *ctx, + struct dhcp_msg *msg, uint8_t *options, + uint8_t optlen) +{ + struct dhcpv4_parameter_request_list params = { 0 }; + struct dhcpv4_addr_slot *selected = NULL; + struct dhcpv4_client_id client_id; + struct in_addr requested_ip, server_id, ciaddr, giaddr; + int ret; + + memcpy(&ciaddr, msg->ciaddr, sizeof(ciaddr)); + memcpy(&giaddr, msg->giaddr, sizeof(giaddr)); + + if (!net_ipv4_is_addr_unspecified(&giaddr)) { + /* Only addresses in local subnet supported for now. */ + return; + } + + ret = dhcpv4_get_client_id(msg, options, optlen, &client_id); + if (ret < 0) { + /* Failed to obtain Client ID, ignore. */ + return; + } + + (void)dhcpv4_find_parameter_request_list_option(options, optlen, ¶ms); + + ret = dhcpv4_find_server_id_option(options, optlen, &server_id); + if (ret == 0) { + /* Server ID present, Request generated during SELECTING. */ + if (!net_ipv4_addr_cmp(&ctx->server_addr, &server_id)) { + /* Not for us, ignore. */ + return; + } + + ret = dhcpv4_find_requested_ip_option(options, optlen, + &requested_ip); + if (ret < 0) { + /* Requested IP missing, ignore. */ + return; + } + + if (!net_ipv4_is_addr_unspecified(&ciaddr)) { + /* ciaddr MUST be zero */ + return; + } + + for (int i = 0; i < ARRAY_SIZE(ctx->addr_pool); i++) { + struct dhcpv4_addr_slot *slot = &ctx->addr_pool[i]; + + if (net_ipv4_addr_cmp(&slot->addr, &requested_ip) && + slot->client_id.len == client_id.len && + memcmp(slot->client_id.buf, client_id.buf, + client_id.len) == 0 && + slot->state == DHCPV4_SERVER_ADDR_RESERVED) { + selected = slot; + break; + } + } + + if (selected == NULL) { + LOG_ERR("No valid slot found for DHCPv4 Request"); + } else { + uint32_t lease_time = dhcpv4_get_lease_time(options, optlen); + + if (dhcpv4_send_ack(ctx, msg, &selected->addr, lease_time, + ¶ms, false) < 0) { + return; + } + + LOG_DBG("DHCPv4 processing Request - allocated %s", + net_sprint_ipv4_addr(&selected->addr)); + + selected->lease_time = lease_time; + selected->expiry = sys_timepoint_calc( + K_SECONDS(lease_time)); + selected->state = DHCPV4_SERVER_ADDR_ALLOCATED; + dhcpv4_server_timeout_recalc(ctx); + } + + return; + } + + /* No server ID option - check requested address. */ + ret = dhcpv4_find_requested_ip_option(options, optlen, &requested_ip); + if (ret == 0) { + /* Requested IP present, Request generated during INIT-REBOOT. */ + if (!net_ipv4_is_addr_unspecified(&ciaddr)) { + /* ciaddr MUST be zero */ + return; + } + + if (!net_if_ipv4_addr_mask_cmp(ctx->iface, &requested_ip)) { + /* Wrong subnet. */ + dhcpv4_send_nak(ctx, msg); + } + + for (int i = 0; i < ARRAY_SIZE(ctx->addr_pool); i++) { + struct dhcpv4_addr_slot *slot = &ctx->addr_pool[i]; + + if (slot->client_id.len == client_id.len && + memcmp(slot->client_id.buf, client_id.buf, + client_id.len) == 0 && + (slot->state == DHCPV4_SERVER_ADDR_RESERVED || + slot->state == DHCPV4_SERVER_ADDR_ALLOCATED)) { + selected = slot; + break; + } + } + + if (selected != NULL) { + if (net_ipv4_addr_cmp(&selected->addr, &requested_ip)) { + uint32_t lease_time = dhcpv4_get_lease_time( + options, optlen); + + if (dhcpv4_send_ack(ctx, msg, &selected->addr, + lease_time, ¶ms, + false) < 0) { + return; + } + + selected->lease_time = lease_time; + selected->expiry = sys_timepoint_calc( + K_SECONDS(lease_time)); + dhcpv4_server_timeout_recalc(ctx); + } else { + dhcpv4_send_nak(ctx, msg); + } + } + + /* No notion of the client, remain silent. */ + return; + } + + /* Neither server ID or requested IP set, Request generated during + * RENEWING or REBINDING. + */ + + if (!net_if_ipv4_addr_mask_cmp(ctx->iface, &ciaddr)) { + /* Wrong subnet. */ + dhcpv4_send_nak(ctx, msg); + } + + for (int i = 0; i < ARRAY_SIZE(ctx->addr_pool); i++) { + struct dhcpv4_addr_slot *slot = &ctx->addr_pool[i]; + + if (net_ipv4_addr_cmp(&slot->addr, &ciaddr)) { + selected = slot; + break; + } + } + + if (selected != NULL) { + if (selected->state == DHCPV4_SERVER_ADDR_ALLOCATED && + selected->client_id.len == client_id.len && + memcmp(selected->client_id.buf, client_id.buf, + client_id.len) == 0) { + uint32_t lease_time = dhcpv4_get_lease_time( + options, optlen); + + if (dhcpv4_send_ack(ctx, msg, &ciaddr, lease_time, + ¶ms, false) < 0) { + return; + } + + selected->lease_time = lease_time; + selected->expiry = sys_timepoint_calc( + K_SECONDS(lease_time)); + dhcpv4_server_timeout_recalc(ctx); + } else { + dhcpv4_send_nak(ctx, msg); + } + } +} + +static void dhcpv4_handle_decline(struct dhcpv4_server_ctx *ctx, + struct dhcp_msg *msg, uint8_t *options, + uint8_t optlen) +{ + struct dhcpv4_client_id client_id; + struct in_addr requested_ip, server_id; + int ret; + + ret = dhcpv4_find_server_id_option(options, optlen, &server_id); + if (ret < 0) { + /* No server ID, ignore. */ + return; + } + + if (!net_ipv4_addr_cmp(&ctx->server_addr, &server_id)) { + /* Not for us, ignore. */ + return; + } + + ret = dhcpv4_get_client_id(msg, options, optlen, &client_id); + if (ret < 0) { + /* Failed to obtain Client ID, ignore. */ + return; + } + + ret = dhcpv4_find_requested_ip_option(options, optlen, + &requested_ip); + if (ret < 0) { + /* Requested IP missing, ignore. */ + return; + } + + LOG_ERR("Received DHCPv4 Decline for %s (address already in use)", + net_sprint_ipv4_addr(&requested_ip)); + + for (int i = 0; i < ARRAY_SIZE(ctx->addr_pool); i++) { + struct dhcpv4_addr_slot *slot = &ctx->addr_pool[i]; + + if (net_ipv4_addr_cmp(&slot->addr, &requested_ip) && + slot->client_id.len == client_id.len && + memcmp(slot->client_id.buf, client_id.buf, + client_id.len) == 0 && + (slot->state == DHCPV4_SERVER_ADDR_RESERVED || + slot->state == DHCPV4_SERVER_ADDR_ALLOCATED)) { + slot->state = DHCPV4_SERVER_ADDR_DECLINED; + slot->expiry = sys_timepoint_calc(K_FOREVER); + dhcpv4_server_timeout_recalc(ctx); + break; + } + } +} + +static void dhcpv4_handle_release(struct dhcpv4_server_ctx *ctx, + struct dhcp_msg *msg, uint8_t *options, + uint8_t optlen) +{ + struct dhcpv4_client_id client_id; + struct in_addr ciaddr, server_id; + int ret; + + ret = dhcpv4_find_server_id_option(options, optlen, &server_id); + if (ret < 0) { + /* No server ID, ignore. */ + return; + } + + if (!net_ipv4_addr_cmp(&ctx->server_addr, &server_id)) { + /* Not for us, ignore. */ + return; + } + + ret = dhcpv4_get_client_id(msg, options, optlen, &client_id); + if (ret < 0) { + /* Failed to obtain Client ID, ignore. */ + return; + } + + memcpy(&ciaddr, msg->ciaddr, sizeof(ciaddr)); + + for (int i = 0; i < ARRAY_SIZE(ctx->addr_pool); i++) { + struct dhcpv4_addr_slot *slot = &ctx->addr_pool[i]; + + if (net_ipv4_addr_cmp(&slot->addr, &ciaddr) && + slot->client_id.len == client_id.len && + memcmp(slot->client_id.buf, client_id.buf, + client_id.len) == 0 && + (slot->state == DHCPV4_SERVER_ADDR_RESERVED || + slot->state == DHCPV4_SERVER_ADDR_ALLOCATED)) { + LOG_DBG("DHCPv4 processing Release - %s", + net_sprint_ipv4_addr(&slot->addr)); + + slot->state = DHCPV4_SERVER_ADDR_FREE; + slot->expiry = sys_timepoint_calc(K_FOREVER); + dhcpv4_server_timeout_recalc(ctx); + break; + } + } +} + +static void dhcpv4_handle_inform(struct dhcpv4_server_ctx *ctx, + struct dhcp_msg *msg, uint8_t *options, + uint8_t optlen) +{ + struct dhcpv4_parameter_request_list params = { 0 }; + + (void)dhcpv4_find_parameter_request_list_option(options, optlen, ¶ms); + (void)dhcpv4_send_ack(ctx, msg, (struct in_addr *)msg->ciaddr, 0, + ¶ms, true); +} + +/* Server core. */ + +static void dhcpv4_server_timeout(struct k_work *work) +{ + struct k_work_delayable *dwork = k_work_delayable_from_work(work); + struct dhcpv4_server_ctx *ctx = + CONTAINER_OF(dwork, struct dhcpv4_server_ctx, timeout_work); + + + k_mutex_lock(&server_lock, K_FOREVER); + + for (int i = 0; i < ARRAY_SIZE(ctx->addr_pool); i++) { + struct dhcpv4_addr_slot *slot = &ctx->addr_pool[i]; + + if (slot->state == DHCPV4_SERVER_ADDR_RESERVED || + slot->state == DHCPV4_SERVER_ADDR_ALLOCATED) { + if (sys_timepoint_expired(slot->expiry)) { + LOG_DBG("Address %s expired", + net_sprint_ipv4_addr(&slot->addr)); + slot->state = DHCPV4_SERVER_ADDR_FREE; + } + } + } + + dhcpv4_server_timeout_recalc(ctx); + + k_mutex_unlock(&server_lock); +} + +static void dhcpv4_process_data(struct dhcpv4_server_ctx *ctx, uint8_t *data, + size_t datalen) +{ + struct dhcp_msg *msg; + uint8_t msgtype; + int ret; + + if (datalen < sizeof(struct dhcp_msg)) { + LOG_DBG("DHCPv4 server malformed message"); + return; + } + + msg = (struct dhcp_msg *)data; + + if (msg->op != DHCPV4_MSG_BOOT_REQUEST) { + /* Silently drop messages other than BOOTREQUEST */ + return; + } + + data += sizeof(struct dhcp_msg); + datalen -= sizeof(struct dhcp_msg); + + /* Skip server hostname/filename/option cookie */ + if (datalen < (SIZE_OF_SNAME + SIZE_OF_FILE + SIZE_OF_MAGIC_COOKIE)) { + return; + } + + data += SIZE_OF_SNAME + SIZE_OF_FILE + SIZE_OF_MAGIC_COOKIE; + datalen -= SIZE_OF_SNAME + SIZE_OF_FILE + SIZE_OF_MAGIC_COOKIE; + + /* Search options for DHCP message type. */ + ret = dhcpv4_find_message_type_option(data, datalen, &msgtype); + if (ret < 0) { + LOG_ERR("No message type option"); + return; + } + + k_mutex_lock(&server_lock, K_FOREVER); + + switch (msgtype) { + case NET_DHCPV4_MSG_TYPE_DISCOVER: + dhcpv4_handle_discover(ctx, msg, data, datalen); + break; + case NET_DHCPV4_MSG_TYPE_REQUEST: + dhcpv4_handle_request(ctx, msg, data, datalen); + break; + case NET_DHCPV4_MSG_TYPE_DECLINE: + dhcpv4_handle_decline(ctx, msg, data, datalen); + break; + case NET_DHCPV4_MSG_TYPE_RELEASE: + dhcpv4_handle_release(ctx, msg, data, datalen); + break; + case NET_DHCPV4_MSG_TYPE_INFORM: + dhcpv4_handle_inform(ctx, msg, data, datalen); + break; + + case NET_DHCPV4_MSG_TYPE_OFFER: + case NET_DHCPV4_MSG_TYPE_ACK: + case NET_DHCPV4_MSG_TYPE_NAK: + default: + /* Ignore server initiated and unknown message types. */ + break; + } + + k_mutex_unlock(&server_lock); +} + +static void dhcpv4_server_cb(struct k_work *work) +{ + struct net_socket_service_event *evt = + CONTAINER_OF(work, struct net_socket_service_event, work); + struct dhcpv4_server_ctx *ctx = NULL; + uint8_t recv_buf[NET_IPV4_MTU]; + int ret; + + for (int i = 0; i < ARRAY_SIZE(server_ctx); i++) { + if (server_ctx[i].sock == evt->event.fd) { + ctx = &server_ctx[i]; + break; + } + } + + if (ctx == NULL) { + LOG_ERR("No DHCPv4 server context found for given FD."); + return; + } + + if (evt->event.revents & ZSOCK_POLLERR) { + LOG_ERR("DHCPv4 server poll revents error"); + net_dhcpv4_server_stop(ctx->iface); + return; + } + + if (!(evt->event.revents & ZSOCK_POLLIN)) { + return; + } + + ret = zsock_recvfrom(evt->event.fd, recv_buf, sizeof(recv_buf), + ZSOCK_MSG_DONTWAIT, NULL, 0); + if (ret < 0) { + if (errno == EAGAIN) { + return; + } + + LOG_ERR("DHCPv4 server recv error, %d", errno); + net_dhcpv4_server_stop(ctx->iface); + return; + } + + dhcpv4_process_data(ctx, recv_buf, ret); +} + +NET_SOCKET_SERVICE_SYNC_DEFINE_STATIC(dhcpv4_server, NULL, dhcpv4_server_cb, + CONFIG_NET_DHCPV4_SERVER_INSTANCES); + +int net_dhcpv4_server_start(struct net_if *iface, struct in_addr *base_addr) +{ + struct sockaddr_in addr = { + .sin_family = AF_INET, + .sin_addr = INADDR_ANY_INIT, + .sin_port = htons(DHCPV4_SERVER_PORT), + }; + struct ifreq ifreq = { 0 }; + int ret, sock = -1, slot = -1; + const struct in_addr *server_addr; + struct in_addr netmask; + + if (iface == NULL || base_addr == NULL) { + return -EINVAL; + } + + if (!net_if_ipv4_addr_mask_cmp(iface, base_addr)) { + LOG_ERR("Address pool does not belong to the interface subnet."); + return -EINVAL; + } + + server_addr = net_if_ipv4_select_src_addr(iface, base_addr); + if (server_addr == NULL) { + LOG_ERR("Failed to obtain a valid server address."); + return -EINVAL; + } + + netmask = net_if_ipv4_get_netmask(iface); + if (net_ipv4_is_addr_unspecified(&netmask)) { + LOG_ERR("Failed to obtain subnet mask."); + return -EINVAL; + } + + k_mutex_lock(&server_lock, K_FOREVER); + + for (int i = 0; i < ARRAY_SIZE(server_ctx); i++) { + if (server_ctx[i].iface != NULL) { + if (server_ctx[i].iface == iface) { + LOG_ERR("DHCPv4 server instance already running."); + ret = -EALREADY; + goto error; + } + } else { + if (slot < 0) { + slot = i; + } + } + } + + if (slot < 0) { + LOG_ERR("No free DHCPv4 server intance."); + ret = -ENOMEM; + goto error; + } + + ret = net_if_get_name(iface, ifreq.ifr_name, sizeof(ifreq.ifr_name)); + if (ret < 0) { + LOG_ERR("Failed to obtain interface name."); + goto error; + } + + sock = zsock_socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (sock < 0) { + ret = errno; + LOG_ERR("Failed to create DHCPv4 server socket, %d", ret); + goto error; + } + + ret = zsock_setsockopt(sock, SOL_SOCKET, SO_BINDTODEVICE, &ifreq, + sizeof(ifreq)); + if (ret < 0) { + ret = errno; + LOG_ERR("Failed to bind DHCPv4 server socket with interface, %d", + ret); + goto error; + } + + ret = zsock_bind(sock, (struct sockaddr *)&addr, sizeof(addr)); + if (ret < 0) { + ret = errno; + LOG_ERR("Failed to bind DHCPv4 server socket, %d", ret); + goto error; + } + + fds[slot].fd = sock; + fds[slot].events = ZSOCK_POLLIN; + + server_ctx[slot].iface = iface; + server_ctx[slot].sock = sock; + server_ctx[slot].server_addr = *server_addr; + server_ctx[slot].netmask = netmask; + + k_work_init_delayable(&server_ctx[slot].timeout_work, + dhcpv4_server_timeout); + + LOG_DBG("Started DHCPv4 server, address pool:"); + for (int i = 0; i < ARRAY_SIZE(server_ctx[slot].addr_pool); i++) { + server_ctx[slot].addr_pool[i].state = DHCPV4_SERVER_ADDR_FREE; + server_ctx[slot].addr_pool[i].addr.s_addr = + htonl(ntohl(base_addr->s_addr) + i); + + LOG_DBG("\t%2d: %s", i, + net_sprint_ipv4_addr( + &server_ctx[slot].addr_pool[i].addr)); + } + + ret = net_socket_service_register(&dhcpv4_server, fds, ARRAY_SIZE(fds), + NULL); + if (ret < 0) { + LOG_ERR("Failed to register socket service, %d", ret); + memset(&server_ctx[slot], 0, sizeof(server_ctx[slot])); + fds[slot].fd = -1; + goto error; + } + + k_mutex_unlock(&server_lock); + + return 0; + +error: + if (sock >= 0) { + (void)zsock_close(sock); + } + + k_mutex_unlock(&server_lock); + + return ret; +} + +int net_dhcpv4_server_stop(struct net_if *iface) +{ + struct k_work_sync sync; + int slot = -1; + int ret = 0; + bool service_stop = true; + + if (iface == NULL) { + return -EINVAL; + } + + k_mutex_lock(&server_lock, K_FOREVER); + + for (int i = 0; i < ARRAY_SIZE(server_ctx); i++) { + if (server_ctx[i].iface == iface) { + slot = i; + break; + } + } + + if (slot < 0) { + ret = -ENOENT; + goto out; + } + + fds[slot].fd = -1; + (void)zsock_close(server_ctx[slot].sock); + + k_work_cancel_delayable_sync(&server_ctx[slot].timeout_work, &sync); + + memset(&server_ctx[slot], 0, sizeof(server_ctx[slot])); + + for (int i = 0; i < ARRAY_SIZE(fds); i++) { + if (fds[i].fd >= 0) { + service_stop = false; + break; + } + } + + if (service_stop) { + ret = net_socket_service_unregister(&dhcpv4_server); + } else { + ret = net_socket_service_register(&dhcpv4_server, fds, + ARRAY_SIZE(fds), NULL); + } + +out: + k_mutex_unlock(&server_lock); + + return ret; +} + +static void dhcpv4_server_foreach_lease_on_ctx(struct dhcpv4_server_ctx *ctx, + net_dhcpv4_lease_cb_t cb, + void *user_data) +{ + for (int i = 0; i < ARRAY_SIZE(ctx->addr_pool); i++) { + struct dhcpv4_addr_slot *addr = &ctx->addr_pool[i]; + + if (addr->state != DHCPV4_SERVER_ADDR_FREE) { + cb(ctx->iface, addr, user_data); + } + } +} + +int net_dhcpv4_server_foreach_lease(struct net_if *iface, + net_dhcpv4_lease_cb_t cb, + void *user_data) +{ + int slot = -1; + int ret = 0; + + k_mutex_lock(&server_lock, K_FOREVER); + + if (iface == NULL) { + for (int i = 0; i < ARRAY_SIZE(server_ctx); i++) { + if (server_ctx[i].iface != NULL) { + dhcpv4_server_foreach_lease_on_ctx( + &server_ctx[i], cb, user_data); + } + } + + return 0; + } + + for (int i = 0; i < ARRAY_SIZE(server_ctx); i++) { + if (server_ctx[i].iface == iface) { + slot = i; + break; + } + } + + if (slot < 0) { + ret = -ENOENT; + goto out; + } + + dhcpv4_server_foreach_lease_on_ctx(&server_ctx[slot], cb, user_data); + +out: + k_mutex_unlock(&server_lock); + + return ret; +} + +void net_dhcpv4_server_init(void) +{ + for (int i = 0; i < ARRAY_SIZE(fds); i++) { + fds[i].fd = -1; + } +} From c1f96a6bf0ef4f150b1b8e7272ec5a0bff652f25 Mon Sep 17 00:00:00 2001 From: Robert Lubos Date: Tue, 16 Jan 2024 17:38:40 +0100 Subject: [PATCH 8/9] [nrf fromtree] net: socket_services: Increase default stack size for DHCPv4 server Increase socket services thread default stack size when DHCPv4 server is enabled, as it uses synchronous processing. Signed-off-by: Robert Lubos (cherry picked from commit 3bc50871bccb7a4ff9c03c845ac9a4a2208a0d12) --- subsys/net/lib/sockets/Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/subsys/net/lib/sockets/Kconfig b/subsys/net/lib/sockets/Kconfig index 40d86b9498f..35750dd3583 100644 --- a/subsys/net/lib/sockets/Kconfig +++ b/subsys/net/lib/sockets/Kconfig @@ -88,6 +88,7 @@ config NET_SOCKETS_SERVICE config NET_SOCKETS_SERVICE_STACK_SIZE int "Stack size for the thread handling socket services" + default 2400 if NET_DHCPV4_SERVER default 1200 depends on NET_SOCKETS_SERVICE help From 0aef8712c570a8d4d16e793cfa08e25ee9cd6a90 Mon Sep 17 00:00:00 2001 From: Robert Lubos Date: Tue, 16 Jan 2024 17:38:23 +0100 Subject: [PATCH 9/9] [nrf fromtree] net: shell: Implement DHCPv4 server shell commands Implement DHCPv4 shell module, which allows to start/stop DHCPv4 server operation, and print server status (address leases). Signed-off-by: Robert Lubos (cherry picked from commit 2c70c5d74adb250420506d8a7f2aa40631a24916) --- subsys/net/lib/shell/CMakeLists.txt | 1 + subsys/net/lib/shell/dhcpv4.c | 215 ++++++++++++++++++++++++++++ 2 files changed, 216 insertions(+) create mode 100644 subsys/net/lib/shell/dhcpv4.c diff --git a/subsys/net/lib/shell/CMakeLists.txt b/subsys/net/lib/shell/CMakeLists.txt index 2c5c0757505..5cfb5c269b4 100644 --- a/subsys/net/lib/shell/CMakeLists.txt +++ b/subsys/net/lib/shell/CMakeLists.txt @@ -10,6 +10,7 @@ zephyr_library_sources(allocs.c) zephyr_library_sources(arp.c) zephyr_library_sources(capture.c) zephyr_library_sources(conn.c) +zephyr_library_sources(dhcpv4.c) zephyr_library_sources(dns.c) zephyr_library_sources(events.c) zephyr_library_sources(gptp.c) diff --git a/subsys/net/lib/shell/dhcpv4.c b/subsys/net/lib/shell/dhcpv4.c new file mode 100644 index 00000000000..a59b9c45edb --- /dev/null +++ b/subsys/net/lib/shell/dhcpv4.c @@ -0,0 +1,215 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +LOG_MODULE_DECLARE(net_shell); + +#include +#include +#include + +#include "net_shell_private.h" + +static int cmd_net_dhcpv4_server_start(const struct shell *sh, size_t argc, char *argv[]) +{ +#if defined(CONFIG_NET_DHCPV4_SERVER) + struct net_if *iface = NULL; + struct in_addr base_addr; + int idx, ret; + + idx = get_iface_idx(sh, argv[1]); + if (idx < 0) { + return -ENOEXEC; + } + + iface = net_if_get_by_index(idx); + if (!iface) { + PR_WARNING("No such interface in index %d\n", idx); + return -ENOEXEC; + } + + if (net_addr_pton(AF_INET, argv[2], &base_addr)) { + PR_ERROR("Invalid address: %s\n", argv[2]); + return -EINVAL; + } + + ret = net_dhcpv4_server_start(iface, &base_addr); + if (ret == -EALREADY) { + PR_WARNING("DHCPv4 server already running on interface %d\n", idx); + } else if (ret < 0) { + PR_ERROR("DHCPv4 server failed to start on interface %d, error %d\n", + idx, -ret); + } else { + PR("DHCPv4 server started on interface %d\n", idx); + } +#else /* CONFIG_NET_DHCPV4_SERVER */ + PR_INFO("Set %s to enable %s support.\n", + "CONFIG_NET_DHCPV4_SERVER", "DHCPv4 server"); +#endif /* CONFIG_NET_DHCPV4_SERVER */ + return 0; +} + +static int cmd_net_dhcpv4_server_stop(const struct shell *sh, size_t argc, char *argv[]) +{ +#if defined(CONFIG_NET_DHCPV4_SERVER) + struct net_if *iface = NULL; + int idx, ret; + + idx = get_iface_idx(sh, argv[1]); + if (idx < 0) { + return -ENOEXEC; + } + + iface = net_if_get_by_index(idx); + if (!iface) { + PR_WARNING("No such interface in index %d\n", idx); + return -ENOEXEC; + } + + ret = net_dhcpv4_server_stop(iface); + if (ret == -ENOENT) { + PR_WARNING("DHCPv4 server is not running on interface %d\n", idx); + } else if (ret < 0) { + PR_ERROR("DHCPv4 server failed to stop on interface %d, error %d\n", + idx, -ret); + } else { + PR("DHCPv4 server stopped on interface %d\n", idx); + } +#else /* CONFIG_NET_DHCPV4_SERVER */ + PR_INFO("Set %s to enable %s support.\n", + "CONFIG_NET_DHCPV4_SERVER", "DHCPv4 server"); +#endif /* CONFIG_NET_DHCPV4_SERVER */ + return 0; +} + +#if defined(CONFIG_NET_DHCPV4_SERVER) +static const char *dhcpv4_addr_state_to_str(enum dhcpv4_server_addr_state state) +{ + switch (state) { + case DHCPV4_SERVER_ADDR_FREE: + return "FREE"; + case DHCPV4_SERVER_ADDR_RESERVED: + return "RESERVED"; + case DHCPV4_SERVER_ADDR_ALLOCATED: + return "ALLOCATED"; + case DHCPV4_SERVER_ADDR_DECLINED: + return "DECLINED"; + } + + return ""; +} + +static uint32_t timepoint_to_s(k_timepoint_t timepoint) +{ + k_timeout_t timeout = sys_timepoint_timeout(timepoint); + + if (K_TIMEOUT_EQ(timeout, K_NO_WAIT)) { + return 0; + } + + if (K_TIMEOUT_EQ(timeout, K_FOREVER)) { + return UINT32_MAX; + } + + return k_ticks_to_ms_floor64(timeout.ticks) / 1000; +} + +static void dhcpv4_lease_cb(struct net_if *iface, + struct dhcpv4_addr_slot *lease, + void *user_data) +{ + struct net_shell_user_data *data = user_data; + const struct shell *sh = data->sh; + int *count = data->user_data; + char expiry_str[] = "4294967295"; /* Lease time is uint32_t, so take + * theoretical max. + */ + char iface_name[IFNAMSIZ] = ""; + + if (*count == 0) { + PR(" Iface Address\t State\tExpiry (sec)\n"); + } + + (*count)++; + + (void)net_if_get_name(iface, iface_name, sizeof(iface_name)); + + if (lease->state == DHCPV4_SERVER_ADDR_DECLINED) { + snprintk(expiry_str, sizeof(expiry_str) - 1, "infinite"); + } else { + snprintk(expiry_str, sizeof(expiry_str) - 1, "%u", + timepoint_to_s(lease->expiry)); + } + + PR("%2d. %6s %15s\t%9s\t%12s\n", + *count, iface_name, net_sprint_ipv4_addr(&lease->addr), + dhcpv4_addr_state_to_str(lease->state), expiry_str); +} +#endif /* CONFIG_NET_DHCPV4_SERVER */ + +static int cmd_net_dhcpv4_server_status(const struct shell *sh, size_t argc, char *argv[]) +{ +#if defined(CONFIG_NET_DHCPV4_SERVER) + struct net_shell_user_data user_data; + struct net_if *iface = NULL; + int idx = 0, ret; + int count = 0; + + if (argc > 1) { + idx = get_iface_idx(sh, argv[1]); + if (idx < 0) { + return -ENOEXEC; + } + + iface = net_if_get_by_index(idx); + if (!iface) { + PR_WARNING("No such interface in index %d\n", idx); + return -ENOEXEC; + } + } + + user_data.sh = sh; + user_data.user_data = &count; + + ret = net_dhcpv4_server_foreach_lease(iface, dhcpv4_lease_cb, &user_data); + if (ret == -ENOENT) { + PR_WARNING("DHCPv4 server is not running on interface %d\n", idx); + } else if (count == 0) { + PR("DHCPv4 server - no addresses assigned\n"); + } +#else /* CONFIG_NET_DHCPV4_SERVER */ + PR_INFO("Set %s to enable %s support.\n", + "CONFIG_NET_DHCPV4_SERVER", "DHCPv4 server"); +#endif /* CONFIG_NET_DHCPV4_SERVER */ + return 0; +} + +SHELL_STATIC_SUBCMD_SET_CREATE(net_cmd_dhcpv4_server, + SHELL_CMD_ARG(start, NULL, "Start the DHCPv4 server operation on the interface.\n" + "'net dhcpv4 server start '\n" + " is the network interface index.\n" + " is the first address for the address pool.", + cmd_net_dhcpv4_server_start, 3, 0), + SHELL_CMD_ARG(stop, NULL, "Stop the DHCPv4 server operation on the interface.\n" + "'net dhcpv4 server stop '\n" + " is the network interface index.", + cmd_net_dhcpv4_server_stop, 2, 0), + SHELL_CMD_ARG(status, NULL, "Print the DHCPv4 server status on the interface.\n" + "'net dhcpv4 server status '\n" + " is the network interface index. Optional.", + cmd_net_dhcpv4_server_status, 1, 1), + SHELL_SUBCMD_SET_END +); + +SHELL_STATIC_SUBCMD_SET_CREATE(net_cmd_dhcpv4, + SHELL_CMD(server, &net_cmd_dhcpv4_server, + "DHCPv4 server service management.", + NULL), + SHELL_SUBCMD_SET_END +); + +SHELL_SUBCMD_ADD((net), dhcpv4, &net_cmd_dhcpv4, "Manage DHPCv4 services.", + NULL, 1, 0);