| /** @file |
| * @brief DHCPv4 client related functions |
| */ |
| |
| /* |
| * Copyright (c) 2017 ARM Ltd. |
| * Copyright (c) 2016 Intel Corporation |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #if defined(CONFIG_NET_DEBUG_DHCPV4) |
| #define SYS_LOG_DOMAIN "net/dhcpv4" |
| #define NET_LOG_ENABLED 1 |
| #endif |
| |
| #include <errno.h> |
| #include <inttypes.h> |
| #include <net/net_core.h> |
| #include <net/nbuf.h> |
| #include <net/net_if.h> |
| #include "net_private.h" |
| |
| #include "udp.h" |
| #include <net/dhcpv4.h> |
| |
| struct dhcp_msg { |
| uint8_t op; /* Message type, 1:BOOTREQUEST, 2:BOOTREPLY */ |
| uint8_t htype; /* Hardware Address Type */ |
| uint8_t hlen; /* Hardware Address length */ |
| uint8_t hops; /* used by relay agents when booting via relay |
| * agent, client sets zero |
| */ |
| uint32_t xid; /* Transaction ID, random number */ |
| uint16_t secs; /* Seconds elapsed since client began address |
| * acquisition or renewal process |
| */ |
| uint16_t flags; /* Broadcast or Unicast */ |
| uint8_t ciaddr[4]; /* Client IP Address */ |
| uint8_t yiaddr[4]; /* your (client) IP address */ |
| uint8_t siaddr[4]; /* IP address of next server to use in bootstrap |
| * returned in DHCPOFFER, DHCPACK by server |
| */ |
| uint8_t giaddr[4]; /* Relat agent IP address */ |
| uint8_t chaddr[16]; /* Client hardware address */ |
| } __packed; |
| |
| #define SIZE_OF_SNAME 64 |
| #define SIZE_OF_FILE 128 |
| |
| #define DHCPV4_MSG_BROADCAST 0x8000 |
| #define DHCPV4_MSG_UNICAST 0x0000 |
| |
| #define DHCPV4_MSG_BOOT_REQUEST 1 |
| #define DHCPV4_MSG_BOOT_REPLY 2 |
| |
| #define HARDWARE_ETHERNET_TYPE 1 |
| #define HARDWARE_ETHERNET_LEN 6 |
| |
| #define DHCPV4_SERVER_PORT 67 |
| #define DHCPV4_CLIENT_PORT 68 |
| |
| #define DHCPV4_MSG_TYPE_DISCOVER 1 |
| #define DHCPV4_MSG_TYPE_OFFER 2 |
| #define DHCPV4_MSG_TYPE_REQUEST 3 |
| #define DHCPV4_MSG_TYPE_DECLINE 4 |
| #define DHCPV4_MSG_TYPE_ACK 5 |
| #define DHCPV4_MSG_TYPE_NAK 6 |
| #define DHCPV4_MSG_TYPE_RELEASE 7 |
| #define DHCPV4_MSG_TYPE_INFORM 8 |
| |
| #define DHCPV4_OPTIONS_SUBNET_MASK 1 |
| #define DHCPV4_OPTIONS_ROUTER 3 |
| #define DHCPV4_OPTIONS_DNS_SERVER 6 |
| #define DHCPV4_OPTIONS_REQ_IPADDR 50 |
| #define DHCPV4_OPTIONS_LEASE_TIME 51 |
| #define DHCPV4_OPTIONS_MSG_TYPE 53 |
| #define DHCPV4_OPTIONS_SERVER_ID 54 |
| #define DHCPV4_OPTIONS_REQ_LIST 55 |
| #define DHCPV4_OPTIONS_RENEWAL 58 |
| #define DHCPV4_OPTIONS_END 255 |
| |
| /* TODO: |
| * 1) Support for UNICAST flag (some dhcpv4 servers will not reply if |
| * DISCOVER message contains BROADCAST FLAG). |
| * 2) Support T2(Rebind) timer. |
| */ |
| |
| /* Maximum number of REQUEST or RENEWAL retransmits before reverting |
| * to DISCOVER. |
| */ |
| #define DHCPV4_MAX_NUMBER_OF_ATTEMPTS 3 |
| |
| /* Initial message retry timeout (s). This timeout increases |
| * exponentially on each retransmit. |
| * RFC2131 4.1 |
| */ |
| #define DHCPV4_INITIAL_RETRY_TIMEOUT 4 |
| |
| /* Initial minimum and maximum delay in INIT state before sending the |
| * initial DISCOVER message. |
| * RFC2131 4.1.1 |
| */ |
| #define DHCPV4_INITIAL_DELAY_MIN 1 |
| #define DHCPV4_INITIAL_DELAY_MAX 10 |
| |
| static uint8_t magic_cookie[4] = { 0x63, 0x82, 0x53, 0x63 }; /* RFC 1497 [17] */ |
| |
| static void dhcpv4_timeout(struct k_work *work); |
| |
| static const char * |
| net_dhcpv4_state_name(enum net_dhcpv4_state state) __attribute__((unused)); |
| |
| static const char * |
| net_dhcpv4_msg_type_name(uint8_t msg_type) __attribute__((unused)); |
| |
| static const char * |
| net_dhcpv4_state_name(enum net_dhcpv4_state state) |
| { |
| static const char * const name[] = { |
| "init", |
| "discover", |
| "offer", |
| "request", |
| "renewal", |
| "ack" |
| }; |
| |
| __ASSERT_NO_MSG(state >= 0 && state < sizeof(name)); |
| return name[state]; |
| } |
| |
| static const char * |
| net_dhcpv4_msg_type_name(uint8_t 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 inline uint32_t get_dhcpv4_renewal_time(struct net_if *iface) |
| { |
| return (iface->dhcpv4.renewal_time ? iface->dhcpv4.renewal_time : |
| iface->dhcpv4.lease_time / 2); |
| } |
| |
| static inline void unset_dhcpv4_on_iface(struct net_if *iface) |
| { |
| if (!iface) { |
| return; |
| } |
| |
| iface->dhcpv4.state = NET_DHCPV4_INIT; |
| NET_DBG("state=%s", net_dhcpv4_state_name(iface->dhcpv4.state)); |
| |
| iface->dhcpv4.attempts = 0; |
| iface->dhcpv4.lease_time = 0; |
| iface->dhcpv4.renewal_time = 0; |
| |
| memset(iface->dhcpv4.server_id.s4_addr, 0, 4); |
| memset(iface->dhcpv4.server_id.s4_addr, 0, 4); |
| memset(iface->dhcpv4.requested_ip.s4_addr, 0, 4); |
| k_delayed_work_cancel(&iface->dhcpv4_timeout); |
| k_delayed_work_cancel(&iface->dhcpv4_t1_timer); |
| } |
| |
| /* Add magic cookie to DCHPv4 messages */ |
| static inline bool add_cookie(struct net_buf *buf) |
| { |
| return net_nbuf_append(buf, sizeof(magic_cookie), magic_cookie, |
| K_FOREVER); |
| } |
| |
| /* Add DHCPv4 message type */ |
| static inline bool add_msg_type(struct net_buf *buf, uint8_t type) |
| { |
| uint8_t data[3] = { DHCPV4_OPTIONS_MSG_TYPE, 1, type }; |
| |
| return net_nbuf_append(buf, sizeof(data), data, K_FOREVER); |
| } |
| |
| /* Add DHCPv4 minimum required options for server to reply. |
| * Can be added more if needed. |
| */ |
| static inline bool add_req_options(struct net_buf *buf) |
| { |
| uint8_t data[5] = { DHCPV4_OPTIONS_REQ_LIST, |
| 3, /* Length */ |
| DHCPV4_OPTIONS_SUBNET_MASK, |
| DHCPV4_OPTIONS_ROUTER, |
| DHCPV4_OPTIONS_DNS_SERVER }; |
| |
| return net_nbuf_append(buf, sizeof(data), data, K_FOREVER); |
| } |
| |
| static inline bool add_server_id(struct net_buf *buf) |
| { |
| struct net_if *iface = net_nbuf_iface(buf); |
| uint8_t data; |
| |
| data = DHCPV4_OPTIONS_SERVER_ID; |
| if (!net_nbuf_append(buf, 1, &data, K_FOREVER)) { |
| return false; |
| } |
| |
| data = 4; |
| if (!net_nbuf_append(buf, 1, &data, K_FOREVER)) { |
| return false; |
| } |
| |
| if (!net_nbuf_append(buf, 4, iface->dhcpv4.server_id.s4_addr, |
| K_FOREVER)) { |
| return false; |
| } |
| |
| return true; |
| } |
| |
| static inline bool add_req_ipaddr(struct net_buf *buf) |
| { |
| struct net_if *iface = net_nbuf_iface(buf); |
| uint8_t data; |
| |
| data = DHCPV4_OPTIONS_REQ_IPADDR; |
| if (!net_nbuf_append(buf, 1, &data, K_FOREVER)) { |
| return false; |
| } |
| |
| data = 4; |
| if (!net_nbuf_append(buf, 1, &data, K_FOREVER)) { |
| return false; |
| } |
| |
| if (!net_nbuf_append(buf, 4, iface->dhcpv4.requested_ip.s4_addr, |
| K_FOREVER)) { |
| return false; |
| } |
| |
| return true; |
| } |
| |
| /* Add DHCPv4 Options end, rest of the message can be padded wit zeros */ |
| static inline bool add_end(struct net_buf *buf) |
| { |
| uint8_t data = DHCPV4_OPTIONS_END; |
| |
| return net_nbuf_append(buf, 1, &data, K_FOREVER); |
| } |
| |
| /* File is empty ATM */ |
| static inline bool add_file(struct net_buf *buf) |
| { |
| uint8_t len = SIZE_OF_FILE; |
| uint8_t data = 0; |
| |
| while (len-- > 0) { |
| if (!net_nbuf_append(buf, 1, &data, K_FOREVER)) { |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| /* SNAME is empty ATM */ |
| static inline bool add_sname(struct net_buf *buf) |
| { |
| uint8_t len = SIZE_OF_SNAME; |
| uint8_t data = 0; |
| |
| while (len-- > 0) { |
| if (!net_nbuf_append(buf, 1, &data, K_FOREVER)) { |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| /* Setup IPv4 + UDP header */ |
| static void setup_header(struct net_buf *buf) |
| { |
| struct net_ipv4_hdr *ipv4; |
| struct net_udp_hdr *udp; |
| uint16_t len; |
| |
| ipv4 = NET_IPV4_BUF(buf); |
| udp = NET_UDP_BUF(buf); |
| |
| len = net_buf_frags_len(buf->frags); |
| |
| /* Setup IPv4 header */ |
| memset(ipv4, 0, sizeof(struct net_ipv4_hdr)); |
| |
| ipv4->vhl = 0x45; |
| ipv4->ttl = 0xFF; |
| ipv4->proto = IPPROTO_UDP; |
| ipv4->len[0] = len >> 8; |
| ipv4->len[1] = (uint8_t)len; |
| ipv4->chksum = ~net_calc_chksum_ipv4(buf); |
| |
| net_ipaddr_copy(&ipv4->dst, net_ipv4_broadcast_address()); |
| |
| len -= NET_IPV4H_LEN; |
| /* Setup UDP header */ |
| udp->src_port = htons(DHCPV4_CLIENT_PORT); |
| udp->dst_port = htons(DHCPV4_SERVER_PORT); |
| udp->len = htons(len); |
| udp->chksum = ~net_calc_chksum_udp(buf); |
| } |
| |
| /* Prepare initial DHCPv4 message and add options as per message type */ |
| static struct net_buf *prepare_message(struct net_if *iface, uint8_t type) |
| { |
| struct net_buf *buf; |
| struct net_buf *frag; |
| struct dhcp_msg *msg; |
| |
| buf = net_nbuf_get_reserve_tx(0, K_FOREVER); |
| |
| frag = net_nbuf_get_reserve_data(net_if_get_ll_reserve(iface, NULL), |
| K_FOREVER); |
| |
| net_nbuf_set_ll_reserve(buf, net_buf_headroom(frag)); |
| net_nbuf_set_iface(buf, iface); |
| net_nbuf_set_family(buf, AF_INET); |
| net_nbuf_set_ip_hdr_len(buf, sizeof(struct net_ipv4_hdr)); |
| |
| net_buf_frag_add(buf, frag); |
| |
| /* Leave room for IPv4 + UDP headers */ |
| net_buf_add(buf->frags, NET_IPV4UDPH_LEN); |
| |
| if (net_buf_tailroom(frag) < sizeof(struct dhcp_msg)) { |
| goto fail; |
| } |
| |
| msg = (struct dhcp_msg *)(frag->data + NET_IPV4UDPH_LEN); |
| memset(msg, 0, sizeof(struct dhcp_msg)); |
| |
| msg->op = DHCPV4_MSG_BOOT_REQUEST; |
| |
| msg->htype = HARDWARE_ETHERNET_TYPE; |
| msg->hlen = HARDWARE_ETHERNET_LEN; |
| |
| msg->xid = htonl(iface->dhcpv4.xid); |
| msg->flags = htons(DHCPV4_MSG_BROADCAST); |
| |
| /* Send DHCPV4_MSG_TYPE_REQUEST, |
| * ciaddr must 0.0.0.0, or router will return NAK |
| */ |
| if ((iface->dhcpv4.state == NET_DHCPV4_INIT) || |
| (type == DHCPV4_MSG_TYPE_REQUEST)) { |
| memset(msg->ciaddr, 0, sizeof(msg->ciaddr)); |
| } else { |
| memcpy(msg->ciaddr, iface->dhcpv4.requested_ip.s4_addr, 4); |
| } |
| |
| memcpy(msg->chaddr, iface->link_addr.addr, iface->link_addr.len); |
| |
| net_buf_add(frag, sizeof(struct dhcp_msg)); |
| |
| if (!add_sname(buf) || |
| !add_file(buf) || |
| !add_cookie(buf) || |
| !add_msg_type(buf, type)) { |
| goto fail; |
| } |
| |
| return buf; |
| |
| fail: |
| net_nbuf_unref(buf); |
| return NULL; |
| } |
| |
| /* Prepare DHCPv4 Message request and send it to peer */ |
| static void send_request(struct net_if *iface, bool renewal) |
| { |
| struct net_buf *buf; |
| uint32_t timeout; |
| |
| iface->dhcpv4.xid++; |
| |
| buf = prepare_message(iface, DHCPV4_MSG_TYPE_REQUEST); |
| if (!buf) { |
| goto fail; |
| } |
| |
| if (!add_server_id(buf) || |
| !add_req_ipaddr(buf) || |
| !add_end(buf)) { |
| goto fail; |
| } |
| |
| setup_header(buf); |
| |
| if (net_send_data(buf) < 0) { |
| goto fail; |
| } |
| |
| if (renewal) { |
| iface->dhcpv4.state = NET_DHCPV4_RENEWAL; |
| } else { |
| iface->dhcpv4.state = NET_DHCPV4_REQUEST; |
| } |
| |
| timeout = DHCPV4_INITIAL_RETRY_TIMEOUT << iface->dhcpv4.attempts; |
| |
| NET_DBG("enter state=%s xid=0x%"PRIx32" timeout=%"PRIu32"s", |
| net_dhcpv4_state_name(iface->dhcpv4.state), |
| iface->dhcpv4.xid, timeout); |
| |
| k_delayed_work_init(&iface->dhcpv4_timeout, dhcpv4_timeout); |
| k_delayed_work_submit(&iface->dhcpv4_timeout, timeout * MSEC_PER_SEC); |
| |
| iface->dhcpv4.attempts++; |
| return; |
| |
| fail: |
| NET_DBG("Message preparation failed"); |
| |
| if (!buf) { |
| net_nbuf_unref(buf); |
| } |
| } |
| |
| /* Prepare DHCPv4 Discover message and broadcast it */ |
| static void send_discover(struct net_if *iface) |
| { |
| struct net_buf *buf; |
| uint32_t timeout; |
| |
| iface->dhcpv4.xid++; |
| |
| buf = prepare_message(iface, DHCPV4_MSG_TYPE_DISCOVER); |
| if (!buf) { |
| goto fail; |
| } |
| |
| if (!add_req_options(buf) || |
| !add_end(buf)) { |
| goto fail; |
| } |
| |
| setup_header(buf); |
| |
| if (net_send_data(buf) < 0) { |
| goto fail; |
| } |
| |
| timeout = DHCPV4_INITIAL_RETRY_TIMEOUT << iface->dhcpv4.attempts; |
| |
| iface->dhcpv4.state = NET_DHCPV4_DISCOVER; |
| |
| NET_DBG("enter state=%s xid=0x%"PRIx32" timeout=%"PRIu32"s", |
| net_dhcpv4_state_name(iface->dhcpv4.state), |
| iface->dhcpv4.xid, timeout); |
| |
| k_delayed_work_init(&iface->dhcpv4_timeout, dhcpv4_timeout); |
| k_delayed_work_submit(&iface->dhcpv4_timeout, timeout * MSEC_PER_SEC); |
| |
| iface->dhcpv4.attempts++; |
| return; |
| |
| fail: |
| NET_DBG("Message preparation failed"); |
| |
| if (!buf) { |
| net_nbuf_unref(buf); |
| } |
| } |
| |
| static void dhcpv4_timeout(struct k_work *work) |
| { |
| struct net_if *iface = CONTAINER_OF(work, struct net_if, |
| dhcpv4_timeout); |
| |
| NET_DBG("state=%s", net_dhcpv4_state_name(iface->dhcpv4.state)); |
| if (!iface) { |
| NET_DBG("Invalid iface"); |
| return; |
| } |
| |
| switch (iface->dhcpv4.state) { |
| case NET_DHCPV4_INIT: |
| /* Send an initial discover */ |
| send_discover(iface); |
| break; |
| case NET_DHCPV4_DISCOVER: |
| /* Failed to get OFFER message, send DISCOVER again */ |
| send_discover(iface); |
| break; |
| case NET_DHCPV4_REQUEST: |
| /* Maximum number of renewal attempts failed, so start |
| * from the beginning. |
| */ |
| if (iface->dhcpv4.attempts >= DHCPV4_MAX_NUMBER_OF_ATTEMPTS) { |
| NET_DBG("too many attempts, restart"); |
| iface->dhcpv4.attempts = 0; |
| send_discover(iface); |
| } else { |
| /* Repeat requests until max number of attempts */ |
| send_request(iface, false); |
| } |
| break; |
| case NET_DHCPV4_RENEWAL: |
| if (iface->dhcpv4.attempts >= DHCPV4_MAX_NUMBER_OF_ATTEMPTS) { |
| iface->dhcpv4.attempts = 0; |
| NET_DBG("too many attempts, restart"); |
| if (!net_if_ipv4_addr_rm(iface, |
| &iface->dhcpv4.requested_ip)) { |
| NET_DBG("Failed to remove addr from iface"); |
| } |
| |
| /* Maximum number of renewal attempts failed, so start |
| * from the beginning. |
| */ |
| send_discover(iface); |
| } else { |
| /* Repeat renewal request for max number of attempts */ |
| send_request(iface, true); |
| } |
| break; |
| default: |
| NET_DBG("ignore"); |
| break; |
| } |
| } |
| |
| static void dhcpv4_t1_timeout(struct k_work *work) |
| { |
| struct net_if *iface = CONTAINER_OF(work, struct net_if, |
| dhcpv4_t1_timer); |
| |
| NET_DBG(""); |
| |
| if (!iface) { |
| NET_DBG("Invalid iface"); |
| return; |
| } |
| |
| send_request(iface, true); |
| } |
| |
| /* Parse DHCPv4 options and retrieve relavant information |
| * as per RFC 2132. |
| */ |
| static enum net_verdict parse_options(struct net_if *iface, struct net_buf *buf, |
| uint16_t offset, uint8_t *msg_type) |
| { |
| struct net_buf *frag; |
| uint8_t cookie[4]; |
| uint8_t length; |
| uint8_t type; |
| uint16_t pos; |
| |
| frag = net_nbuf_read(buf, offset, &pos, sizeof(magic_cookie), |
| (uint8_t *)cookie); |
| if (!frag || memcmp(magic_cookie, cookie, sizeof(magic_cookie))) { |
| |
| NET_DBG("Incorrect magic cookie"); |
| return NET_DROP; |
| } |
| |
| while (frag) { |
| frag = net_nbuf_read_u8(frag, pos, &pos, &type); |
| |
| if (type == DHCPV4_OPTIONS_END) { |
| NET_DBG("options_end"); |
| return NET_OK; |
| } |
| |
| frag = net_nbuf_read_u8(frag, pos, &pos, &length); |
| if (!frag) { |
| NET_ERR("option parsing, bad length"); |
| return NET_DROP; |
| } |
| |
| switch (type) { |
| case DHCPV4_OPTIONS_SUBNET_MASK: { |
| struct in_addr netmask; |
| |
| if (length != 4) { |
| NET_ERR("options_subnet_mask, bad length"); |
| return NET_DROP; |
| } |
| |
| frag = net_nbuf_read(frag, pos, &pos, length, |
| netmask.s4_addr); |
| if (!frag && pos) { |
| NET_ERR("options_subnet_mask, short packet"); |
| return NET_DROP; |
| } |
| 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 != 0 || length < 4) { |
| NET_ERR("options_router, bad length"); |
| return NET_DROP; |
| } |
| |
| frag = net_nbuf_read(frag, pos, &pos, 4, |
| router.s4_addr); |
| frag = net_nbuf_skip(frag, pos, &pos, length - 4); |
| if (!frag && pos) { |
| NET_ERR("options_router, short packet"); |
| return NET_DROP; |
| } |
| |
| NET_DBG("options_router: %s", |
| net_sprint_ipv4_addr(&router)); |
| net_if_ipv4_set_gw(iface, &router); |
| break; |
| } |
| case DHCPV4_OPTIONS_LEASE_TIME: |
| if (length != 4) { |
| NET_ERR("options_lease_time, bad length"); |
| return NET_DROP; |
| } |
| |
| frag = net_nbuf_read_be32(frag, pos, &pos, |
| &iface->dhcpv4.lease_time); |
| NET_DBG("options_lease_time: %u", |
| iface->dhcpv4.lease_time); |
| if (!iface->dhcpv4.lease_time) { |
| return NET_DROP; |
| } |
| |
| break; |
| case DHCPV4_OPTIONS_RENEWAL: |
| if (length != 4) { |
| NET_DBG("options_renewal, bad length"); |
| return NET_DROP; |
| } |
| |
| frag = net_nbuf_read_be32(frag, pos, &pos, |
| &iface->dhcpv4.renewal_time); |
| NET_DBG("options_renewal: %u", |
| iface->dhcpv4.renewal_time); |
| if (!iface->dhcpv4.renewal_time) { |
| return NET_DROP; |
| } |
| |
| break; |
| case DHCPV4_OPTIONS_SERVER_ID: |
| if (length != 4) { |
| NET_DBG("options_server_id, bad length"); |
| return NET_DROP; |
| } |
| |
| frag = net_nbuf_read(frag, pos, &pos, length, |
| iface->dhcpv4.server_id.s4_addr); |
| NET_DBG("options_server_id: %s", |
| net_sprint_ipv4_addr(&iface->dhcpv4.server_id)); |
| break; |
| case DHCPV4_OPTIONS_MSG_TYPE: |
| if (length != 1) { |
| NET_DBG("options_msg_type, bad length"); |
| return NET_DROP; |
| } |
| |
| frag = net_nbuf_read_u8(frag, pos, &pos, msg_type); |
| break; |
| default: |
| NET_DBG("option unknown: %d", type); |
| frag = net_nbuf_skip(frag, pos, &pos, length); |
| break; |
| } |
| |
| if (!frag && pos) { |
| return NET_DROP; |
| } |
| } |
| |
| /* Invalid case: Options without DHCPV4_OPTIONS_END. */ |
| return NET_DROP; |
| } |
| |
| /* TODO: Handles only DHCPv4 OFFER and ACK messages */ |
| static inline void handle_dhcpv4_reply(struct net_if *iface, uint8_t msg_type) |
| { |
| NET_DBG("state=%s msg=%s", |
| net_dhcpv4_state_name(iface->dhcpv4.state), |
| net_dhcpv4_msg_type_name(msg_type)); |
| /* Check for previous state, reason behind this check is, if client |
| * receives multiple OFFER messages, first one will be handled. |
| * Rest of the replies are discarded. |
| */ |
| if (iface->dhcpv4.state == NET_DHCPV4_DISCOVER) { |
| if (msg_type != NET_DHCPV4_OFFER) { |
| NET_DBG("Reply ignored"); |
| return; |
| } |
| |
| /* Send DHCPv4 Request Message */ |
| k_delayed_work_cancel(&iface->dhcpv4_timeout); |
| |
| iface->dhcpv4.attempts = 0; |
| send_request(iface, false); |
| |
| } else if (iface->dhcpv4.state == NET_DHCPV4_REQUEST || |
| iface->dhcpv4.state == NET_DHCPV4_RENEWAL) { |
| uint32_t timeout; |
| |
| if (msg_type != NET_DHCPV4_ACK) { |
| NET_DBG("Reply ignored"); |
| return; |
| } |
| |
| k_delayed_work_cancel(&iface->dhcpv4_timeout); |
| |
| iface->dhcpv4.attempts = 0; |
| |
| switch (iface->dhcpv4.state) { |
| case NET_DHCPV4_REQUEST: |
| NET_INFO("Received: %s", |
| net_sprint_ipv4_addr( |
| &iface->dhcpv4.requested_ip)); |
| if (!net_if_ipv4_addr_add(iface, |
| &iface->dhcpv4.requested_ip, |
| NET_ADDR_DHCP, |
| iface->dhcpv4.lease_time)) { |
| NET_DBG("Failed to add IPv4 addr to iface %p", |
| iface); |
| return; |
| } |
| |
| break; |
| case NET_DHCPV4_RENEWAL: |
| /* TODO: If the renewal is success, update only |
| * vlifetime on iface. |
| */ |
| break; |
| default: |
| break; |
| } |
| |
| timeout = get_dhcpv4_renewal_time(iface); |
| iface->dhcpv4.state = NET_DHCPV4_ACK; |
| NET_DBG("enter state=%s timeout=%"PRIu32"s", |
| net_dhcpv4_state_name(iface->dhcpv4.state), |
| timeout); |
| |
| /* Start renewal time */ |
| k_delayed_work_init(&iface->dhcpv4_t1_timer, dhcpv4_t1_timeout); |
| k_delayed_work_submit(&iface->dhcpv4_t1_timer, |
| timeout * MSEC_PER_SEC); |
| } |
| } |
| |
| static enum net_verdict net_dhcpv4_input(struct net_conn *conn, |
| struct net_buf *buf, |
| void *user_data) |
| { |
| struct dhcp_msg *msg; |
| struct net_buf *frag; |
| struct net_if *iface; |
| uint8_t msg_type; |
| uint8_t min; |
| uint16_t pos; |
| |
| if (!conn) { |
| NET_DBG("Invalid connection"); |
| return NET_DROP; |
| } |
| |
| if (!buf || !buf->frags) { |
| NET_DBG("Invalid buffer, no fragments"); |
| return NET_DROP; |
| } |
| |
| iface = net_nbuf_iface(buf); |
| if (!iface) { |
| NET_DBG("no iface"); |
| return NET_DROP; |
| } |
| |
| frag = buf->frags; |
| min = NET_IPV4UDPH_LEN + sizeof(struct dhcp_msg); |
| |
| /* If the message is not DHCP then continue passing to |
| * related handlers. |
| */ |
| if (net_buf_frags_len(frag) < min) { |
| NET_DBG("Input msg is not related to DHCPv4"); |
| return NET_CONTINUE; |
| |
| } |
| |
| msg = (struct dhcp_msg *)(frag->data + NET_IPV4UDPH_LEN); |
| |
| NET_DBG("Received dhcp msg [op=0x%x htype=0x%x hlen=%u xid=0x%x " |
| "secs=%u flags=0x%x ciaddr=%d.%d.%d.%d yiaddr=%d.%d.%d.%d " |
| "siaddr=%d.%d.%d.%d giaddr=%d.%d.%d.%d chaddr=%s]", |
| msg->op, msg->htype, msg->hlen, ntohl(msg->xid), |
| msg->secs, msg->flags, |
| msg->ciaddr[0], msg->ciaddr[1], msg->ciaddr[2], msg->ciaddr[3], |
| msg->yiaddr[0], msg->yiaddr[1], msg->yiaddr[2], msg->yiaddr[3], |
| msg->siaddr[0], msg->siaddr[1], msg->siaddr[2], msg->siaddr[3], |
| msg->giaddr[0], msg->giaddr[1], msg->giaddr[2], msg->giaddr[3], |
| net_sprint_ll_addr(msg->chaddr, 6)); |
| |
| if (!(msg->op == DHCPV4_MSG_BOOT_REPLY && |
| iface->dhcpv4.xid == ntohl(msg->xid) && |
| !memcmp(msg->chaddr, iface->link_addr.addr, |
| iface->link_addr.len))) { |
| |
| NET_DBG("Unexpected op (%d), xid (%x vs %x) or chaddr", |
| msg->op, iface->dhcpv4.xid, ntohl(msg->xid)); |
| goto drop; |
| } |
| |
| memcpy(iface->dhcpv4.requested_ip.s4_addr, msg->yiaddr, |
| sizeof(msg->yiaddr)); |
| |
| /* SNAME, FILE are not used at the moment, skip it */ |
| frag = net_nbuf_skip(frag, min, &pos, SIZE_OF_SNAME + SIZE_OF_FILE); |
| if (!frag && pos) { |
| NET_DBG("short packet while skipping sname"); |
| goto drop; |
| } |
| |
| if (parse_options(iface, frag, pos, &msg_type) == NET_DROP) { |
| NET_DBG("Invalid Options"); |
| goto drop; |
| } |
| |
| net_nbuf_unref(buf); |
| |
| handle_dhcpv4_reply(iface, msg_type); |
| |
| return NET_OK; |
| |
| drop: |
| return NET_DROP; |
| } |
| |
| void net_dhcpv4_start(struct net_if *iface) |
| { |
| int ret; |
| uint32_t timeout; |
| uint32_t entropy; |
| |
| iface->dhcpv4.state = NET_DHCPV4_INIT; |
| NET_DBG("state=%s", net_dhcpv4_state_name(iface->dhcpv4.state)); |
| |
| iface->dhcpv4.attempts = 0; |
| iface->dhcpv4.lease_time = 0; |
| iface->dhcpv4.renewal_time = 0; |
| |
| /* 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->dhcpv4.xid = entropy; |
| |
| /* Register UDP input callback on |
| * DHCPV4_SERVER_PORT(67) and DHCPV4_CLIENT_PORT(68) for |
| * all dhcpv4 related incoming packets. |
| */ |
| ret = net_udp_register(NULL, NULL, |
| DHCPV4_SERVER_PORT, |
| DHCPV4_CLIENT_PORT, |
| net_dhcpv4_input, NULL, NULL); |
| if (ret < 0) { |
| NET_DBG("UDP callback registration failed"); |
| return; |
| } |
| |
| /* RFC2131 4.1.1 requires we wait a random period between 1 |
| * and 10 seconds before sending the initial discover. |
| */ |
| timeout = entropy % |
| (DHCPV4_INITIAL_DELAY_MAX - DHCPV4_INITIAL_DELAY_MIN) + |
| DHCPV4_INITIAL_DELAY_MIN; |
| |
| NET_DBG("enter state=%s timeout=%"PRIu32"s", |
| net_dhcpv4_state_name(iface->dhcpv4.state), timeout); |
| |
| k_delayed_work_init(&iface->dhcpv4_timeout, dhcpv4_timeout); |
| k_delayed_work_submit(&iface->dhcpv4_timeout, timeout * MSEC_PER_SEC); |
| } |