| /** @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 <zephyr/logging/log.h> |
| LOG_MODULE_REGISTER(net_dhcpv4, CONFIG_NET_DHCPV4_LOG_LEVEL); |
| |
| #include <errno.h> |
| #include <inttypes.h> |
| #include <zephyr/random/rand32.h> |
| #include <zephyr/net/net_core.h> |
| #include <zephyr/net/net_pkt.h> |
| #include <zephyr/net/net_if.h> |
| #include <zephyr/net/net_mgmt.h> |
| #include "net_private.h" |
| |
| #include <zephyr/net/udp.h> |
| #include "udp_internal.h" |
| #include <zephyr/net/dhcpv4.h> |
| #include <zephyr/net/dns_resolve.h> |
| |
| #include "dhcpv4.h" |
| #include "ipv4.h" |
| |
| #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; |
| |
| /* RFC 1497 [17] */ |
| static const uint8_t magic_cookie[4] = { 0x63, 0x82, 0x53, 0x63 }; |
| |
| static const char *dhcpv4_msg_type_name(enum dhcpv4_msg_type msg_type) |
| __attribute__((unused)); |
| |
| static const char *dhcpv4_msg_type_name(enum 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]; |
| } |
| |
| /* 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 == 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); |
| |
| 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 = 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 == 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; |
| 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, 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_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) : "<unknown>", |
| 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, DHCPV4_MSG_TYPE_DISCOVER, |
| NULL, NULL, net_ipv4_broadcast_address(), |
| false, false); |
| if (!pkt) { |
| goto fail; |
| } |
| |
| if (net_send_data(pkt) < 0) { |
| goto fail; |
| } |
| |
| 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)ceiling_fraction(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) |
| { |
| 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)); |
| |
| 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; |
| } |
| |
| 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 dhcpv4_msg_type *msg_type) |
| { |
| uint8_t cookie[4]; |
| uint8_t length; |
| uint8_t type; |
| bool router_present = false; |
| |
| 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; |
| } |
| |
| 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: |
| NET_DBG("option unknown: %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 == 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) |
| { |
| 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); |
| 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_RENEWING: |
| case NET_DHCPV4_BOUND: |
| break; |
| case NET_DHCPV4_REQUESTING: |
| case NET_DHCPV4_REBINDING: |
| /* Restart the configuration process. */ |
| dhcpv4_enter_selecting(iface); |
| break; |
| } |
| } |
| |
| /* Takes and releases lock */ |
| static void dhcpv4_handle_reply(struct net_if *iface, |
| enum dhcpv4_msg_type msg_type) |
| { |
| NET_DBG("state=%s msg=%s", |
| net_dhcpv4_state_name(iface->config.dhcpv4.state), |
| dhcpv4_msg_type_name(msg_type)); |
| |
| switch (msg_type) { |
| case DHCPV4_MSG_TYPE_OFFER: |
| dhcpv4_handle_msg_offer(iface); |
| break; |
| case DHCPV4_MSG_TYPE_ACK: |
| dhcpv4_handle_msg_ack(iface); |
| break; |
| case 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 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 continue passing to |
| * related handlers. |
| */ |
| if (net_pkt_get_len(pkt) < NET_IPV4UDPH_LEN + sizeof(struct dhcp_msg)) { |
| NET_DBG("Input msg is not related to DHCPv4"); |
| return NET_CONTINUE; |
| |
| } |
| |
| 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; |
| } |
| |
| memcpy(iface->config.dhcpv4.requested_ip.s4_addr, |
| msg->yiaddr, sizeof(msg->yiaddr)); |
| |
| 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; |
| } |
| |
| net_pkt_unref(pkt); |
| |
| dhcpv4_handle_reply(iface, msg_type); |
| |
| 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]; |
| } |
| |
| void net_dhcpv4_start(struct net_if *iface) |
| { |
| uint32_t timeout; |
| uint32_t entropy; |
| |
| 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; |
| |
| |
| /* 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); |
| } |
| |
| 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); |
| } |
| |
| 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); |
| |
| return 0; |
| } |