/** @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 #include LOG_MODULE_REGISTER(net_dhcpv4_server, CONFIG_NET_DHCPV4_SERVER_LOG_LEVEL); #include "dhcpv4_internal.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_ROUTER_SIZE 6 #define DHCPV4_OPTIONS_DNS_SERVER_SIZE 6 #define DHCPV4_OPTIONS_CLIENT_ID_MIN_SIZE 2 #define ADDRESS_RESERVED_TIMEOUT K_SECONDS(30) #define ADDRESS_PROBE_TIMEOUT K_MSEC(CONFIG_NET_DHCPV4_SERVER_ICMP_PROBE_TIMEOUT) #define ADDRESS_DECLINED_TIMEOUT K_SECONDS(CONFIG_NET_DHCPV4_SERVER_ADDR_DECLINE_TIME) #if (CONFIG_NET_DHCPV4_SERVER_ICMP_PROBE_TIMEOUT > 0) #define DHCPV4_SERVER_ICMP_PROBE 1 #endif /* RFC 1497 [17] */ static const uint8_t magic_cookie[4] = { 0x63, 0x82, 0x53, 0x63 }; #define DHCPV4_MAX_PARAMETERS_REQUEST_LEN 16 struct dhcpv4_parameter_request_list { uint8_t list[DHCPV4_MAX_PARAMETERS_REQUEST_LEN]; uint8_t count; }; struct dhcpv4_server_probe_ctx { struct net_icmp_ctx icmp_ctx; struct dhcp_msg discovery; struct dhcpv4_parameter_request_list params; struct dhcpv4_client_id client_id; struct dhcpv4_addr_slot *slot; }; 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; #if defined(DHCPV4_SERVER_ICMP_PROBE) struct dhcpv4_server_probe_ctx probe_ctx; #endif }; static void *address_provider_callback_user_data; static net_dhcpv4_server_provider_cb_t address_provider_callback; 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); 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 || slot->state == DHCPV4_SERVER_ADDR_DECLINED) { 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_client_id_option(uint8_t *buf, size_t *buflen, struct dhcpv4_client_id *client_id) { if (buf == NULL || *buflen < client_id->len + 2) { return NULL; } buf[0] = DHCPV4_OPTIONS_CLIENT_ID; buf[1] = client_id->len; memcpy(&buf[2], client_id->buf, client_id->len); *buflen -= client_id->len + 2; return buf + client_id->len + 2; } 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_router_option(uint8_t *buf, size_t *buflen, struct in_addr *router) { if (buf == NULL || *buflen < DHCPV4_OPTIONS_ROUTER_SIZE) { return NULL; } buf[0] = DHCPV4_OPTIONS_ROUTER; buf[1] = sizeof(struct in_addr); memcpy(&buf[2], router->s4_addr, sizeof(struct in_addr)); *buflen -= DHCPV4_OPTIONS_ROUTER_SIZE; return buf + DHCPV4_OPTIONS_ROUTER_SIZE; } static uint8_t *dhcpv4_encode_dns_server_option(uint8_t *buf, size_t *buflen) { struct in_addr dns_address; if (buf == NULL || *buflen < DHCPV4_OPTIONS_DNS_SERVER_SIZE) { return NULL; } if (net_addr_pton(AF_INET, CONFIG_NET_DHCPV4_SERVER_OPTION_DNS_ADDRESS, &dns_address)) { LOG_ERR("Invalid DNS server address: %s", CONFIG_NET_DHCPV4_SERVER_OPTION_DNS_ADDRESS); return NULL; } buf[0] = DHCPV4_OPTIONS_DNS_SERVER; buf[1] = sizeof(struct in_addr); memcpy(&buf[2], dns_address.s4_addr, sizeof(struct in_addr)); *buflen -= DHCPV4_OPTIONS_DNS_SERVER_SIZE; return buf + DHCPV4_OPTIONS_DNS_SERVER_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; case DHCPV4_OPTIONS_ROUTER: buf = dhcpv4_encode_router_option( buf, buflen, &ctx->iface->config.ip.ipv4->gw); if (buf == NULL) { goto out; } break; case DHCPV4_OPTIONS_DNS_SERVER: buf = dhcpv4_encode_dns_server_option(buf, buflen); 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, struct dhcpv4_client_id *client_id) { 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_client_id_option(buf, &buflen, client_id); 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, struct dhcpv4_client_id *client_id, 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); if (!inform) { buf = dhcpv4_encode_client_id_option(buf, &buflen, client_id); } 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, struct dhcpv4_client_id *client_id) { 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_client_id_option(buf, &buflen, client_id); 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; memcpy(client_id->buf + 1, msg->chaddr, msg->hlen); client_id->len = msg->hlen + 1; 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; } #if defined(DHCPV4_SERVER_ICMP_PROBE) static int dhcpv4_probe_address(struct dhcpv4_server_ctx *ctx, struct dhcpv4_addr_slot *slot) { struct sockaddr_in dest_addr = { .sin_family = AF_INET, .sin_addr = slot->addr, }; int ret; ret = net_icmp_send_echo_request(&ctx->probe_ctx.icmp_ctx, ctx->iface, (struct sockaddr *)&dest_addr, NULL, ctx); if (ret < 0) { LOG_ERR("Failed to send ICMP probe"); } return ret; } static int echo_reply_handler(struct net_icmp_ctx *icmp_ctx, struct net_pkt *pkt, struct net_icmp_ip_hdr *ip_hdr, struct net_icmp_hdr *icmp_hdr, void *user_data) { struct dhcpv4_server_ctx *ctx = user_data; struct dhcpv4_server_probe_ctx *probe_ctx; struct dhcpv4_addr_slot *new_slot = NULL; struct in_addr peer_addr; ARG_UNUSED(icmp_ctx); ARG_UNUSED(pkt); ARG_UNUSED(ip_hdr); ARG_UNUSED(icmp_hdr); k_mutex_lock(&server_lock, K_FOREVER); if (ctx == NULL) { goto out; } probe_ctx = &ctx->probe_ctx; if (probe_ctx->slot == NULL) { goto out; } if (ip_hdr->family != AF_INET) { goto out; } net_ipv4_addr_copy_raw((uint8_t *)&peer_addr, ip_hdr->ipv4->src); if (!net_ipv4_addr_cmp(&peer_addr, &probe_ctx->slot->addr)) { goto out; } LOG_DBG("Got ICMP probe response, blocking address %s", net_sprint_ipv4_addr(&probe_ctx->slot->addr)); probe_ctx->slot->state = DHCPV4_SERVER_ADDR_DECLINED; probe_ctx->slot->expiry = sys_timepoint_calc(ADDRESS_DECLINED_TIMEOUT); /* Try to find next free address */ 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) { new_slot = slot; break; } } if (new_slot == NULL) { LOG_DBG("No more free addresses to assign, ICMP probing stopped"); probe_ctx->slot = NULL; dhcpv4_server_timeout_recalc(ctx); goto out; } if (dhcpv4_probe_address(ctx, new_slot) < 0) { probe_ctx->slot = NULL; dhcpv4_server_timeout_recalc(ctx); goto out; } new_slot->state = DHCPV4_SERVER_ADDR_RESERVED; new_slot->expiry = sys_timepoint_calc(ADDRESS_PROBE_TIMEOUT); new_slot->client_id.len = probe_ctx->slot->client_id.len; memcpy(new_slot->client_id.buf, probe_ctx->slot->client_id.buf, new_slot->client_id.len); new_slot->lease_time = probe_ctx->slot->lease_time; probe_ctx->slot = new_slot; dhcpv4_server_timeout_recalc(ctx); out: k_mutex_unlock(&server_lock); return 0; } static int dhcpv4_server_probing_init(struct dhcpv4_server_ctx *ctx) { return net_icmp_init_ctx(&ctx->probe_ctx.icmp_ctx, NET_ICMPV4_ECHO_REPLY, 0, echo_reply_handler); } static void dhcpv4_server_probing_deinit(struct dhcpv4_server_ctx *ctx) { (void)net_icmp_cleanup_ctx(&ctx->probe_ctx.icmp_ctx); } static int dhcpv4_server_probe_setup(struct dhcpv4_server_ctx *ctx, struct dhcpv4_addr_slot *slot, struct dhcp_msg *msg, struct dhcpv4_parameter_request_list *params, struct dhcpv4_client_id *client_id) { int ret; if (ctx->probe_ctx.slot != NULL) { return -EBUSY; } ret = dhcpv4_probe_address(ctx, slot); if (ret < 0) { return ret; } ctx->probe_ctx.slot = slot; ctx->probe_ctx.discovery = *msg; ctx->probe_ctx.params = *params; ctx->probe_ctx.client_id = *client_id; return 0; } static void dhcpv4_server_probe_timeout(struct dhcpv4_server_ctx *ctx, struct dhcpv4_addr_slot *slot) { /* Probe timer expired, send offer. */ ctx->probe_ctx.slot = NULL; (void)net_arp_clear_pending(ctx->iface, &slot->addr); if (dhcpv4_send_offer(ctx, &ctx->probe_ctx.discovery, &slot->addr, slot->lease_time, &ctx->probe_ctx.params, &ctx->probe_ctx.client_id) < 0) { slot->state = DHCPV4_SERVER_ADDR_FREE; return; } slot->expiry = sys_timepoint_calc(ADDRESS_RESERVED_TIMEOUT); } static bool dhcpv4_server_is_slot_probed(struct dhcpv4_server_ctx *ctx, struct dhcpv4_addr_slot *slot) { return (ctx->probe_ctx.slot == slot); } #else /* defined(DHCPV4_SERVER_ICMP_PROBE) */ #define dhcpv4_server_probing_init(...) (0) #define dhcpv4_server_probing_deinit(...) #define dhcpv4_server_probe_setup(...) (-ENOTSUP) #define dhcpv4_server_probe_timeout(...) #define dhcpv4_server_is_slot_probed(...) (false) #endif /* defined(DHCPV4_SERVER_ICMP_PROBE) */ 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; bool probe = false; 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) { if (slot->state == DHCPV4_SERVER_ADDR_RESERVED && dhcpv4_server_is_slot_probed(ctx, slot)) { LOG_DBG("ICMP probing in progress, ignore Discovery"); return; } /* Got match in current bindings. */ selected = slot; break; } struct in_addr addr = { 0 }; if (slot->state == DHCPV4_SERVER_ADDR_FREE && address_provider_callback) { ret = address_provider_callback(ctx->iface, &client_id, &addr, address_provider_callback_user_data); if (ret == 0) { selected = slot; slot->addr = addr; } 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; probe = true; 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 supported 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; probe = true; break; } } } /* In case no free address slot was found, as a last resort, try to * reuse the oldest declined entry, if present. */ if (selected == NULL) { 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_DECLINED) { continue; } /* Find first to expire (oldest) entry. */ if ((selected == NULL) || (sys_timepoint_cmp(slot->expiry, selected->expiry) < 0)) { selected = slot; probe = true; } } } if (selected == NULL) { LOG_ERR("No free address found in address pool"); } else { uint32_t lease_time = dhcpv4_get_lease_time(options, optlen); if (IS_ENABLED(DHCPV4_SERVER_ICMP_PROBE) && probe) { if (dhcpv4_server_probe_setup(ctx, selected, msg, ¶ms, &client_id) < 0) { /* Probing context already in use or failed to * send probe, ignore Discovery for now and wait * for retransmission. */ return; } selected->expiry = sys_timepoint_calc(ADDRESS_PROBE_TIMEOUT); } else { if (dhcpv4_send_offer(ctx, msg, &selected->addr, lease_time, ¶ms, &client_id) < 0) { return; } selected->expiry = sys_timepoint_calc(ADDRESS_RESERVED_TIMEOUT); } 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; 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 || slot->state == DHCPV4_SERVER_ADDR_ALLOCATED)) { 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, &client_id, 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, &client_id); } 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, &client_id, 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, &client_id); } } else if (IS_ENABLED(CONFIG_NET_DHCPV4_SERVER_NAK_UNRECOGNIZED_REQUESTS)) { dhcpv4_send_nak(ctx, msg, &client_id); } /* 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, &client_id); } 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, &client_id, 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, &client_id); } } } 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(ADDRESS_DECLINED_TIMEOUT); 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, NULL, 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) && sys_timepoint_expired(slot->expiry)) { if (slot->state == DHCPV4_SERVER_ADDR_RESERVED && dhcpv4_server_is_slot_probed(ctx, slot)) { dhcpv4_server_probe_timeout(ctx, slot); } else { LOG_DBG("Address %s expired", net_sprint_ipv4_addr(&slot->addr)); slot->state = DHCPV4_SERVER_ADDR_FREE; } } if (slot->state == DHCPV4_SERVER_ADDR_DECLINED && sys_timepoint_expired(slot->expiry)) { 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 net_socket_service_event *evt) { 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, 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; } if ((htonl(server_addr->s_addr) >= htonl(base_addr->s_addr)) && (htonl(server_addr->s_addr) < htonl(base_addr->s_addr) + CONFIG_NET_DHCPV4_SERVER_ADDR_COUNT)) { LOG_ERR("Address pool overlaps with server address."); return -EINVAL; } netmask = net_if_ipv4_get_netmask_by_addr(iface, server_addr); 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 instance."); 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 = dhcpv4_server_probing_init(&server_ctx[slot]); if (ret < 0) { LOG_ERR("Failed to register probe handler, %d", ret); goto cleanup; } ret = net_socket_service_register(&dhcpv4_server, fds, ARRAY_SIZE(fds), NULL); if (ret < 0) { LOG_ERR("Failed to register socket service, %d", ret); dhcpv4_server_probing_deinit(&server_ctx[slot]); goto cleanup; } k_mutex_unlock(&server_lock); return 0; cleanup: memset(&server_ctx[slot], 0, sizeof(server_ctx[slot])); fds[slot].fd = -1; 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); dhcpv4_server_probing_deinit(&server_ctx[slot]); 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_set_provider_cb(net_dhcpv4_server_provider_cb_t cb, void *user_data) { address_provider_callback_user_data = user_data; address_provider_callback = cb; } void net_dhcpv4_server_init(void) { address_provider_callback = NULL; address_provider_callback_user_data = NULL; for (int i = 0; i < ARRAY_SIZE(fds); i++) { fds[i].fd = -1; } }