blob: b38e8e1ac6071864b90e28a1c71bab1228fccd32 [file] [log] [blame]
/** @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",
"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 = 0;
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 != DHCPV4_MSG_TYPE_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 != DHCPV4_MSG_TYPE_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);
}