/** @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 "dhcpv4.h" #include #include #include #include #include #include "net_private.h" #include #include "udp_internal.h" #include struct dhcp_msg { u8_t op; /* Message type, 1:BOOTREQUEST, 2:BOOTREPLY */ u8_t htype; /* Hardware Address Type */ u8_t hlen; /* Hardware Address length */ u8_t hops; /* used by relay agents when booting via relay * agent, client sets zero */ u32_t xid; /* Transaction ID, random number */ u16_t secs; /* Seconds elapsed since client began address * acquisition or renewal process */ u16_t flags; /* Broadcast or Unicast */ u8_t ciaddr[4]; /* Client IP Address */ u8_t yiaddr[4]; /* your (client) IP address */ u8_t siaddr[4]; /* IP address of next server to use in bootstrap * returned in DHCPOFFER, DHCPACK by server */ u8_t giaddr[4]; /* Relat agent IP address */ u8_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 /* These enumerations represent RFC2131 defined msy type codes, hence * they should not be renumbered. */ enum dhcpv4_msg_type { DHCPV4_MSG_TYPE_DISCOVER = 1, DHCPV4_MSG_TYPE_OFFER = 2, DHCPV4_MSG_TYPE_REQUEST = 3, DHCPV4_MSG_TYPE_DECLINE = 4, DHCPV4_MSG_TYPE_ACK = 5, DHCPV4_MSG_TYPE_NAK = 6, DHCPV4_MSG_TYPE_RELEASE = 7, 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_REBINDING 59 #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 /* RFC 1497 [17] */ static const u8_t magic_cookie[4] = { 0x63, 0x82, 0x53, 0x63 }; static void dhcpv4_timeout(struct k_work *work); static const char * net_dhcpv4_msg_type_name(enum dhcpv4_msg_type msg_type) __attribute__((unused)); 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]; } static const char * net_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 add_cookie(struct net_pkt *pkt) { return net_pkt_append_all(pkt, sizeof(magic_cookie), magic_cookie, K_FOREVER); } /* Add a an option with the form OPTION LENGTH VALUE. */ static bool add_option_length_value(struct net_pkt *pkt, u8_t option, u8_t size, const u8_t *value) { if (!net_pkt_append_u8(pkt, option)) { return false; } if (!net_pkt_append_u8(pkt, size)) { return false; } if (!net_pkt_append_all(pkt, size, value, K_FOREVER)) { return false; } return true; } /* Add DHCPv4 message type */ static bool add_msg_type(struct net_pkt *pkt, u8_t type) { return 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 add_req_options(struct net_pkt *pkt) { static const u8_t data[5] = { DHCPV4_OPTIONS_REQ_LIST, 3, /* Length */ DHCPV4_OPTIONS_SUBNET_MASK, DHCPV4_OPTIONS_ROUTER, DHCPV4_OPTIONS_DNS_SERVER }; return net_pkt_append_all(pkt, sizeof(data), data, K_FOREVER); } static bool add_server_id(struct net_pkt *pkt, const struct in_addr *addr) { return add_option_length_value(pkt, DHCPV4_OPTIONS_SERVER_ID, 4, addr->s4_addr); } static bool add_req_ipaddr(struct net_pkt *pkt, const struct in_addr *addr) { return add_option_length_value(pkt, DHCPV4_OPTIONS_REQ_IPADDR, 4, addr->s4_addr); } /* Add DHCPv4 Options end, rest of the message can be padded wit zeros */ static inline bool add_end(struct net_pkt *pkt) { return net_pkt_append_u8(pkt, DHCPV4_OPTIONS_END); } /* File is empty ATM */ static inline bool add_file(struct net_pkt *pkt) { u8_t len = SIZE_OF_FILE; while (len-- > 0) { if (!net_pkt_append_u8(pkt, 0)) { return false; } } return true; } /* SNAME is empty ATM */ static inline bool add_sname(struct net_pkt *pkt) { u8_t len = SIZE_OF_SNAME; while (len-- > 0) { if (!net_pkt_append_u8(pkt, 0)) { return false; } } return true; } /* Setup IPv4 + UDP header */ static void setup_header(struct net_pkt *pkt, const struct in_addr *server_addr) { struct net_ipv4_hdr *ipv4; struct net_udp_hdr hdr, *udp; u16_t len; ipv4 = NET_IPV4_HDR(pkt); udp = net_udp_get_hdr(pkt, &hdr); NET_ASSERT(udp && udp != &hdr); len = net_pkt_get_len(pkt); /* 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] = (u8_t)len; ipv4->chksum = ~net_calc_chksum_ipv4(pkt); net_ipaddr_copy(&ipv4->dst, server_addr); 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 = 0; udp->chksum = ~net_calc_chksum_udp(pkt); net_udp_set_hdr(pkt, udp); } /* Prepare initial DHCPv4 message and add options as per message type */ static struct net_pkt *prepare_message(struct net_if *iface, u8_t type, const struct in_addr *ciaddr) { struct net_pkt *pkt; struct net_buf *frag; struct dhcp_msg *msg; pkt = net_pkt_get_reserve_tx(net_if_get_ll_reserve(iface, NULL), K_FOREVER); frag = net_pkt_get_frag(pkt, K_FOREVER); net_pkt_set_iface(pkt, iface); net_pkt_set_family(pkt, AF_INET); net_pkt_set_ip_hdr_len(pkt, sizeof(struct net_ipv4_hdr)); net_pkt_frag_add(pkt, frag); /* Leave room for IPv4 + UDP headers */ net_buf_add(pkt->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); 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, iface->link_addr.addr, iface->link_addr.len); net_buf_add(frag, sizeof(struct dhcp_msg)); if (!add_sname(pkt) || !add_file(pkt) || !add_cookie(pkt) || !add_msg_type(pkt, type)) { goto fail; } return pkt; fail: net_pkt_unref(pkt); return NULL; } /* Prepare DHCPv4 Message request and send it to peer */ static void send_request(struct net_if *iface) { struct net_pkt *pkt; u32_t timeout; const struct in_addr *server_addr = net_ipv4_broadcast_address(); const struct in_addr *ciaddr = NULL; bool with_server_id = false; bool with_requested_ip = false; iface->dhcpv4.xid++; switch (iface->dhcpv4.state) { case NET_DHCPV4_DISABLED: case NET_DHCPV4_INIT: case NET_DHCPV4_SELECTING: case NET_DHCPV4_BOUND: /* Not possible */ NET_ASSERT_INFO(0, "Invalid state %s", net_dhcpv4_state_name(iface->dhcpv4.state)); 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->dhcpv4.requested_ip; /* UNICAST the DHCPREQUEST */ server_addr = &iface->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->dhcpv4.requested_ip; server_addr = net_ipv4_broadcast_address(); break; } pkt = prepare_message(iface, DHCPV4_MSG_TYPE_REQUEST, ciaddr); if (!pkt) { goto fail; } if (with_server_id && !add_server_id(pkt, &iface->dhcpv4.server_id)) { goto fail; } if (with_requested_ip && !add_req_ipaddr(pkt, &iface->dhcpv4.requested_ip)) { goto fail; } if (!add_end(pkt)) { goto fail; } setup_header(pkt, server_addr); if (net_send_data(pkt) < 0) { goto fail; } timeout = DHCPV4_INITIAL_RETRY_TIMEOUT << iface->dhcpv4.attempts; k_delayed_work_submit(&iface->dhcpv4.timer, timeout * MSEC_PER_SEC); iface->dhcpv4.attempts++; const char *ciaddr_buf = 0; static char pbuf[NET_IPV4_ADDR_LEN]; if (ciaddr) { ciaddr_buf = net_addr_ntop(AF_INET, ciaddr, pbuf, sizeof(pbuf)); } else { ciaddr_buf = "0.0.0.0"; } NET_DBG("send request dst=%s xid=0x%"PRIx32" ciaddr=%s" "%s%s timeout=%"PRIu32"s", net_sprint_ipv4_addr(server_addr), iface->dhcpv4.xid, ciaddr_buf, with_server_id ? " +server-id" : "", with_requested_ip ? " +requested-ip" : "", timeout); return; fail: NET_DBG("Message preparation failed"); if (!pkt) { net_pkt_unref(pkt); } } /* Prepare DHCPv4 Discover message and broadcast it */ static void send_discover(struct net_if *iface) { struct net_pkt *pkt; u32_t timeout; iface->dhcpv4.xid++; pkt = prepare_message(iface, DHCPV4_MSG_TYPE_DISCOVER, NULL); if (!pkt) { goto fail; } if (!add_req_options(pkt) || !add_end(pkt)) { goto fail; } setup_header(pkt, net_ipv4_broadcast_address()); if (net_send_data(pkt) < 0) { goto fail; } timeout = DHCPV4_INITIAL_RETRY_TIMEOUT << iface->dhcpv4.attempts; k_delayed_work_submit(&iface->dhcpv4.timer, timeout * MSEC_PER_SEC); iface->dhcpv4.attempts++; NET_DBG("send discover xid=0x%"PRIx32" timeout=%"PRIu32"s", iface->dhcpv4.xid, timeout); return; fail: NET_DBG("Message preparation failed"); if (!pkt) { net_pkt_unref(pkt); } } static void enter_selecting(struct net_if *iface) { iface->dhcpv4.attempts = 0; iface->dhcpv4.lease_time = 0; iface->dhcpv4.renewal_time = 0; iface->dhcpv4.rebinding_time = 0; iface->dhcpv4.state = NET_DHCPV4_SELECTING; NET_DBG("enter state=%s", net_dhcpv4_state_name(iface->dhcpv4.state)); send_discover(iface); } static void enter_requesting(struct net_if *iface) { iface->dhcpv4.attempts = 0; iface->dhcpv4.state = NET_DHCPV4_REQUESTING; NET_DBG("enter state=%s", net_dhcpv4_state_name(iface->dhcpv4.state)); send_request(iface); } static void dhcpv4_t1_timeout(struct k_work *work) { struct net_if *iface = CONTAINER_OF(work, struct net_if, dhcpv4.t1_timer); NET_DBG(""); switch (iface->dhcpv4.state) { case NET_DHCPV4_DISABLED: case NET_DHCPV4_INIT: case NET_DHCPV4_SELECTING: case NET_DHCPV4_REQUESTING: case NET_DHCPV4_RENEWING: case NET_DHCPV4_REBINDING: /* This path cannot happen. */ NET_ASSERT_INFO(0, "Invalid state %s", net_dhcpv4_state_name(iface->dhcpv4.state)); break; case NET_DHCPV4_BOUND: iface->dhcpv4.state = NET_DHCPV4_RENEWING; NET_DBG("enter state=%s", net_dhcpv4_state_name(iface->dhcpv4.state)); iface->dhcpv4.attempts = 0; send_request(iface); break; } } static void dhcpv4_t2_timeout(struct k_work *work) { struct net_if *iface = CONTAINER_OF(work, struct net_if, dhcpv4.t2_timer); NET_DBG(""); switch (iface->dhcpv4.state) { case NET_DHCPV4_DISABLED: case NET_DHCPV4_INIT: case NET_DHCPV4_SELECTING: case NET_DHCPV4_REQUESTING: case NET_DHCPV4_REBINDING: NET_ASSERT_INFO(0, "Invalid state %s", net_dhcpv4_state_name(iface->dhcpv4.state)); break; case NET_DHCPV4_BOUND: /* If renewal time and rebinding time are * misconfigured we may end up with T2 firing before * T1. Deal with it as though we had transitioned * through RENEWAL already. */ case NET_DHCPV4_RENEWING: iface->dhcpv4.state = NET_DHCPV4_REBINDING; NET_DBG("enter state=%s", net_dhcpv4_state_name(iface->dhcpv4.state)); iface->dhcpv4.attempts = 0; send_request(iface); break; } } static void enter_bound(struct net_if *iface) { u32_t renewal_time; u32_t rebinding_time; k_delayed_work_cancel(&iface->dhcpv4.timer); renewal_time = iface->dhcpv4.renewal_time; if (!renewal_time) { /* The default renewal time rfc2131 4.4.5 */ renewal_time = iface->dhcpv4.lease_time / 2; } rebinding_time = iface->dhcpv4.rebinding_time; if (!rebinding_time) { /* The default rebinding time rfc2131 4.4.5 */ rebinding_time = iface->dhcpv4.lease_time * 875 / 1000; } iface->dhcpv4.state = NET_DHCPV4_BOUND; NET_DBG("enter state=%s renewal=%"PRIu32"s " "rebinding=%"PRIu32"s", net_dhcpv4_state_name(iface->dhcpv4.state), renewal_time, rebinding_time); /* Start renewal time */ k_delayed_work_submit(&iface->dhcpv4.t1_timer, renewal_time * MSEC_PER_SEC); /* Start rebinding time */ k_delayed_work_submit(&iface->dhcpv4.t2_timer, rebinding_time * MSEC_PER_SEC); } static void dhcpv4_timeout(struct k_work *work) { struct net_if *iface = CONTAINER_OF(work, struct net_if, dhcpv4.timer); NET_DBG("state=%s", net_dhcpv4_state_name(iface->dhcpv4.state)); switch (iface->dhcpv4.state) { case NET_DHCPV4_DISABLED: break; case NET_DHCPV4_INIT: enter_selecting(iface); break; case NET_DHCPV4_SELECTING: /* Failed to get OFFER message, send DISCOVER again */ send_discover(iface); break; case NET_DHCPV4_REQUESTING: /* 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"); enter_selecting(iface); } else { send_request(iface); } break; case NET_DHCPV4_BOUND: break; case NET_DHCPV4_RENEWING: case NET_DHCPV4_REBINDING: if (iface->dhcpv4.attempts >= DHCPV4_MAX_NUMBER_OF_ATTEMPTS) { 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. */ enter_selecting(iface); } else { send_request(iface); } break; } } /* Parse DHCPv4 options and retrieve relavant information * as per RFC 2132. */ static enum net_verdict parse_options(struct net_if *iface, struct net_buf *frag, u16_t offset, enum dhcpv4_msg_type *msg_type) { u8_t cookie[4]; u8_t length; u8_t type; u16_t pos; frag = net_frag_read(frag, offset, &pos, sizeof(magic_cookie), (u8_t *)cookie); if (!frag || memcmp(magic_cookie, cookie, sizeof(magic_cookie))) { NET_DBG("Incorrect magic cookie"); return NET_DROP; } while (frag) { frag = net_frag_read_u8(frag, pos, &pos, &type); if (type == DHCPV4_OPTIONS_END) { NET_DBG("options_end"); return NET_OK; } frag = net_frag_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_frag_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_frag_read(frag, pos, &pos, 4, router.s4_addr); frag = net_frag_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_frag_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_frag_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_REBINDING: if (length != 4) { NET_DBG("options_rebinding, bad length"); return NET_DROP; } frag = net_frag_read_be32(frag, pos, &pos, &iface->dhcpv4.rebinding_time); NET_DBG("options_rebinding: %u", iface->dhcpv4.rebinding_time); if (!iface->dhcpv4.rebinding_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_frag_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: { u8_t v; if (length != 1) { NET_DBG("options_msg_type, bad length"); return NET_DROP; } frag = net_frag_read_u8(frag, pos, &pos, &v); *msg_type = v; break; } default: NET_DBG("option unknown: %d", type); frag = net_frag_skip(frag, pos, &pos, length); break; } if (!frag && pos) { return NET_DROP; } } /* Invalid case: Options without DHCPV4_OPTIONS_END. */ return NET_DROP; } static inline void handle_offer(struct net_if *iface) { switch (iface->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: k_delayed_work_cancel(&iface->dhcpv4.timer); enter_requesting(iface); break; } } static void handle_ack(struct net_if *iface) { switch (iface->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->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; } enter_bound(iface); break; case NET_DHCPV4_RENEWING: case NET_DHCPV4_REBINDING: /* TODO: If the renewal is success, update only * vlifetime on iface. */ enter_bound(iface); break; } } static void handle_nak(struct net_if *iface) { switch (iface->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. */ k_delayed_work_cancel(&iface->dhcpv4.timer); k_delayed_work_cancel(&iface->dhcpv4.t1_timer); k_delayed_work_cancel(&iface->dhcpv4.t2_timer); enter_selecting(iface); break; } } static void handle_dhcpv4_reply(struct net_if *iface, enum dhcpv4_msg_type msg_type) { NET_DBG("state=%s msg=%s", net_dhcpv4_state_name(iface->dhcpv4.state), net_dhcpv4_msg_type_name(msg_type)); switch (msg_type) { case DHCPV4_MSG_TYPE_OFFER: handle_offer(iface); break; case DHCPV4_MSG_TYPE_ACK: handle_ack(iface); break; case DHCPV4_MSG_TYPE_NAK: handle_nak(iface); break; default: NET_DBG("ignore message"); break; } } static enum net_verdict net_dhcpv4_input(struct net_conn *conn, struct net_pkt *pkt, void *user_data) { struct dhcp_msg *msg; struct net_buf *frag; struct net_if *iface; enum dhcpv4_msg_type msg_type = 0; u8_t min; u16_t pos; if (!conn) { NET_DBG("Invalid connection"); return NET_DROP; } if (!pkt || !pkt->frags) { NET_DBG("Invalid packet, no fragments"); return NET_DROP; } iface = net_pkt_iface(pkt); if (!iface) { NET_DBG("no iface"); return NET_DROP; } frag = pkt->frags; min = NET_IPV4UDPH_LEN + sizeof(struct dhcp_msg); /* If the message is not DHCP then continue passing to * related handlers. */ if (net_pkt_get_len(pkt) < 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_frag_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_pkt_unref(pkt); handle_dhcpv4_reply(iface, msg_type); return NET_OK; drop: return NET_DROP; } void net_dhcpv4_start(struct net_if *iface) { u32_t timeout; u32_t entropy; switch (iface->dhcpv4.state) { case NET_DHCPV4_DISABLED: 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; iface->dhcpv4.server_id.s_addr = 0; iface->dhcpv4.requested_ip.s_addr = 0; k_delayed_work_init(&iface->dhcpv4.timer, dhcpv4_timeout); k_delayed_work_init(&iface->dhcpv4.t1_timer, dhcpv4_t1_timeout); k_delayed_work_init(&iface->dhcpv4.t2_timer, dhcpv4_t2_timeout); /* 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; /* 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("wait timeout=%"PRIu32"s", timeout); k_delayed_work_submit(&iface->dhcpv4.timer, timeout * MSEC_PER_SEC); 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; } } void net_dhcpv4_stop(struct net_if *iface) { switch (iface->dhcpv4.state) { case NET_DHCPV4_DISABLED: break; case NET_DHCPV4_BOUND: if (!net_if_ipv4_addr_rm(iface, &iface->dhcpv4.requested_ip)) { NET_DBG("Failed to remove addr from iface"); } /* Fall through */ case NET_DHCPV4_INIT: case NET_DHCPV4_SELECTING: case NET_DHCPV4_REQUESTING: case NET_DHCPV4_RENEWING: case NET_DHCPV4_REBINDING: iface->dhcpv4.state = NET_DHCPV4_DISABLED; NET_DBG("state=%s", net_dhcpv4_state_name(iface->dhcpv4.state)); k_delayed_work_cancel(&iface->dhcpv4.timer); k_delayed_work_cancel(&iface->dhcpv4.t1_timer); k_delayed_work_cancel(&iface->dhcpv4.t2_timer); break; } } int 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.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(NULL, &local_addr, DHCPV4_SERVER_PORT, DHCPV4_CLIENT_PORT, net_dhcpv4_input, NULL, NULL); if (ret < 0) { NET_DBG("UDP callback registration failed"); return ret; } return 0; }