/** @file * @brief DHCPv4 client related functions */ /* * Copyright (c) 2017 ARM Ltd. * Copyright (c) 2016 Intel Corporation * Copyright (c) 2018 Vincent van der Locht * * SPDX-License-Identifier: Apache-2.0 */ #include LOG_MODULE_REGISTER(net_dhcpv4, CONFIG_NET_DHCPV4_LOG_LEVEL); #include #include #include #include #include #include #include #include #include "net_private.h" #include #include "udp_internal.h" #include #include #include "dhcpv4.h" #include "ipv4.h" #include "net_stats.h" #include #include #define PKT_WAIT_TIME K_SECONDS(1) static K_MUTEX_DEFINE(lock); static sys_slist_t dhcpv4_ifaces; static struct k_work_delayable timeout_work; static struct net_mgmt_event_callback mgmt4_cb; #if defined(CONFIG_NET_DHCPV4_OPTION_CALLBACKS) static sys_slist_t option_callbacks; #endif /* RFC 1497 [17] */ static const uint8_t magic_cookie[4] = { 0x63, 0x82, 0x53, 0x63 }; /* Add magic cookie to DCHPv4 messages */ static inline bool dhcpv4_add_cookie(struct net_pkt *pkt) { if (net_pkt_write(pkt, (void *)magic_cookie, ARRAY_SIZE(magic_cookie))) { return false; } return true; } /* Add a an option with the form OPTION LENGTH VALUE. */ static bool dhcpv4_add_option_length_value(struct net_pkt *pkt, uint8_t option, uint8_t size, const void *value) { if (net_pkt_write_u8(pkt, option) || net_pkt_write_u8(pkt, size) || net_pkt_write(pkt, value, size)) { return false; } return true; } /* Add DHCPv4 message type */ static bool dhcpv4_add_msg_type(struct net_pkt *pkt, uint8_t type) { return dhcpv4_add_option_length_value(pkt, DHCPV4_OPTIONS_MSG_TYPE, 1, &type); } /* Add DHCPv4 minimum required options for server to reply. * Can be added more if needed. */ static bool dhcpv4_add_req_options(struct net_pkt *pkt) { static uint8_t data[3] = { DHCPV4_OPTIONS_SUBNET_MASK, DHCPV4_OPTIONS_ROUTER, DHCPV4_OPTIONS_DNS_SERVER }; return dhcpv4_add_option_length_value(pkt, DHCPV4_OPTIONS_REQ_LIST, ARRAY_SIZE(data), data); } static bool dhcpv4_add_server_id(struct net_pkt *pkt, const struct in_addr *addr) { return dhcpv4_add_option_length_value(pkt, DHCPV4_OPTIONS_SERVER_ID, 4, addr->s4_addr); } static bool dhcpv4_add_req_ipaddr(struct net_pkt *pkt, const struct in_addr *addr) { return dhcpv4_add_option_length_value(pkt, DHCPV4_OPTIONS_REQ_IPADDR, 4, addr->s4_addr); } #if defined(CONFIG_NET_HOSTNAME_ENABLE) static bool dhcpv4_add_hostname(struct net_pkt *pkt, const char *hostname, const size_t size) { return dhcpv4_add_option_length_value(pkt, DHCPV4_OPTIONS_HOST_NAME, size, hostname); } #endif /* Add DHCPv4 Options end, rest of the message can be padded wit zeros */ static inline bool dhcpv4_add_end(struct net_pkt *pkt) { if (net_pkt_write_u8(pkt, DHCPV4_OPTIONS_END)) { return false; } return true; } /* File is empty ATM */ static inline bool dhcpv4_add_file(struct net_pkt *pkt) { if (net_pkt_memset(pkt, 0, SIZE_OF_FILE)) { return false; } return true; } /* SNAME is empty ATM */ static inline bool dhcpv4_add_sname(struct net_pkt *pkt) { if (net_pkt_memset(pkt, 0, SIZE_OF_SNAME)) { return false; } return true; } /* Create DHCPv4 message and add options as per message type */ static struct net_pkt *dhcpv4_create_message(struct net_if *iface, uint8_t type, const struct in_addr *ciaddr, const struct in_addr *src_addr, const struct in_addr *server_addr, bool server_id, bool requested_ip) { NET_PKT_DATA_ACCESS_DEFINE(dhcp_access, struct dhcp_msg); const struct in_addr *addr; size_t size = DHCPV4_MESSAGE_SIZE; struct net_pkt *pkt; struct dhcp_msg *msg; #if defined(CONFIG_NET_HOSTNAME_ENABLE) const char *hostname = net_hostname_get(); const size_t hostname_size = strlen(hostname); #endif if (src_addr == NULL) { addr = net_ipv4_unspecified_address(); } else { addr = src_addr; } if (server_id) { size += DHCPV4_OLV_MSG_SERVER_ID; } if (requested_ip) { size += DHCPV4_OLV_MSG_REQ_IPADDR; } if (type == NET_DHCPV4_MSG_TYPE_DISCOVER) { size += DHCPV4_OLV_MSG_REQ_LIST; } #if defined(CONFIG_NET_HOSTNAME_ENABLE) if (hostname_size > 0) { size += DHCPV4_OLV_MSG_HOST_NAME + hostname_size; } #endif pkt = net_pkt_alloc_with_buffer(iface, size, AF_INET, IPPROTO_UDP, K_FOREVER); if (!pkt) { return NULL; } net_pkt_set_ipv4_ttl(pkt, 0xFF); if (net_ipv4_create(pkt, addr, server_addr) || net_udp_create(pkt, htons(DHCPV4_CLIENT_PORT), htons(DHCPV4_SERVER_PORT))) { goto fail; } msg = (struct dhcp_msg *)net_pkt_get_data(pkt, &dhcp_access); (void)memset(msg, 0, sizeof(struct dhcp_msg)); msg->op = DHCPV4_MSG_BOOT_REQUEST; msg->htype = HARDWARE_ETHERNET_TYPE; msg->hlen = net_if_get_link_addr(iface)->len; msg->xid = htonl(iface->config.dhcpv4.xid); msg->flags = IS_ENABLED(CONFIG_NET_DHCPV4_ACCEPT_UNICAST) ? htons(DHCPV4_MSG_UNICAST) : htons(DHCPV4_MSG_BROADCAST); if (ciaddr) { /* The ciaddr field was zero'd out above, if we are * asked to send a ciaddr then fill it in now * otherwise leave it as all zeros. */ memcpy(msg->ciaddr, ciaddr, 4); } memcpy(msg->chaddr, net_if_get_link_addr(iface)->addr, net_if_get_link_addr(iface)->len); if (net_pkt_set_data(pkt, &dhcp_access)) { goto fail; } if (!dhcpv4_add_sname(pkt) || !dhcpv4_add_file(pkt) || !dhcpv4_add_cookie(pkt) || !dhcpv4_add_msg_type(pkt, type)) { goto fail; } if ((server_id && !dhcpv4_add_server_id(pkt, &iface->config.dhcpv4.server_id)) || (requested_ip && !dhcpv4_add_req_ipaddr(pkt, &iface->config.dhcpv4.requested_ip))) { goto fail; } if (type == NET_DHCPV4_MSG_TYPE_DISCOVER && !dhcpv4_add_req_options(pkt)) { goto fail; } #if defined(CONFIG_NET_HOSTNAME_ENABLE) if (hostname_size > 0 && !dhcpv4_add_hostname(pkt, hostname, hostname_size)) { goto fail; } #endif if (!dhcpv4_add_end(pkt)) { goto fail; } net_pkt_cursor_init(pkt); net_ipv4_finalize(pkt, IPPROTO_UDP); return pkt; fail: NET_DBG("Message creation failed"); net_pkt_unref(pkt); return NULL; } /* Must be invoked with lock held. */ static void dhcpv4_immediate_timeout(struct net_if_dhcpv4 *dhcpv4) { NET_DBG("force timeout dhcpv4=%p", dhcpv4); dhcpv4->timer_start = k_uptime_get() - 1; dhcpv4->request_time = 0U; k_work_reschedule(&timeout_work, K_NO_WAIT); } /* Must be invoked with lock held. */ static void dhcpv4_set_timeout(struct net_if_dhcpv4 *dhcpv4, uint32_t timeout) { NET_DBG("sched timeout dhcvp4=%p timeout=%us", dhcpv4, timeout); dhcpv4->timer_start = k_uptime_get(); dhcpv4->request_time = timeout; /* NB: This interface may not be providing the next timeout * event; also this timeout may replace the current timeout * event. Delegate scheduling to the timeout manager. */ k_work_reschedule(&timeout_work, K_NO_WAIT); } /* Must be invoked with lock held */ static uint32_t dhcpv4_update_message_timeout(struct net_if_dhcpv4 *dhcpv4) { uint32_t timeout; timeout = DHCPV4_INITIAL_RETRY_TIMEOUT << dhcpv4->attempts; /* Max 64 seconds, see RFC 2131 chapter 4.1 */ if (timeout < DHCPV4_INITIAL_RETRY_TIMEOUT || timeout > 64) { timeout = 64; } /* +1/-1 second randomization */ timeout += (sys_rand32_get() % 3U) - 1; dhcpv4->attempts++; dhcpv4_set_timeout(dhcpv4, timeout); return timeout; } /* Prepare DHCPv4 Message request and send it to peer. * * Returns the number of seconds until the next time-triggered event, * or UINT32_MAX if the client is in an invalid state. */ static uint32_t dhcpv4_send_request(struct net_if *iface) { const struct in_addr *server_addr = net_ipv4_broadcast_address(); const struct in_addr *ciaddr = NULL; const struct in_addr *src_addr = NULL; bool with_server_id = false; bool with_requested_ip = false; struct net_pkt *pkt = NULL; uint32_t timeout = UINT32_MAX; iface->config.dhcpv4.xid++; switch (iface->config.dhcpv4.state) { case NET_DHCPV4_DISABLED: case NET_DHCPV4_INIT: case NET_DHCPV4_SELECTING: case NET_DHCPV4_BOUND: /* Not possible */ NET_ASSERT(0, "Invalid state %s", net_dhcpv4_state_name(iface->config.dhcpv4.state)); goto fail; break; case NET_DHCPV4_REQUESTING: with_server_id = true; with_requested_ip = true; memcpy(&iface->config.dhcpv4.request_server_addr, &iface->config.dhcpv4.server_id, sizeof(struct in_addr)); break; case NET_DHCPV4_RENEWING: /* Since we have an address populate the ciaddr field. */ ciaddr = &iface->config.dhcpv4.requested_ip; /* UNICAST the DHCPREQUEST */ src_addr = ciaddr; server_addr = &iface->config.dhcpv4.server_id; /* RFC2131 4.4.5 Client MUST NOT include server * identifier in the DHCPREQUEST. */ break; case NET_DHCPV4_REBINDING: /* Since we have an address populate the ciaddr field. */ ciaddr = &iface->config.dhcpv4.requested_ip; src_addr = ciaddr; break; } timeout = dhcpv4_update_message_timeout(&iface->config.dhcpv4); pkt = dhcpv4_create_message(iface, NET_DHCPV4_MSG_TYPE_REQUEST, ciaddr, src_addr, server_addr, with_server_id, with_requested_ip); if (!pkt) { goto fail; } if (net_send_data(pkt) < 0) { goto fail; } net_stats_update_udp_sent(iface); NET_DBG("send request dst=%s xid=0x%x ciaddr=%s%s%s timeout=%us", net_sprint_ipv4_addr(server_addr), iface->config.dhcpv4.xid, ciaddr ? net_sprint_ipv4_addr(ciaddr) : "", with_server_id ? " +server-id" : "", with_requested_ip ? " +requested-ip" : "", timeout); return timeout; fail: if (pkt) { net_pkt_unref(pkt); } return timeout; } /* Prepare DHCPv4 Discover message and broadcast it */ static uint32_t dhcpv4_send_discover(struct net_if *iface) { struct net_pkt *pkt; uint32_t timeout; iface->config.dhcpv4.xid++; pkt = dhcpv4_create_message(iface, NET_DHCPV4_MSG_TYPE_DISCOVER, NULL, NULL, net_ipv4_broadcast_address(), false, false); if (!pkt) { goto fail; } if (net_send_data(pkt) < 0) { goto fail; } net_stats_update_udp_sent(iface); timeout = dhcpv4_update_message_timeout(&iface->config.dhcpv4); NET_DBG("send discover xid=0x%x timeout=%us", iface->config.dhcpv4.xid, timeout); return timeout; fail: if (pkt) { net_pkt_unref(pkt); } return iface->config.dhcpv4.xid % (CONFIG_NET_DHCPV4_INITIAL_DELAY_MAX - DHCPV4_INITIAL_DELAY_MIN) + DHCPV4_INITIAL_DELAY_MIN; } static void dhcpv4_enter_selecting(struct net_if *iface) { iface->config.dhcpv4.attempts = 0U; iface->config.dhcpv4.lease_time = 0U; iface->config.dhcpv4.renewal_time = 0U; iface->config.dhcpv4.rebinding_time = 0U; iface->config.dhcpv4.server_id.s_addr = INADDR_ANY; iface->config.dhcpv4.requested_ip.s_addr = INADDR_ANY; iface->config.dhcpv4.state = NET_DHCPV4_SELECTING; NET_DBG("enter state=%s", net_dhcpv4_state_name(iface->config.dhcpv4.state)); } static uint32_t dhcpv4_get_timeleft(int64_t start, uint32_t time, int64_t now) { int64_t deadline = start + MSEC_PER_SEC * time; uint32_t ret = 0U; /* If we haven't reached the deadline, calculate the * rounded-up whole seconds until the deadline. */ if (deadline > now) { ret = (uint32_t)DIV_ROUND_UP(deadline - now, MSEC_PER_SEC); } return ret; } static uint32_t dhcpv4_request_timeleft(struct net_if *iface, int64_t now) { uint32_t request_time = iface->config.dhcpv4.request_time; return dhcpv4_get_timeleft(iface->config.dhcpv4.timer_start, request_time, now); } static uint32_t dhcpv4_renewal_timeleft(struct net_if *iface, int64_t now) { uint32_t rem = dhcpv4_get_timeleft(iface->config.dhcpv4.timer_start, iface->config.dhcpv4.renewal_time, now); if (rem == 0U) { iface->config.dhcpv4.state = NET_DHCPV4_RENEWING; NET_DBG("enter state=%s", net_dhcpv4_state_name(iface->config.dhcpv4.state)); iface->config.dhcpv4.attempts = 0U; } return rem; } static uint32_t dhcpv4_rebinding_timeleft(struct net_if *iface, int64_t now) { uint32_t rem = dhcpv4_get_timeleft(iface->config.dhcpv4.timer_start, iface->config.dhcpv4.rebinding_time, now); if (rem == 0U) { iface->config.dhcpv4.state = NET_DHCPV4_REBINDING; NET_DBG("enter state=%s", net_dhcpv4_state_name(iface->config.dhcpv4.state)); iface->config.dhcpv4.attempts = 0U; } return rem; } static void dhcpv4_enter_requesting(struct net_if *iface, struct dhcp_msg *msg) { iface->config.dhcpv4.attempts = 0U; iface->config.dhcpv4.state = NET_DHCPV4_REQUESTING; NET_DBG("enter state=%s", net_dhcpv4_state_name(iface->config.dhcpv4.state)); memcpy(iface->config.dhcpv4.requested_ip.s4_addr, msg->yiaddr, sizeof(msg->yiaddr)); dhcpv4_send_request(iface); } /* Must be invoked with lock held */ static void dhcpv4_enter_bound(struct net_if *iface) { uint32_t renewal_time; uint32_t rebinding_time; renewal_time = iface->config.dhcpv4.renewal_time; if (!renewal_time) { /* The default renewal time rfc2131 4.4.5 */ renewal_time = iface->config.dhcpv4.lease_time / 2U; iface->config.dhcpv4.renewal_time = renewal_time; } rebinding_time = iface->config.dhcpv4.rebinding_time; if (!rebinding_time) { /* The default rebinding time rfc2131 4.4.5 */ rebinding_time = iface->config.dhcpv4.lease_time * 875U / 1000; iface->config.dhcpv4.rebinding_time = rebinding_time; } iface->config.dhcpv4.state = NET_DHCPV4_BOUND; NET_DBG("enter state=%s renewal=%us rebinding=%us", net_dhcpv4_state_name(iface->config.dhcpv4.state), renewal_time, rebinding_time); dhcpv4_set_timeout(&iface->config.dhcpv4, MIN(renewal_time, rebinding_time)); net_mgmt_event_notify_with_info(NET_EVENT_IPV4_DHCP_BOUND, iface, &iface->config.dhcpv4, sizeof(iface->config.dhcpv4)); } static uint32_t dhcpv4_manage_timers(struct net_if *iface, int64_t now) { uint32_t timeleft = dhcpv4_request_timeleft(iface, now); NET_DBG("iface %p dhcpv4=%p state=%s timeleft=%u", iface, &iface->config.dhcpv4, net_dhcpv4_state_name(iface->config.dhcpv4.state), timeleft); if (timeleft != 0U) { return timeleft; } if (!net_if_is_up(iface)) { /* An interface is down, the registered event handler will * restart DHCP procedure when the interface is back up. */ return UINT32_MAX; } switch (iface->config.dhcpv4.state) { case NET_DHCPV4_DISABLED: break; case NET_DHCPV4_INIT: dhcpv4_enter_selecting(iface); __fallthrough; case NET_DHCPV4_SELECTING: /* Failed to get OFFER message, send DISCOVER again */ return dhcpv4_send_discover(iface); case NET_DHCPV4_REQUESTING: /* Maximum number of renewal attempts failed, so start * from the beginning. */ if (iface->config.dhcpv4.attempts >= DHCPV4_MAX_NUMBER_OF_ATTEMPTS) { NET_DBG("too many attempts, restart"); dhcpv4_enter_selecting(iface); return dhcpv4_send_discover(iface); } return dhcpv4_send_request(iface); case NET_DHCPV4_BOUND: timeleft = dhcpv4_renewal_timeleft(iface, now); if (timeleft != 0U) { timeleft = MIN(timeleft, dhcpv4_rebinding_timeleft(iface, now)); } if (timeleft == 0U) { return dhcpv4_send_request(iface); } return timeleft; case NET_DHCPV4_RENEWING: case NET_DHCPV4_REBINDING: if (iface->config.dhcpv4.attempts >= DHCPV4_MAX_NUMBER_OF_ATTEMPTS) { NET_DBG("too many attempts, restart"); if (!net_if_ipv4_addr_rm(iface, &iface->config.dhcpv4.requested_ip)) { NET_DBG("Failed to remove addr from iface"); } /* Maximum number of renewal attempts failed, so start * from the beginning. */ dhcpv4_enter_selecting(iface); return dhcpv4_send_discover(iface); } else { return dhcpv4_send_request(iface); } } return UINT32_MAX; } static void dhcpv4_timeout(struct k_work *work) { uint32_t timeout_update = UINT32_MAX; int64_t now = k_uptime_get(); struct net_if_dhcpv4 *current, *next; ARG_UNUSED(work); k_mutex_lock(&lock, K_FOREVER); SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&dhcpv4_ifaces, current, next, node) { struct net_if *iface = CONTAINER_OF( CONTAINER_OF(current, struct net_if_config, dhcpv4), struct net_if, config); uint32_t next_timeout; next_timeout = dhcpv4_manage_timers(iface, now); if (next_timeout < timeout_update) { timeout_update = next_timeout; } } k_mutex_unlock(&lock); if (timeout_update != UINT32_MAX) { NET_DBG("Waiting for %us", timeout_update); k_work_reschedule(&timeout_work, K_SECONDS(timeout_update)); } } /* Parse DHCPv4 options and retrieve relevant information * as per RFC 2132. */ static bool dhcpv4_parse_options(struct net_pkt *pkt, struct net_if *iface, enum net_dhcpv4_msg_type *msg_type) { #if defined(CONFIG_NET_DHCPV4_OPTION_CALLBACKS) struct net_dhcpv4_option_callback *cb, *tmp; struct net_pkt_cursor backup; #endif uint8_t cookie[4]; uint8_t length; uint8_t type; bool router_present = false; bool unhandled = true; if (net_pkt_read(pkt, cookie, sizeof(cookie)) || memcmp(magic_cookie, cookie, sizeof(magic_cookie))) { NET_DBG("Incorrect magic cookie"); return false; } while (!net_pkt_read_u8(pkt, &type)) { if (type == DHCPV4_OPTIONS_END) { NET_DBG("options_end"); goto end; } if (net_pkt_read_u8(pkt, &length)) { NET_ERR("option parsing, bad length"); return false; } #if defined(CONFIG_NET_DHCPV4_OPTION_CALLBACKS) net_pkt_cursor_backup(pkt, &backup); unhandled = true; SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&option_callbacks, cb, tmp, node) { if (cb->option == type) { NET_ASSERT(cb->handler, "No callback handler!"); if (net_pkt_read(pkt, cb->data, MIN(cb->max_length, length))) { NET_DBG("option callback, read err"); return false; } cb->handler(cb, length, *msg_type, iface); unhandled = false; } net_pkt_cursor_restore(pkt, &backup); } #endif /* CONFIG_NET_DHCPV4_OPTION_CALLBACKS */ switch (type) { case DHCPV4_OPTIONS_SUBNET_MASK: { struct in_addr netmask; if (length != 4U) { NET_ERR("options_subnet_mask, bad length"); return false; } if (net_pkt_read(pkt, netmask.s4_addr, length)) { NET_ERR("options_subnet_mask, short packet"); return false; } net_if_ipv4_set_netmask(iface, &netmask); NET_DBG("options_subnet_mask %s", net_sprint_ipv4_addr(&netmask)); break; } case DHCPV4_OPTIONS_ROUTER: { struct in_addr router; /* Router option may present 1 or more * addresses for routers on the clients * subnet. Routers should be listed in order * of preference. Hence we choose the first * and skip the rest. */ if (length % 4 != 0U || length < 4) { NET_ERR("options_router, bad length"); return false; } if (net_pkt_read(pkt, router.s4_addr, 4) || net_pkt_skip(pkt, length - 4U)) { NET_ERR("options_router, short packet"); return false; } NET_DBG("options_router: %s", net_sprint_ipv4_addr(&router)); net_if_ipv4_set_gw(iface, &router); router_present = true; break; } #if defined(CONFIG_DNS_RESOLVER) case DHCPV4_OPTIONS_DNS_SERVER: { struct dns_resolve_context *ctx; struct sockaddr_in dns; const struct sockaddr *dns_servers[] = { (struct sockaddr *)&dns, NULL }; int status; /* DNS server option may present 1 or more * addresses. Each 4 bytes in length. DNS * servers should be listed in order * of preference. Hence we choose the first * and skip the rest. */ if (length % 4 != 0U) { NET_ERR("options_dns, bad length"); return false; } (void)memset(&dns, 0, sizeof(dns)); if (net_pkt_read(pkt, dns.sin_addr.s4_addr, 4) || net_pkt_skip(pkt, length - 4U)) { NET_ERR("options_dns, short packet"); return false; } ctx = dns_resolve_get_default(); dns.sin_family = AF_INET; status = dns_resolve_reconfigure(ctx, NULL, dns_servers); if (status < 0) { NET_DBG("options_dns, failed to set " "resolve address: %d", status); return false; } break; } #endif case DHCPV4_OPTIONS_LEASE_TIME: if (length != 4U) { NET_ERR("options_lease_time, bad length"); return false; } if (net_pkt_read_be32( pkt, &iface->config.dhcpv4.lease_time) || !iface->config.dhcpv4.lease_time) { NET_ERR("options_lease_time, wrong value"); return false; } NET_DBG("options_lease_time: %u", iface->config.dhcpv4.lease_time); break; case DHCPV4_OPTIONS_RENEWAL: if (length != 4U) { NET_DBG("options_renewal, bad length"); return false; } if (net_pkt_read_be32( pkt, &iface->config.dhcpv4.renewal_time) || !iface->config.dhcpv4.renewal_time) { NET_DBG("options_renewal, wrong value"); return false; } NET_DBG("options_renewal: %u", iface->config.dhcpv4.renewal_time); break; case DHCPV4_OPTIONS_REBINDING: if (length != 4U) { NET_DBG("options_rebinding, bad length"); return false; } if (net_pkt_read_be32( pkt, &iface->config.dhcpv4.rebinding_time) || !iface->config.dhcpv4.rebinding_time) { NET_DBG("options_rebinding, wrong value"); return false; } NET_DBG("options_rebinding: %u", iface->config.dhcpv4.rebinding_time); break; case DHCPV4_OPTIONS_SERVER_ID: if (length != 4U) { NET_DBG("options_server_id, bad length"); return false; } if (net_pkt_read( pkt, iface->config.dhcpv4.server_id.s4_addr, length)) { NET_DBG("options_server_id, read err"); return false; } NET_DBG("options_server_id: %s", net_sprint_ipv4_addr(&iface->config.dhcpv4.server_id)); break; case DHCPV4_OPTIONS_MSG_TYPE: { if (length != 1U) { NET_DBG("options_msg_type, bad length"); return false; } { uint8_t val = 0U; if (net_pkt_read_u8(pkt, &val)) { NET_DBG("options_msg_type, read err"); return false; } *msg_type = val; } break; } default: if (unhandled) { NET_DBG("option unknown: %d", type); } else { NET_DBG("option unknown, handled by callback: %d", type); } if (net_pkt_skip(pkt, length)) { NET_DBG("option unknown, skip err"); return false; } break; } } /* Invalid case: Options without DHCPV4_OPTIONS_END. */ return false; end: if (*msg_type == NET_DHCPV4_MSG_TYPE_OFFER && !router_present) { struct in_addr any = INADDR_ANY_INIT; net_if_ipv4_set_gw(iface, &any); } return true; } static inline void dhcpv4_handle_msg_offer(struct net_if *iface, struct dhcp_msg *msg) { switch (iface->config.dhcpv4.state) { case NET_DHCPV4_DISABLED: case NET_DHCPV4_INIT: case NET_DHCPV4_REQUESTING: case NET_DHCPV4_RENEWING: case NET_DHCPV4_REBINDING: case NET_DHCPV4_BOUND: break; case NET_DHCPV4_SELECTING: dhcpv4_enter_requesting(iface, msg); break; } } /* Must be invoked with lock held */ static void dhcpv4_handle_msg_ack(struct net_if *iface) { switch (iface->config.dhcpv4.state) { case NET_DHCPV4_DISABLED: case NET_DHCPV4_INIT: case NET_DHCPV4_SELECTING: case NET_DHCPV4_BOUND: break; case NET_DHCPV4_REQUESTING: NET_INFO("Received: %s", net_sprint_ipv4_addr(&iface->config.dhcpv4.requested_ip)); if (!net_if_ipv4_addr_add(iface, &iface->config.dhcpv4.requested_ip, NET_ADDR_DHCP, iface->config.dhcpv4.lease_time)) { NET_DBG("Failed to add IPv4 addr to iface %p", iface); return; } dhcpv4_enter_bound(iface); break; case NET_DHCPV4_RENEWING: case NET_DHCPV4_REBINDING: /* TODO: If the renewal is success, update only * vlifetime on iface. */ dhcpv4_enter_bound(iface); break; } } static void dhcpv4_handle_msg_nak(struct net_if *iface) { switch (iface->config.dhcpv4.state) { case NET_DHCPV4_DISABLED: case NET_DHCPV4_INIT: case NET_DHCPV4_SELECTING: case NET_DHCPV4_REQUESTING: if (memcmp(&iface->config.dhcpv4.request_server_addr, &iface->config.dhcpv4.response_src_addr, sizeof(iface->config.dhcpv4.request_server_addr)) == 0) { LOG_DBG("NAK from requesting server %s, restart config", net_sprint_ipv4_addr(&iface->config.dhcpv4.request_server_addr)); dhcpv4_enter_selecting(iface); } else { LOG_DBG("NAK from non-requesting server %s, ignore it", net_sprint_ipv4_addr(&iface->config.dhcpv4.response_src_addr)); } break; case NET_DHCPV4_BOUND: break; case NET_DHCPV4_RENEWING: case NET_DHCPV4_REBINDING: if (!net_if_ipv4_addr_rm(iface, &iface->config.dhcpv4.requested_ip)) { NET_DBG("Failed to remove addr from iface"); } /* Restart the configuration process. */ dhcpv4_enter_selecting(iface); break; } } /* Takes and releases lock */ static void dhcpv4_handle_reply(struct net_if *iface, enum net_dhcpv4_msg_type msg_type, struct dhcp_msg *msg) { NET_DBG("state=%s msg=%s", net_dhcpv4_state_name(iface->config.dhcpv4.state), net_dhcpv4_msg_type_name(msg_type)); switch (msg_type) { case NET_DHCPV4_MSG_TYPE_OFFER: dhcpv4_handle_msg_offer(iface, msg); break; case NET_DHCPV4_MSG_TYPE_ACK: dhcpv4_handle_msg_ack(iface); break; case NET_DHCPV4_MSG_TYPE_NAK: dhcpv4_handle_msg_nak(iface); break; default: NET_DBG("ignore message"); break; } } static enum net_verdict net_dhcpv4_input(struct net_conn *conn, struct net_pkt *pkt, union net_ip_header *ip_hdr, union net_proto_header *proto_hdr, void *user_data) { NET_PKT_DATA_ACCESS_DEFINE(dhcp_access, struct dhcp_msg); enum net_verdict verdict = NET_DROP; enum net_dhcpv4_msg_type msg_type = 0; struct dhcp_msg *msg; struct net_if *iface; if (!conn) { NET_DBG("Invalid connection"); return NET_DROP; } if (!pkt) { NET_DBG("Invalid packet"); return NET_DROP; } iface = net_pkt_iface(pkt); if (!iface) { NET_DBG("no iface"); return NET_DROP; } /* If the message is not DHCP then drop the packet. */ if (net_pkt_get_len(pkt) < NET_IPV4UDPH_LEN + sizeof(struct dhcp_msg)) { NET_DBG("Input msg is not related to DHCPv4"); return NET_DROP; } net_pkt_cursor_init(pkt); if (net_pkt_skip(pkt, NET_IPV4UDPH_LEN)) { return NET_DROP; } msg = (struct dhcp_msg *)net_pkt_get_data(pkt, &dhcp_access); if (!msg) { return NET_DROP; } NET_DBG("Received dhcp msg [op=0x%x htype=0x%x hlen=%u xid=0x%x " "secs=%u flags=0x%x chaddr=%s", msg->op, msg->htype, msg->hlen, ntohl(msg->xid), msg->secs, msg->flags, net_sprint_ll_addr(msg->chaddr, 6)); NET_DBG(" ciaddr=%d.%d.%d.%d", msg->ciaddr[0], msg->ciaddr[1], msg->ciaddr[2], msg->ciaddr[3]); NET_DBG(" yiaddr=%d.%d.%d.%d", msg->yiaddr[0], msg->yiaddr[1], msg->yiaddr[2], msg->yiaddr[3]); NET_DBG(" siaddr=%d.%d.%d.%d", msg->siaddr[0], msg->siaddr[1], msg->siaddr[2], msg->siaddr[3]); NET_DBG(" giaddr=%d.%d.%d.%d]", msg->giaddr[0], msg->giaddr[1], msg->giaddr[2], msg->giaddr[3]); k_mutex_lock(&lock, K_FOREVER); if (!(msg->op == DHCPV4_MSG_BOOT_REPLY && iface->config.dhcpv4.xid == ntohl(msg->xid) && !memcmp(msg->chaddr, net_if_get_link_addr(iface)->addr, net_if_get_link_addr(iface)->len))) { NET_DBG("Unexpected op (%d), xid (%x vs %x) or chaddr", msg->op, iface->config.dhcpv4.xid, ntohl(msg->xid)); goto drop; } if (msg->hlen != net_if_get_link_addr(iface)->len) { NET_DBG("Unexpected hlen (%d)", msg->hlen); goto drop; } net_pkt_acknowledge_data(pkt, &dhcp_access); /* SNAME, FILE are not used at the moment, skip it */ if (net_pkt_skip(pkt, SIZE_OF_SNAME + SIZE_OF_FILE)) { NET_DBG("short packet while skipping sname"); goto drop; } if (!dhcpv4_parse_options(pkt, iface, &msg_type)) { goto drop; } memcpy(&iface->config.dhcpv4.response_src_addr, ip_hdr->ipv4->src, sizeof(struct in_addr)); dhcpv4_handle_reply(iface, msg_type, msg); net_pkt_unref(pkt); verdict = NET_OK; drop: k_mutex_unlock(&lock); return verdict; } static void dhcpv4_iface_event_handler(struct net_mgmt_event_callback *cb, uint32_t mgmt_event, struct net_if *iface) { sys_snode_t *node = NULL; k_mutex_lock(&lock, K_FOREVER); SYS_SLIST_FOR_EACH_NODE(&dhcpv4_ifaces, node) { if (node == &iface->config.dhcpv4.node) { break; } } if (node == NULL) { goto out; } if (mgmt_event == NET_EVENT_IF_DOWN) { NET_DBG("Interface %p going down", iface); if (iface->config.dhcpv4.state == NET_DHCPV4_BOUND) { iface->config.dhcpv4.attempts = 0U; iface->config.dhcpv4.state = NET_DHCPV4_RENEWING; NET_DBG("enter state=%s", net_dhcpv4_state_name( iface->config.dhcpv4.state)); } } else if (mgmt_event == NET_EVENT_IF_UP) { NET_DBG("Interface %p coming up", iface); /* We should not call dhcpv4_send_request() directly here as * the CONFIG_NET_MGMT_EVENT_STACK_SIZE is not large * enough. Instead we can force a request timeout * which will then call dhcpv4_send_request() automatically. */ dhcpv4_immediate_timeout(&iface->config.dhcpv4); } out: k_mutex_unlock(&lock); } const char *net_dhcpv4_state_name(enum net_dhcpv4_state state) { static const char * const name[] = { "disabled", "init", "selecting", "requesting", "renewing", "rebinding", "bound", }; __ASSERT_NO_MSG(state >= 0 && state < sizeof(name)); return name[state]; } const char *net_dhcpv4_msg_type_name(enum net_dhcpv4_msg_type msg_type) { static const char * const name[] = { "discover", "offer", "request", "decline", "ack", "nak", "release", "inform" }; __ASSERT_NO_MSG(msg_type >= 1 && msg_type <= sizeof(name)); return name[msg_type - 1]; } static void dhcpv4_start_internal(struct net_if *iface, bool first_start) { uint32_t entropy; uint32_t timeout = 0; net_mgmt_event_notify(NET_EVENT_IPV4_DHCP_START, iface); k_mutex_lock(&lock, K_FOREVER); switch (iface->config.dhcpv4.state) { case NET_DHCPV4_DISABLED: iface->config.dhcpv4.state = NET_DHCPV4_INIT; NET_DBG("iface %p state=%s", iface, net_dhcpv4_state_name(iface->config.dhcpv4.state)); /* We need entropy for both an XID and a random delay * before sending the initial discover message. */ entropy = sys_rand32_get(); /* A DHCP client MUST choose xid's in such a way as to * minimize the change of using and xid identical to * one used by another client. Choose a random xid st * startup and increment it on each new request. */ iface->config.dhcpv4.xid = entropy; /* Use default */ if (first_start) { /* RFC2131 4.1.1 requires we wait a random period * between 1 and 10 seconds before sending the initial * discover. */ timeout = entropy % (CONFIG_NET_DHCPV4_INITIAL_DELAY_MAX - DHCPV4_INITIAL_DELAY_MIN) + DHCPV4_INITIAL_DELAY_MIN; } NET_DBG("wait timeout=%us", timeout); if (sys_slist_is_empty(&dhcpv4_ifaces)) { net_mgmt_add_event_callback(&mgmt4_cb); } sys_slist_append(&dhcpv4_ifaces, &iface->config.dhcpv4.node); dhcpv4_set_timeout(&iface->config.dhcpv4, timeout); break; case NET_DHCPV4_INIT: case NET_DHCPV4_SELECTING: case NET_DHCPV4_REQUESTING: case NET_DHCPV4_RENEWING: case NET_DHCPV4_REBINDING: case NET_DHCPV4_BOUND: break; } k_mutex_unlock(&lock); } #if defined(CONFIG_NET_DHCPV4_OPTION_CALLBACKS) int net_dhcpv4_add_option_callback(struct net_dhcpv4_option_callback *cb) { if (!cb || !cb->handler) { return -EINVAL; } k_mutex_lock(&lock, K_FOREVER); sys_slist_prepend(&option_callbacks, &cb->node); k_mutex_unlock(&lock); return 0; } int net_dhcpv4_remove_option_callback(struct net_dhcpv4_option_callback *cb) { int ret = 0; if (!cb || !cb->handler) { return -EINVAL; } k_mutex_lock(&lock, K_FOREVER); if (!sys_slist_find_and_remove(&option_callbacks, &cb->node)) { ret = -EINVAL; } k_mutex_unlock(&lock); return ret; } #endif /* CONFIG_NET_DHCPV4_OPTION_CALLBACKS */ void net_dhcpv4_start(struct net_if *iface) { return dhcpv4_start_internal(iface, true); } void net_dhcpv4_stop(struct net_if *iface) { k_mutex_lock(&lock, K_FOREVER); switch (iface->config.dhcpv4.state) { case NET_DHCPV4_DISABLED: break; case NET_DHCPV4_RENEWING: case NET_DHCPV4_BOUND: if (!net_if_ipv4_addr_rm(iface, &iface->config.dhcpv4.requested_ip)) { NET_DBG("Failed to remove addr from iface"); } __fallthrough; case NET_DHCPV4_INIT: case NET_DHCPV4_SELECTING: case NET_DHCPV4_REQUESTING: case NET_DHCPV4_REBINDING: iface->config.dhcpv4.state = NET_DHCPV4_DISABLED; NET_DBG("state=%s", net_dhcpv4_state_name(iface->config.dhcpv4.state)); sys_slist_find_and_remove(&dhcpv4_ifaces, &iface->config.dhcpv4.node); if (sys_slist_is_empty(&dhcpv4_ifaces)) { /* Best effort cancel. Handler is safe to invoke if * cancellation is unsuccessful. */ (void)k_work_cancel_delayable(&timeout_work); net_mgmt_del_event_callback(&mgmt4_cb); } break; } net_mgmt_event_notify(NET_EVENT_IPV4_DHCP_STOP, iface); k_mutex_unlock(&lock); } void net_dhcpv4_restart(struct net_if *iface) { net_dhcpv4_stop(iface); dhcpv4_start_internal(iface, false); } int net_dhcpv4_init(void) { struct sockaddr local_addr; int ret; NET_DBG(""); net_ipaddr_copy(&net_sin(&local_addr)->sin_addr, net_ipv4_unspecified_address()); local_addr.sa_family = AF_INET; /* Register UDP input callback on * DHCPV4_SERVER_PORT(67) and DHCPV4_CLIENT_PORT(68) for * all dhcpv4 related incoming packets. */ ret = net_udp_register(AF_INET, NULL, &local_addr, DHCPV4_SERVER_PORT, DHCPV4_CLIENT_PORT, NULL, net_dhcpv4_input, NULL, NULL); if (ret < 0) { NET_DBG("UDP callback registration failed"); return ret; } k_work_init_delayable(&timeout_work, dhcpv4_timeout); /* Catch network interface UP or DOWN events and renew the address * if interface is coming back up again. */ net_mgmt_init_event_callback(&mgmt4_cb, dhcpv4_iface_event_handler, NET_EVENT_IF_DOWN | NET_EVENT_IF_UP); #if defined(CONFIG_NET_DHCPV4_OPTION_CALLBACKS) k_mutex_lock(&lock, K_FOREVER); sys_slist_init(&option_callbacks); k_mutex_unlock(&lock); #endif return 0; } #if defined(CONFIG_NET_DHCPV4_ACCEPT_UNICAST) bool net_dhcpv4_accept_unicast(struct net_pkt *pkt) { NET_PKT_DATA_ACCESS_DEFINE(udp_access, struct net_udp_hdr); struct net_pkt_cursor backup; struct net_udp_hdr *udp_hdr; struct net_if *iface; bool accept = false; iface = net_pkt_iface(pkt); if (iface == NULL) { return false; } /* Only accept DHCPv4 packets during active query. */ if (iface->config.dhcpv4.state != NET_DHCPV4_SELECTING && iface->config.dhcpv4.state != NET_DHCPV4_REQUESTING && iface->config.dhcpv4.state != NET_DHCPV4_RENEWING && iface->config.dhcpv4.state != NET_DHCPV4_REBINDING) { return false; } net_pkt_cursor_backup(pkt, &backup); net_pkt_skip(pkt, net_pkt_ip_hdr_len(pkt)); /* Verify destination UDP port. */ udp_hdr = (struct net_udp_hdr *)net_pkt_get_data(pkt, &udp_access); if (udp_hdr == NULL) { goto out; } if (udp_hdr->dst_port != htons(DHCPV4_CLIENT_PORT)) { goto out; } accept = true; out: net_pkt_cursor_restore(pkt, &backup); return accept; } #endif /* CONFIG_NET_DHCPV4_ACCEPT_UNICAST */