/** @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 <stdbool.h>
#include <zephyr/random/random.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 <zephyr/logging/log_backend.h>
#include <zephyr/logging/log_backend_net.h>
#include <zephyr/logging/log_ctrl.h>

#include "dhcpv4_internal.h"
#include "ipv4.h"
#include "net_stats.h"

#include <zephyr/sys/slist.h>
#include <zephyr/sys/util.h>

#define PKT_WAIT_TIME K_MSEC(100)

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_if_cb;
#if defined(CONFIG_NET_IPV4_ACD)
static struct net_mgmt_event_callback mgmt4_acd_cb;
#endif

#if defined(CONFIG_NET_DHCPV4_OPTION_CALLBACKS)
static sys_slist_t option_callbacks = SYS_SLIST_STATIC_INIT(&option_callbacks);
static int unique_types_in_callbacks;
#endif

#if defined(CONFIG_NET_DHCPV4_OPTION_CALLBACKS_VENDOR_SPECIFIC)
static sys_slist_t option_vendor_callbacks = SYS_SLIST_STATIC_INIT(&option_vendor_callbacks);
#endif

static const uint8_t min_req_options[] = {
	DHCPV4_OPTIONS_SUBNET_MASK,
	DHCPV4_OPTIONS_ROUTER,
#ifdef CONFIG_LOG_BACKEND_NET_USE_DHCPV4_OPTION
	DHCPV4_OPTIONS_LOG_SERVER,
#endif
#ifdef CONFIG_NET_DHCPV4_OPTION_NTP_SERVER
	DHCPV4_OPTIONS_NTP_SERVER,
#endif
#ifdef CONFIG_NET_DHCPV4_OPTION_CALLBACKS_VENDOR_SPECIFIC
	DHCPV4_OPTIONS_VENDOR_SPECIFIC,
#endif
	DHCPV4_OPTIONS_DNS_SERVER
};

/* RFC 1497 [17] */
static const uint8_t magic_cookie[4] = { 0x63, 0x82, 0x53, 0x63 };

/* Add magic cookie to DCHPv4 messages */
static inline bool dhcpv4_add_cookie(struct net_pkt *pkt)
{
	if (net_pkt_write(pkt, (void *)magic_cookie,
			  ARRAY_SIZE(magic_cookie))) {
		return false;
	}

	return true;
}

#if defined(CONFIG_NET_DHCPV4_OPTION_CALLBACKS)
static void dhcpv4_option_callback_get_unique_types(uint8_t *types)
{
	struct net_dhcpv4_option_callback *cb, *tmp;
	int count = ARRAY_SIZE(min_req_options);
	bool found = false;

	memcpy(types, min_req_options, ARRAY_SIZE(min_req_options));

	SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&option_callbacks, cb, tmp, node) {
		for (int j = 0; j < count; j++) {
			if (types[j] == cb->option) {
				found = true;
				break;
			}
		}

		if (!found) {
			if (count >= CONFIG_NET_DHCPV4_MAX_REQUESTED_OPTIONS) {
				NET_ERR("Too many unique options in callbacks, cannot request "
					"option %d",
					cb->option);
				continue;
			}
			types[count] = cb->option;
			count++;
		} else {
			found = false;
		}
	}
	unique_types_in_callbacks = count - ARRAY_SIZE(min_req_options);
}

static void dhcpv4_option_callback_count(void)
{
	uint8_t types[CONFIG_NET_DHCPV4_MAX_REQUESTED_OPTIONS];

	dhcpv4_option_callback_get_unique_types(types);
}
#endif	/* CONFIG_NET_DHCPV4_OPTION_CALLBACKS */

/* 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)
{
#ifdef CONFIG_NET_DHCPV4_OPTION_CALLBACKS
	uint8_t data[CONFIG_NET_DHCPV4_MAX_REQUESTED_OPTIONS];

	dhcpv4_option_callback_get_unique_types(data);

	return dhcpv4_add_option_length_value(pkt, DHCPV4_OPTIONS_REQ_LIST,
		unique_types_in_callbacks + ARRAY_SIZE(min_req_options), data);
#else
	return dhcpv4_add_option_length_value(pkt, DHCPV4_OPTIONS_REQ_LIST,
					      ARRAY_SIZE(min_req_options), min_req_options);
#endif
}

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

#if defined(CONFIG_NET_DHCPV4_VENDOR_CLASS_IDENTIFIER)
static bool dhcpv4_add_vendor_class_id(struct net_pkt *pkt,
				 const char *vendor_class_id, const size_t size)
{
	return dhcpv4_add_option_length_value(pkt, DHCPV4_OPTIONS_VENDOR_CLASS_ID,
					      size, vendor_class_id);
}
#endif

/* Add DHCPv4 Options end, rest of the message can be padded with 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 defined(CONFIG_NET_DHCPV4_VENDOR_CLASS_IDENTIFIER)
	const char vendor_class_id[] = CONFIG_NET_DHCPV4_VENDOR_CLASS_IDENTIFIER_STRING;
	const size_t vendor_class_id_size = sizeof(vendor_class_id) - 1;
#endif

	if (src_addr == NULL) {
		addr = net_ipv4_unspecified_address();
	} else {
		addr = src_addr;
	}

	if (server_id) {
		size += DHCPV4_OLV_MSG_SERVER_ID;
	}

	if (requested_ip) {
		size +=  DHCPV4_OLV_MSG_REQ_IPADDR;
	}

	if (type == NET_DHCPV4_MSG_TYPE_DISCOVER) {
		size +=  DHCPV4_OLV_MSG_REQ_LIST + ARRAY_SIZE(min_req_options);
#if defined(CONFIG_NET_DHCPV4_OPTION_CALLBACKS)
		size += unique_types_in_callbacks;
#endif
	}

#if defined(CONFIG_NET_HOSTNAME_ENABLE)
	if (hostname_size > 0) {
		size += DHCPV4_OLV_MSG_HOST_NAME + hostname_size;
	}
#endif

#if defined(CONFIG_NET_DHCPV4_VENDOR_CLASS_IDENTIFIER)
	if (vendor_class_id_size > 0) {
		size += DHCPV4_OLV_MSG_VENDOR_CLASS_ID + vendor_class_id_size;
	}
#endif

	pkt = net_pkt_alloc_with_buffer(iface, size, AF_INET,
					IPPROTO_UDP, PKT_WAIT_TIME);
	if (!pkt) {
		return NULL;
	}

	net_pkt_set_ipv4_ttl(pkt, 0xFF);

	if (net_ipv4_create(pkt, addr, server_addr) ||
	    net_udp_create(pkt, htons(DHCPV4_CLIENT_PORT),
			   htons(DHCPV4_SERVER_PORT))) {
		goto fail;
	}

	msg = (struct dhcp_msg *)net_pkt_get_data(pkt, &dhcp_access);

	(void)memset(msg, 0, sizeof(struct dhcp_msg));

	msg->op    = DHCPV4_MSG_BOOT_REQUEST;
	msg->htype = HARDWARE_ETHERNET_TYPE;
	msg->hlen  = net_if_get_link_addr(iface)->len;
	msg->xid   = htonl(iface->config.dhcpv4.xid);
	msg->flags = IS_ENABLED(CONFIG_NET_DHCPV4_ACCEPT_UNICAST) ?
		     htons(DHCPV4_MSG_UNICAST) : htons(DHCPV4_MSG_BROADCAST);

	if (ciaddr) {
		/* The ciaddr field was zero'd out above, if we are
		 * asked to send a ciaddr then fill it in now
		 * otherwise leave it as all zeros.
		 */
		memcpy(msg->ciaddr, ciaddr, 4);
	}

	memcpy(msg->chaddr, net_if_get_link_addr(iface)->addr,
	       net_if_get_link_addr(iface)->len);

	if (net_pkt_set_data(pkt, &dhcp_access)) {
		goto fail;
	}

	if (!dhcpv4_add_sname(pkt) ||
	    !dhcpv4_add_file(pkt) ||
	    !dhcpv4_add_cookie(pkt) ||
	    !dhcpv4_add_msg_type(pkt, type)) {
		goto fail;
	}

	if ((server_id &&
	     !dhcpv4_add_server_id(pkt, &iface->config.dhcpv4.server_id)) ||
	    (requested_ip &&
	     !dhcpv4_add_req_ipaddr(pkt, &iface->config.dhcpv4.requested_ip))) {
		goto fail;
	}

	if (type == NET_DHCPV4_MSG_TYPE_DISCOVER && !dhcpv4_add_req_options(pkt)) {
		goto fail;
	}

#if defined(CONFIG_NET_HOSTNAME_ENABLE)
	if (hostname_size > 0 &&
	     !dhcpv4_add_hostname(pkt, hostname, hostname_size)) {
		goto fail;
	}
#endif

#if defined(CONFIG_NET_DHCPV4_VENDOR_CLASS_IDENTIFIER)
	if (vendor_class_id_size > 0 &&
	     !dhcpv4_add_vendor_class_id(pkt, vendor_class_id, vendor_class_id_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 dhcpv4=%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);
}

/* Set a new timeout w/o updating base time. Used for RENEWING and REBINDING to
 * keep track of T1/T2/lease timeouts.
 */
static void dhcpv4_set_timeout_inc(struct net_if_dhcpv4 *dhcpv4,
				   int64_t now, uint32_t timeout)
{
	int64_t timeout_ms;

	NET_DBG("sched timeout dhcpv4=%p timeout=%us", dhcpv4, timeout);

	timeout_ms = (now - dhcpv4->timer_start) + MSEC_PER_SEC * timeout;
	dhcpv4->request_time = (uint32_t)(timeout_ms / MSEC_PER_SEC);
}

static uint32_t dhcpv4_get_timeleft(int64_t start, uint32_t time, int64_t now)
{
	int64_t deadline = start + MSEC_PER_SEC * time;
	uint32_t ret = 0U;

	/* If we haven't reached the deadline, calculate the
	 * rounded-up whole seconds until the deadline.
	 */
	if (deadline > now) {
		ret = (uint32_t)DIV_ROUND_UP(deadline - now, MSEC_PER_SEC);
	}

	return ret;
}

static uint32_t dhcpv4_request_timeleft(struct net_if *iface, int64_t now)
{
	uint32_t request_time = iface->config.dhcpv4.request_time;

	return dhcpv4_get_timeleft(iface->config.dhcpv4.timer_start,
				   request_time, now);
}

static uint32_t dhcpv4_renewal_timeleft(struct net_if *iface, int64_t now)
{
	return dhcpv4_get_timeleft(iface->config.dhcpv4.timer_start,
				   iface->config.dhcpv4.renewal_time,
				   now);
}

static uint32_t dhcpv4_rebinding_timeleft(struct net_if *iface, int64_t now)
{
	return dhcpv4_get_timeleft(iface->config.dhcpv4.timer_start,
				   iface->config.dhcpv4.rebinding_time,
				   now);
}

static uint32_t dhcpv4_lease_timeleft(struct net_if *iface, int64_t now)
{
	return dhcpv4_get_timeleft(iface->config.dhcpv4.timer_start,
				   iface->config.dhcpv4.lease_time,
				   now);
}

/* 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_rand8_get() % 3U) - 1;

	dhcpv4->attempts++;
	dhcpv4_set_timeout(dhcpv4, timeout);

	return timeout;
}

static uint32_t dhcpv4_calculate_renew_rebind_timeout(uint32_t timeleft)
{
	uint32_t timeout;

	/* RFC2131 4.4.5:
	 * In both RENEWING and REBINDING states, if the client receives no
	 * response to its DHCPREQUEST message, the client SHOULD wait one-half
	 * of the remaining time until T2 (in RENEWING state) and one-half of
	 * the remaining lease time (in REBINDING state), down to a minimum of
	 * 60 seconds, before retransmitting the DHCPREQUEST message.
	 */

	if (timeleft < DHCPV4_RENEW_REBIND_TIMEOUT_MIN) {
		timeout = timeleft;
	} else if (timeleft / 2U < DHCPV4_RENEW_REBIND_TIMEOUT_MIN) {
		timeout = DHCPV4_RENEW_REBIND_TIMEOUT_MIN;
	} else {
		timeout = timeleft / 2U;
	}

	return timeout;
}

static uint32_t dhcpv4_update_renew_timeout(struct net_if *iface)
{
	struct net_if_dhcpv4 *dhcpv4 = &iface->config.dhcpv4;
	int64_t now = k_uptime_get();
	uint32_t timeout;

	timeout = dhcpv4_calculate_renew_rebind_timeout(
			dhcpv4_rebinding_timeleft(iface, now));

	dhcpv4->attempts++;
	dhcpv4_set_timeout_inc(dhcpv4, now, timeout);

	return timeout;
}

static uint32_t dhcpv4_update_rebind_timeout(struct net_if *iface)
{
	struct net_if_dhcpv4 *dhcpv4 = &iface->config.dhcpv4;
	int64_t now = k_uptime_get();
	uint32_t timeout;

	timeout = dhcpv4_calculate_renew_rebind_timeout(
			dhcpv4_lease_timeleft(iface, now));

	dhcpv4->attempts++;
	dhcpv4_set_timeout_inc(dhcpv4, now, 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:
	case NET_DHCPV4_DECLINE:
		/* Not possible */
		NET_ASSERT(0, "Invalid state %s",
			   net_dhcpv4_state_name(iface->config.dhcpv4.state));
		goto fail;
		break;
	case NET_DHCPV4_REQUESTING:
		with_server_id = true;
		with_requested_ip = true;
		memcpy(&iface->config.dhcpv4.request_server_addr, &iface->config.dhcpv4.server_id,
		       sizeof(struct in_addr));
		timeout = dhcpv4_update_message_timeout(&iface->config.dhcpv4);
		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;
		timeout = dhcpv4_update_renew_timeout(iface);

		/* 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;
		timeout = dhcpv4_update_rebind_timeout(iface);

		break;
	}

	pkt = dhcpv4_create_message(iface, NET_DHCPV4_MSG_TYPE_REQUEST,
				    ciaddr, src_addr, server_addr,
				    with_server_id, with_requested_ip);
	if (!pkt) {
		goto fail;
	}

	if (net_send_data(pkt) < 0) {
		goto fail;
	}

	net_stats_update_udp_sent(iface);

	NET_DBG("send request dst=%s xid=0x%x ciaddr=%s%s%s timeout=%us",
		net_sprint_ipv4_addr(server_addr),
		iface->config.dhcpv4.xid,
		ciaddr ?
		net_sprint_ipv4_addr(ciaddr) : "<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, NET_DHCPV4_MSG_TYPE_DISCOVER,
				    NULL, NULL, net_ipv4_broadcast_address(),
				    false, false);
	if (!pkt) {
		goto fail;
	}

	if (net_send_data(pkt) < 0) {
		goto fail;
	}

	net_stats_update_udp_sent(iface);

	timeout = dhcpv4_update_message_timeout(&iface->config.dhcpv4);

	NET_DBG("send discover xid=0x%x timeout=%us",
		iface->config.dhcpv4.xid, timeout);

	return timeout;

fail:
	if (pkt) {
		net_pkt_unref(pkt);
	}

	return iface->config.dhcpv4.xid %
			(CONFIG_NET_DHCPV4_INITIAL_DELAY_MAX -
			 DHCPV4_INITIAL_DELAY_MIN) +
			DHCPV4_INITIAL_DELAY_MIN;
}

static void dhcpv4_send_decline(struct net_if *iface)
{
	struct net_pkt *pkt;

	iface->config.dhcpv4.xid++;

	pkt = dhcpv4_create_message(iface, NET_DHCPV4_MSG_TYPE_DECLINE,
				    NULL, NULL, net_ipv4_broadcast_address(),
				    false, true);
	if (!pkt) {
		goto fail;
	}

	if (net_send_data(pkt) < 0) {
		goto fail;
	}

	net_stats_update_udp_sent(iface);

	return;

fail:
	if (pkt) {
		net_pkt_unref(pkt);
	}
}

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 void dhcpv4_enter_requesting(struct net_if *iface, struct dhcp_msg *msg)
{
	iface->config.dhcpv4.attempts = 0U;
	iface->config.dhcpv4.state = NET_DHCPV4_REQUESTING;
	NET_DBG("enter state=%s",
		net_dhcpv4_state_name(iface->config.dhcpv4.state));

	memcpy(iface->config.dhcpv4.requested_ip.s4_addr,
	       msg->yiaddr, sizeof(msg->yiaddr));

	dhcpv4_send_request(iface);
}

/* Must be invoked with lock held */
static void dhcpv4_enter_bound(struct net_if *iface)
{
	struct net_if_dhcpv4 *dhcpv4 = &iface->config.dhcpv4;

	/* Load defaults in case server did not provide T1/T2 values. */
	if (dhcpv4->renewal_time == 0U) {
		/* The default renewal time rfc2131 4.4.5 */
		dhcpv4->renewal_time = dhcpv4->lease_time / 2U;
	}

	if (dhcpv4->rebinding_time == 0U) {
		/* The default rebinding time rfc2131 4.4.5 */
		dhcpv4->rebinding_time = dhcpv4->lease_time * 875U / 1000U;
	}

	/* RFC2131 4.4.5:
	 * T1 MUST be earlier than T2, which, in turn, MUST be earlier than the
	 * time at which the client's lease will expire.
	 */
	if ((dhcpv4->renewal_time >= dhcpv4->rebinding_time) ||
	    (dhcpv4->rebinding_time >= dhcpv4->lease_time)) {
		/* In case server did not provide valid values, fall back to
		 * defaults.
		 */
		dhcpv4->renewal_time = dhcpv4->lease_time / 2U;
		dhcpv4->rebinding_time = dhcpv4->lease_time * 875U / 1000U;
	}

	dhcpv4->state = NET_DHCPV4_BOUND;
	NET_DBG("enter state=%s renewal=%us rebinding=%us",
		net_dhcpv4_state_name(dhcpv4->state),
		dhcpv4->renewal_time, dhcpv4->rebinding_time);

	dhcpv4_set_timeout(dhcpv4, dhcpv4->renewal_time);

	net_mgmt_event_notify_with_info(NET_EVENT_IPV4_DHCP_BOUND, iface,
					&iface->config.dhcpv4,
					sizeof(iface->config.dhcpv4));
}

static void dhcpv4_enter_renewing(struct net_if *iface)
{
	iface->config.dhcpv4.state = NET_DHCPV4_RENEWING;
	iface->config.dhcpv4.attempts = 0U;
	NET_DBG("enter state=%s",
		net_dhcpv4_state_name(iface->config.dhcpv4.state));
}

static void dhcpv4_enter_rebinding(struct net_if *iface)
{
	iface->config.dhcpv4.state = NET_DHCPV4_REBINDING;
	iface->config.dhcpv4.attempts = 0U;
	NET_DBG("enter state=%s",
		net_dhcpv4_state_name(iface->config.dhcpv4.state));
}

static uint32_t dhcpv4_manage_timers(struct net_if *iface, int64_t now)
{
	uint32_t timeleft = dhcpv4_request_timeleft(iface, now);

	NET_DBG("iface %p dhcpv4=%p state=%s timeleft=%u", iface,
		&iface->config.dhcpv4,
		net_dhcpv4_state_name(iface->config.dhcpv4.state), timeleft);

	if (timeleft != 0U) {
		return timeleft;
	}

	if (!net_if_is_up(iface)) {
		/* An interface is down, the registered event handler will
		 * restart DHCP procedure when the interface is back up.
		 */
		return UINT32_MAX;
	}

	switch (iface->config.dhcpv4.state) {
	case NET_DHCPV4_DISABLED:
		break;
	case NET_DHCPV4_DECLINE:
		dhcpv4_send_decline(iface);
		__fallthrough;
	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) {
			dhcpv4_enter_renewing(iface);
			return dhcpv4_send_request(iface);
		}

		return timeleft;
	case NET_DHCPV4_RENEWING:
		timeleft = dhcpv4_rebinding_timeleft(iface, now);
		if (timeleft == 0U) {
			dhcpv4_enter_rebinding(iface);
		}

		return dhcpv4_send_request(iface);
	case NET_DHCPV4_REBINDING:
		timeleft = dhcpv4_lease_timeleft(iface, now);
		if (timeleft == 0U) {
			if (!net_if_ipv4_addr_rm(
					iface,
					&iface->config.dhcpv4.requested_ip)) {
				NET_DBG("Failed to remove addr from iface");
			}

			/* Lease time expired, so start from the beginning. */
			dhcpv4_enter_selecting(iface);
			return dhcpv4_send_discover(iface);
		}

		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));
	}
}

#if defined(CONFIG_NET_DHCPV4_OPTION_CALLBACKS_VENDOR_SPECIFIC)

static int dhcpv4_parse_option_vendor(struct net_pkt *pkt, struct net_if *iface,
				      enum net_dhcpv4_msg_type *msg_type, int length)
{
	struct net_dhcpv4_option_callback *cb, *tmp;
	struct net_pkt_cursor backup;
	uint8_t len;
	uint8_t type;

	if (length < 3) {
		NET_ERR("Vendor-specific option parsing, length too short");
		net_pkt_skip(pkt, length);
		return -EBADMSG;
	}

	while (!net_pkt_read_u8(pkt, &type)) {
		if (type == DHCPV4_OPTIONS_END) {
			NET_DBG("Vendor-specific options_end");
			return 0;
		}
		length--;

		if (length <= 0) {
			NET_ERR("Vendor-specific option parsing, malformed option");
			return -EBADMSG;
		}

		if (net_pkt_read_u8(pkt, &len)) {
			NET_ERR("Vendor-specific option parsing, bad length");
			return -ENOBUFS;
		}
		length--;
		if (length < len) {
			NET_ERR("Vendor-specific option parsing, length too long");
			net_pkt_skip(pkt, length);
			return -EBADMSG;
		}
		net_pkt_cursor_backup(pkt, &backup);

		SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&option_vendor_callbacks, cb, tmp, node) {
			if (cb->option == type) {
				NET_ASSERT(cb->handler, "No callback handler!");

				if (net_pkt_read(pkt, cb->data, MIN(cb->max_length, len))) {
					NET_DBG("option vendor callback, read err");
					return -ENOBUFS;
				}

				cb->handler(cb, len, *msg_type, iface);
				net_pkt_cursor_restore(pkt, &backup);
			}
		}
		net_pkt_skip(pkt, len);
		length = length - len;
		if (length <= 0) {
			NET_DBG("Vendor-specific options_end (no code 255)");
			return 0;
		}
	}
	return -ENOBUFS;
}

#endif /* CONFIG_NET_DHCPV4_OPTION_CALLBACKS_VENDOR_SPECIFIC */

/* Parse DHCPv4 options and retrieve relevant information
 * as per RFC 2132.
 */
static bool dhcpv4_parse_options(struct net_pkt *pkt,
				 struct net_if *iface,
				 enum net_dhcpv4_msg_type *msg_type)
{
#if defined(CONFIG_NET_DHCPV4_OPTION_CALLBACKS)
	struct net_dhcpv4_option_callback *cb, *tmp;
	struct net_pkt_cursor backup;
#endif
	uint8_t cookie[4];
	uint8_t length;
	uint8_t type;
	bool router_present = false;
	bool renewal_present = false;
	bool rebinding_present = false;
	bool unhandled = true;

	if (net_pkt_read(pkt, cookie, sizeof(cookie)) ||
	    memcmp(magic_cookie, cookie, sizeof(magic_cookie))) {
		NET_DBG("Incorrect magic cookie");
		return false;
	}

	while (!net_pkt_read_u8(pkt, &type)) {
		if (type == DHCPV4_OPTIONS_END) {
			NET_DBG("options_end");
			goto end;
		}

		if (net_pkt_read_u8(pkt, &length)) {
			NET_ERR("option parsing, bad length");
			return false;
		}


#if defined(CONFIG_NET_DHCPV4_OPTION_CALLBACKS)
		net_pkt_cursor_backup(pkt, &backup);
		unhandled = true;

		SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&option_callbacks,
						  cb, tmp, node) {
			if (cb->option == type) {
				NET_ASSERT(cb->handler, "No callback handler!");

				if (net_pkt_read(pkt, cb->data,
						 MIN(cb->max_length, length))) {
					NET_DBG("option callback, read err");
					return false;
				}

				cb->handler(cb, length, *msg_type, iface);
				unhandled = false;
			}
			net_pkt_cursor_restore(pkt, &backup);
		}
#endif /* CONFIG_NET_DHCPV4_OPTION_CALLBACKS */

		switch (type) {
		case DHCPV4_OPTIONS_SUBNET_MASK: {
			struct in_addr netmask;

			if (length != 4U) {
				NET_ERR("options_subnet_mask, bad length");
				return false;
			}

			if (net_pkt_read(pkt, netmask.s4_addr, length)) {
				NET_ERR("options_subnet_mask, short packet");
				return false;
			}

			iface->config.dhcpv4.netmask = 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_NET_DHCPV4_OPTION_DNS_ADDRESS)
#define MAX_DNS_SERVERS CONFIG_DNS_RESOLVER_MAX_SERVERS
		case DHCPV4_OPTIONS_DNS_SERVER: {
			struct dns_resolve_context *ctx;
			struct sockaddr_in dnses[MAX_DNS_SERVERS] = { 0 };
			const struct sockaddr *dns_servers[MAX_DNS_SERVERS + 1] = { 0 };
			const uint8_t addr_size = 4U;
			int status;

			for (uint8_t i = 0; i < MAX_DNS_SERVERS; i++) {
				dns_servers[i] = (struct sockaddr *)&dnses[i];
			}

			/* DNS server option may present 1 or more
			 * addresses. Each 4 bytes in length. DNS
			 * servers should be listed in order
			 * of preference. Hence how many we parse
			 * depends on CONFIG_DNS_RESOLVER_MAX_SERVERS
			 */
			if (length % addr_size != 0U) {
				NET_ERR("options_dns, bad length");
				return false;
			}

			const uint8_t provided_servers_cnt = length / addr_size;
			uint8_t dns_servers_cnt = 0;

			if (provided_servers_cnt > MAX_DNS_SERVERS) {
				NET_WARN("DHCP server provided more DNS servers than can be saved");
				dns_servers_cnt = MAX_DNS_SERVERS;
			} else {
				for (uint8_t i = provided_servers_cnt; i < MAX_DNS_SERVERS; i++) {
					dns_servers[i] = NULL;
				}

				dns_servers_cnt = provided_servers_cnt;
			}

			for (uint8_t i = 0; i < dns_servers_cnt; i++) {
				if (net_pkt_read(pkt, dnses[i].sin_addr.s4_addr, addr_size)) {
					NET_ERR("options_dns, short packet");
					return false;
				}
			}

			if (net_pkt_skip(pkt, length - dns_servers_cnt * addr_size)) {
				NET_ERR("options_dns, short packet");
				return false;
			}

			ctx = dns_resolve_get_default();
			for (uint8_t i = 0; i < dns_servers_cnt; i++) {
				dnses[i].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
#if defined(CONFIG_LOG_BACKEND_NET_USE_DHCPV4_OPTION)
		case DHCPV4_OPTIONS_LOG_SERVER: {
			struct sockaddr_in log_server = { 0 };

			/* Log server option may present 1 or more
			 * addresses. Each 4 bytes in length. Log
			 * servers should be listed in order
			 * of preference.  Hence we choose the first
			 * and skip the rest.
			 */
			if (length % 4 != 0U) {
				NET_ERR("options_log_server, bad length");
				return false;
			}

			if (net_pkt_read(pkt, log_server.sin_addr.s4_addr, 4) < 0 ||
			    net_pkt_skip(pkt, length - 4U) < 0) {
				NET_ERR("options_log_server, short packet");
				return false;
			}

			log_server.sin_family = AF_INET;
			log_backend_net_set_ip((struct sockaddr *)&log_server);

#ifdef CONFIG_LOG_BACKEND_NET_AUTOSTART
			log_backend_net_start();
#endif

			NET_DBG("options_log_server: %s", net_sprint_ipv4_addr(&log_server));

			break;
		}
#endif /* CONFIG_LOG_BACKEND_NET_USE_DHCPV4_OPTION */
#if defined(CONFIG_NET_DHCPV4_OPTION_NTP_SERVER)
		case DHCPV4_OPTIONS_NTP_SERVER: {

			/* NTP server option may present 1 or more
			 * addresses. Each 4 bytes in length. NTP
			 * servers should be listed in order
			 * of preference.  Hence we choose the first
			 * and skip the rest.
			 */
			if (length % 4 != 0U) {
				NET_ERR("options_log_server, bad length");
				return false;
			}

			if (net_pkt_read(pkt, iface->config.dhcpv4.ntp_addr.s4_addr, 4) < 0 ||
			    net_pkt_skip(pkt, length - 4U) < 0) {
				NET_ERR("options_ntp_server, short packet");
				return false;
			}

			NET_DBG("options_ntp_server: %s",
				net_sprint_ipv4_addr(&iface->config.dhcpv4.ntp_addr));

			break;
		}
#endif /* CONFIG_NET_DHCPV4_OPTION_NTP_SERVER */
#if defined(CONFIG_NET_DHCPV4_OPTION_CALLBACKS_VENDOR_SPECIFIC)
		case DHCPV4_OPTIONS_VENDOR_SPECIFIC: {
			if (!sys_slist_is_empty(&option_vendor_callbacks)) {
				NET_DBG("options_vendor_specific");
				if (dhcpv4_parse_option_vendor(pkt, iface, msg_type, length) ==
				    -ENOBUFS) {
					return false;
				}
			} else {
				NET_DBG("options_vendor_specific, no callbacks");
				if (net_pkt_skip(pkt, length)) {
					NET_DBG("options_vendor_specific, skip err");
					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);

			renewal_present = true;

			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);

			rebinding_present = true;

			break;
		case DHCPV4_OPTIONS_SERVER_ID:
			if (length != 4U) {
				NET_DBG("options_server_id, bad length");
				return false;
			}

			if (net_pkt_read(
				    pkt,
				    iface->config.dhcpv4.server_id.s4_addr,
				    length)) {
				NET_DBG("options_server_id, read err");
				return false;
			}

			NET_DBG("options_server_id: %s",
				net_sprint_ipv4_addr(&iface->config.dhcpv4.server_id));
			break;
		case DHCPV4_OPTIONS_MSG_TYPE: {
			if (length != 1U) {
				NET_DBG("options_msg_type, bad length");
				return false;
			}

			{
				uint8_t val = 0U;

				if (net_pkt_read_u8(pkt, &val)) {
					NET_DBG("options_msg_type, read err");
					return false;
				}
				*msg_type = val;
			}

			break;
		}
		default:
			if (unhandled) {
				NET_DBG("option unknown: %d", type);
			} else {
				NET_DBG("option unknown, handled by callback: %d", type);
			}

			if (net_pkt_skip(pkt, length)) {
				NET_DBG("option unknown, skip err");
				return false;
			}
			break;
		}
	}

	/* Invalid case: Options without DHCPV4_OPTIONS_END. */
	return false;

end:
	if (*msg_type == NET_DHCPV4_MSG_TYPE_OFFER && !router_present) {
		struct in_addr any = INADDR_ANY_INIT;

		net_if_ipv4_set_gw(iface, &any);
	}

	if (*msg_type == NET_DHCPV4_MSG_TYPE_ACK) {
		enum net_dhcpv4_state state = iface->config.dhcpv4.state;

		/* Clear Renew/Rebind times if not provided. They need to be
		 * recalculated accordingly.
		 */
		if (state == NET_DHCPV4_RENEWING || state == NET_DHCPV4_REBINDING) {
			if (!renewal_present) {
				iface->config.dhcpv4.renewal_time = 0U;
			}

			if (!rebinding_present) {
				iface->config.dhcpv4.rebinding_time = 0U;
			}
		}
	}

	return true;
}

static inline void dhcpv4_handle_msg_offer(struct net_if *iface,
					   struct dhcp_msg *msg)
{
	switch (iface->config.dhcpv4.state) {
	case NET_DHCPV4_DISABLED:
	case NET_DHCPV4_INIT:
	case NET_DHCPV4_REQUESTING:
	case NET_DHCPV4_RENEWING:
	case NET_DHCPV4_REBINDING:
	case NET_DHCPV4_BOUND:
	case NET_DHCPV4_DECLINE:
		break;
	case NET_DHCPV4_SELECTING:
		dhcpv4_enter_requesting(iface, msg);
		break;
	}
}

/* Must be invoked with lock held */
static void dhcpv4_handle_msg_ack(struct net_if *iface)
{
	switch (iface->config.dhcpv4.state) {
	case NET_DHCPV4_DISABLED:
	case NET_DHCPV4_INIT:
	case NET_DHCPV4_SELECTING:
	case NET_DHCPV4_BOUND:
	case NET_DHCPV4_DECLINE:
		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;
		}

		net_if_ipv4_set_netmask_by_addr(iface,
						&iface->config.dhcpv4.requested_ip,
						&iface->config.dhcpv4.netmask);

		dhcpv4_enter_bound(iface);
		break;

	case NET_DHCPV4_RENEWING:
	case NET_DHCPV4_REBINDING:
		/* TODO: If the renewal is success, update only
		 * vlifetime on iface.
		 */
		dhcpv4_enter_bound(iface);
		break;
	}
}

static void dhcpv4_handle_msg_nak(struct net_if *iface)
{
	switch (iface->config.dhcpv4.state) {
	case NET_DHCPV4_DISABLED:
	case NET_DHCPV4_INIT:
	case NET_DHCPV4_SELECTING:
	case NET_DHCPV4_REQUESTING:
		if (memcmp(&iface->config.dhcpv4.request_server_addr,
			   &iface->config.dhcpv4.response_src_addr,
			   sizeof(iface->config.dhcpv4.request_server_addr)) == 0) {
			LOG_DBG("NAK from requesting server %s, restart config",
				net_sprint_ipv4_addr(&iface->config.dhcpv4.request_server_addr));
			dhcpv4_enter_selecting(iface);
		} else {
			LOG_DBG("NAK from non-requesting server %s, ignore it",
				net_sprint_ipv4_addr(&iface->config.dhcpv4.response_src_addr));
		}
		break;
	case NET_DHCPV4_BOUND:
	case NET_DHCPV4_DECLINE:
		break;
	case NET_DHCPV4_RENEWING:
	case NET_DHCPV4_REBINDING:
		if (!net_if_ipv4_addr_rm(iface,
					 &iface->config.dhcpv4.requested_ip)) {
			NET_DBG("Failed to remove addr from iface");
		}

		/* Restart the configuration process. */
		dhcpv4_enter_selecting(iface);
		break;
	}
}

/* Takes and releases lock */
static void dhcpv4_handle_reply(struct net_if *iface,
				enum net_dhcpv4_msg_type msg_type,
				struct dhcp_msg *msg)
{
	NET_DBG("state=%s msg=%s",
		net_dhcpv4_state_name(iface->config.dhcpv4.state),
		net_dhcpv4_msg_type_name(msg_type));

	switch (msg_type) {
	case NET_DHCPV4_MSG_TYPE_OFFER:
		dhcpv4_handle_msg_offer(iface, msg);
		break;
	case NET_DHCPV4_MSG_TYPE_ACK:
		dhcpv4_handle_msg_ack(iface);
		break;
	case NET_DHCPV4_MSG_TYPE_NAK:
		dhcpv4_handle_msg_nak(iface);
		break;
	default:
		NET_DBG("ignore message");
		break;
	}
}

static enum net_verdict net_dhcpv4_input(struct net_conn *conn,
					 struct net_pkt *pkt,
					 union net_ip_header *ip_hdr,
					 union net_proto_header *proto_hdr,
					 void *user_data)
{
	NET_PKT_DATA_ACCESS_DEFINE(dhcp_access, struct dhcp_msg);
	enum net_verdict verdict = NET_DROP;
	enum net_dhcpv4_msg_type msg_type = 0;
	struct dhcp_msg *msg;
	struct net_if *iface;

	if (!conn) {
		NET_DBG("Invalid connection");
		return NET_DROP;
	}

	if (!pkt) {
		NET_DBG("Invalid packet");
		return NET_DROP;
	}

	iface = net_pkt_iface(pkt);
	if (!iface) {
		NET_DBG("no iface");
		return NET_DROP;
	}

	/* If the message is not DHCP then drop the packet. */
	if (net_pkt_get_len(pkt) < NET_IPV4UDPH_LEN + sizeof(struct dhcp_msg)) {
		NET_DBG("Input msg is not related to DHCPv4");
		return NET_DROP;

	}

	net_pkt_cursor_init(pkt);

	if (net_pkt_skip(pkt, NET_IPV4UDPH_LEN)) {
		return NET_DROP;
	}

	msg = (struct dhcp_msg *)net_pkt_get_data(pkt, &dhcp_access);
	if (!msg) {
		return NET_DROP;
	}

	NET_DBG("Received dhcp msg [op=0x%x htype=0x%x hlen=%u xid=0x%x "
		"secs=%u flags=0x%x chaddr=%s",
		msg->op, msg->htype, msg->hlen, ntohl(msg->xid),
		msg->secs, msg->flags,
		net_sprint_ll_addr(msg->chaddr, 6));
	NET_DBG("  ciaddr=%d.%d.%d.%d",
		msg->ciaddr[0], msg->ciaddr[1], msg->ciaddr[2], msg->ciaddr[3]);
	NET_DBG("  yiaddr=%d.%d.%d.%d",
		msg->yiaddr[0], msg->yiaddr[1], msg->yiaddr[2], msg->yiaddr[3]);
	NET_DBG("  siaddr=%d.%d.%d.%d",
		msg->siaddr[0], msg->siaddr[1], msg->siaddr[2], msg->siaddr[3]);
	NET_DBG("  giaddr=%d.%d.%d.%d]",
		msg->giaddr[0], msg->giaddr[1], msg->giaddr[2], msg->giaddr[3]);

	k_mutex_lock(&lock, K_FOREVER);

	if (!(msg->op == DHCPV4_MSG_BOOT_REPLY &&
	      iface->config.dhcpv4.xid == ntohl(msg->xid) &&
	      !memcmp(msg->chaddr, net_if_get_link_addr(iface)->addr,
		      net_if_get_link_addr(iface)->len))) {

		NET_DBG("Unexpected op (%d), xid (%x vs %x) or chaddr",
			msg->op, iface->config.dhcpv4.xid, ntohl(msg->xid));
		goto drop;
	}

	if (msg->hlen != net_if_get_link_addr(iface)->len) {
		NET_DBG("Unexpected hlen (%d)", msg->hlen);
		goto drop;
	}

	net_pkt_acknowledge_data(pkt, &dhcp_access);

	/* SNAME, FILE are not used at the moment, skip it */
	if (net_pkt_skip(pkt, SIZE_OF_SNAME + SIZE_OF_FILE)) {
		NET_DBG("short packet while skipping sname");
		goto drop;
	}

	if (!dhcpv4_parse_options(pkt, iface, &msg_type)) {
		goto drop;
	}

	memcpy(&iface->config.dhcpv4.response_src_addr, ip_hdr->ipv4->src,
		       sizeof(struct in_addr));

	dhcpv4_handle_reply(iface, msg_type, msg);

	net_pkt_unref(pkt);

	verdict = NET_OK;

drop:
	k_mutex_unlock(&lock);

	return verdict;
}

static void dhcpv4_iface_event_handler(struct net_mgmt_event_callback *cb,
				       uint32_t mgmt_event, struct net_if *iface)
{
	sys_snode_t *node = NULL;

	k_mutex_lock(&lock, K_FOREVER);

	SYS_SLIST_FOR_EACH_NODE(&dhcpv4_ifaces, node) {
		if (node == &iface->config.dhcpv4.node) {
			break;
		}
	}

	if (node == NULL) {
		goto out;
	}

	if (mgmt_event == NET_EVENT_IF_DOWN) {
		NET_DBG("Interface %p going down", iface);

		if (iface->config.dhcpv4.state == NET_DHCPV4_BOUND) {
			iface->config.dhcpv4.attempts = 0U;
			iface->config.dhcpv4.state = NET_DHCPV4_INIT;
			NET_DBG("enter state=%s", net_dhcpv4_state_name(
					iface->config.dhcpv4.state));
			/* Remove any bound address as interface is gone */
			if (!net_if_ipv4_addr_rm(iface, &iface->config.dhcpv4.requested_ip)) {
				NET_DBG("Failed to remove addr from iface");
			}
		}
	} 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);
}

#if defined(CONFIG_NET_IPV4_ACD)
static void dhcpv4_acd_event_handler(struct net_mgmt_event_callback *cb,
				     uint32_t mgmt_event, struct net_if *iface)
{
	sys_snode_t *node = NULL;
	struct in_addr *addr;


	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_IPV4_ACD_FAILED &&
	    mgmt_event != NET_EVENT_IPV4_ACD_CONFLICT) {
		goto out;
	}

	if (cb->info_length != sizeof(struct in_addr)) {
		goto out;
	}

	addr = (struct in_addr *)cb->info;

	if (!net_ipv4_addr_cmp(&iface->config.dhcpv4.requested_ip, addr)) {
		goto out;
	}

	if (mgmt_event == NET_EVENT_IPV4_ACD_CONFLICT) {
		/* Need to remove address explicitly in this case. */
		(void)net_if_ipv4_addr_rm(iface, &iface->config.dhcpv4.requested_ip);
	}

	NET_DBG("Conflict on DHCP assigned address %s, starting over",
		net_sprint_ipv4_addr(addr));

	iface->config.dhcpv4.state = NET_DHCPV4_DECLINE;
	dhcpv4_immediate_timeout(&iface->config.dhcpv4);

out:
	k_mutex_unlock(&lock);
}
#endif /* CONFIG_NET_IPV4_ACD */

const char *net_dhcpv4_state_name(enum net_dhcpv4_state state)
{
	static const char * const name[] = {
		"disabled",
		"init",
		"selecting",
		"requesting",
		"renewing",
		"rebinding",
		"bound",
		"decline,"
	};

	__ASSERT_NO_MSG(state >= 0 && state < sizeof(name));
	return name[state];
}

const char *net_dhcpv4_msg_type_name(enum net_dhcpv4_msg_type msg_type)
{
	static const char * const name[] = {
		"discover",
		"offer",
		"request",
		"decline",
		"ack",
		"nak",
		"release",
		"inform"
	};

	__ASSERT_NO_MSG(msg_type >= 1 && msg_type <= sizeof(name));
	return name[msg_type - 1];
}

static void dhcpv4_start_internal(struct net_if *iface, bool first_start)
{
	uint32_t entropy;
	uint32_t timeout = 0;

	net_mgmt_event_notify(NET_EVENT_IPV4_DHCP_START, iface);

	k_mutex_lock(&lock, K_FOREVER);

	switch (iface->config.dhcpv4.state) {
	case NET_DHCPV4_DISABLED:
		iface->config.dhcpv4.state = NET_DHCPV4_INIT;
		NET_DBG("iface %p state=%s", iface,
			net_dhcpv4_state_name(iface->config.dhcpv4.state));

		/* We need entropy for both an XID and a random delay
		 * before sending the initial discover message.
		 */
		entropy = sys_rand32_get();

		/* A DHCP client MUST choose xid's in such a way as to
		 * minimize the change of using and xid identical to
		 * one used by another client.  Choose a random xid st
		 * startup and increment it on each new request.
		 */
		iface->config.dhcpv4.xid = entropy;

		/* Use default */
		if (first_start) {
			/* RFC2131 4.1.1 requires we wait a random period
			 * between 1 and 10 seconds before sending the initial
			 * discover.
			 */
			timeout = entropy % (CONFIG_NET_DHCPV4_INITIAL_DELAY_MAX -
					DHCPV4_INITIAL_DELAY_MIN) + DHCPV4_INITIAL_DELAY_MIN;
		}

		NET_DBG("wait timeout=%us", timeout);

		if (sys_slist_is_empty(&dhcpv4_ifaces)) {
			net_mgmt_add_event_callback(&mgmt4_if_cb);
#if defined(CONFIG_NET_IPV4_ACD)
			net_mgmt_add_event_callback(&mgmt4_acd_cb);
#endif
		}

		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:
	case NET_DHCPV4_DECLINE:
		break;
	}

	k_mutex_unlock(&lock);
}

#if defined(CONFIG_NET_DHCPV4_OPTION_CALLBACKS)

int net_dhcpv4_add_option_callback(struct net_dhcpv4_option_callback *cb)
{
	if (cb == NULL || cb->handler == NULL) {
		return -EINVAL;
	}

	k_mutex_lock(&lock, K_FOREVER);
	sys_slist_prepend(&option_callbacks, &cb->node);
	dhcpv4_option_callback_count();
	k_mutex_unlock(&lock);
	return 0;
}

int net_dhcpv4_remove_option_callback(struct net_dhcpv4_option_callback *cb)
{
	int ret = 0;

	if (cb == NULL || cb->handler == NULL) {
		return -EINVAL;
	}

	k_mutex_lock(&lock, K_FOREVER);
	if (!sys_slist_find_and_remove(&option_callbacks, &cb->node)) {
		ret = -EINVAL;
	}
	dhcpv4_option_callback_count();
	k_mutex_unlock(&lock);
	return ret;
}

#endif /* CONFIG_NET_DHCPV4_OPTION_CALLBACKS */

#if defined(CONFIG_NET_DHCPV4_OPTION_CALLBACKS_VENDOR_SPECIFIC)

int net_dhcpv4_add_option_vendor_callback(struct net_dhcpv4_option_callback *cb)
{
	if (cb == NULL || cb->handler == NULL) {
		return -EINVAL;
	}

	k_mutex_lock(&lock, K_FOREVER);
	sys_slist_prepend(&option_vendor_callbacks, &cb->node);
	k_mutex_unlock(&lock);
	return 0;
}

int net_dhcpv4_remove_option_vendor_callback(struct net_dhcpv4_option_callback *cb)
{
	int ret = 0;

	if (cb == NULL || cb->handler == NULL) {
		return -EINVAL;
	}

	k_mutex_lock(&lock, K_FOREVER);
	if (!sys_slist_find_and_remove(&option_vendor_callbacks, &cb->node)) {
		ret = -EINVAL;
	}
	k_mutex_unlock(&lock);
	return ret;
}

#endif /* CONFIG_NET_DHCPV4_OPTION_CALLBACKS_VENDOR_SPECIFIC */

void net_dhcpv4_start(struct net_if *iface)
{
	dhcpv4_start_internal(iface, true);
}

void net_dhcpv4_stop(struct net_if *iface)
{
	k_mutex_lock(&lock, K_FOREVER);

	switch (iface->config.dhcpv4.state) {
	case NET_DHCPV4_DISABLED:
		break;

	case NET_DHCPV4_RENEWING:
	case NET_DHCPV4_BOUND:
		if (!net_if_ipv4_addr_rm(iface,
					 &iface->config.dhcpv4.requested_ip)) {
			NET_DBG("Failed to remove addr from iface");
		}

		__fallthrough;
	case NET_DHCPV4_INIT:
	case NET_DHCPV4_SELECTING:
	case NET_DHCPV4_REQUESTING:
	case NET_DHCPV4_REBINDING:
	case NET_DHCPV4_DECLINE:
		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_if_cb);
#if defined(CONFIG_NET_IPV4_ACD)
			net_mgmt_del_event_callback(&mgmt4_acd_cb);
#endif
		}

		break;
	}

	net_mgmt_event_notify(NET_EVENT_IPV4_DHCP_STOP, iface);

	k_mutex_unlock(&lock);
}

void net_dhcpv4_restart(struct net_if *iface)
{
	net_dhcpv4_stop(iface);
	dhcpv4_start_internal(iface, false);
}

int net_dhcpv4_init(void)
{
	struct sockaddr local_addr;
	int ret;

	NET_DBG("");

	net_ipaddr_copy(&net_sin(&local_addr)->sin_addr,
			net_ipv4_unspecified_address());
	local_addr.sa_family = AF_INET;

	/* Register UDP input callback on
	 * DHCPV4_SERVER_PORT(67) and DHCPV4_CLIENT_PORT(68) for
	 * all dhcpv4 related incoming packets.
	 */
	ret = net_udp_register(AF_INET, NULL, &local_addr,
			       DHCPV4_SERVER_PORT,
			       DHCPV4_CLIENT_PORT,
			       NULL, net_dhcpv4_input, NULL, NULL);
	if (ret < 0) {
		NET_DBG("UDP callback registration failed");
		return ret;
	}

	k_work_init_delayable(&timeout_work, dhcpv4_timeout);

	/* Catch network interface UP or DOWN events and renew the address
	 * if interface is coming back up again.
	 */
	net_mgmt_init_event_callback(&mgmt4_if_cb, dhcpv4_iface_event_handler,
				     NET_EVENT_IF_DOWN | NET_EVENT_IF_UP);
#if defined(CONFIG_NET_IPV4_ACD)
	net_mgmt_init_event_callback(&mgmt4_acd_cb, dhcpv4_acd_event_handler,
				     NET_EVENT_IPV4_ACD_FAILED |
				     NET_EVENT_IPV4_ACD_CONFLICT);
#endif

	return 0;
}

#if defined(CONFIG_NET_DHCPV4_ACCEPT_UNICAST)
bool net_dhcpv4_accept_unicast(struct net_pkt *pkt)
{
	NET_PKT_DATA_ACCESS_DEFINE(udp_access, struct net_udp_hdr);
	struct net_pkt_cursor backup;
	struct net_udp_hdr *udp_hdr;
	struct net_if *iface;
	bool accept = false;

	iface = net_pkt_iface(pkt);
	if (iface == NULL) {
		return false;
	}

	/* Only accept DHCPv4 packets during active query. */
	if (iface->config.dhcpv4.state != NET_DHCPV4_SELECTING &&
	    iface->config.dhcpv4.state != NET_DHCPV4_REQUESTING &&
	    iface->config.dhcpv4.state != NET_DHCPV4_RENEWING &&
	    iface->config.dhcpv4.state != NET_DHCPV4_REBINDING) {
		return false;
	}

	net_pkt_cursor_backup(pkt, &backup);
	net_pkt_skip(pkt, net_pkt_ip_hdr_len(pkt));

	/* Verify destination UDP port. */
	udp_hdr = (struct net_udp_hdr *)net_pkt_get_data(pkt, &udp_access);
	if (udp_hdr == NULL) {
		goto out;
	}

	if (udp_hdr->dst_port != htons(DHCPV4_CLIENT_PORT)) {
		goto out;
	}

	accept = true;

out:
	net_pkt_cursor_restore(pkt, &backup);

	return accept;
}
#endif /* CONFIG_NET_DHCPV4_ACCEPT_UNICAST */