From f8818ca669fada5bcbe97f51c4dcde5ebfe69afc Mon Sep 17 00:00:00 2001 From: Robert Lubos Date: Mon, 24 Jul 2023 15:26:32 +0200 Subject: [PATCH] [nrf fromtree] net: dhcpv4: Accept unicast replies Some DHCPv4 servers do not respect BROADCAST flag set on DHCP Discover, replying with unicast packet, making it impossible to obtain DHCP address by Zephyr in such cases. RFC1542 chapter 3.1.1 makes the following statement about the BROADCAST flag: This addition to the protocol is a workaround for old host implementations. Such implementations SHOULD be modified so that they may receive unicast BOOTREPLY messages, thus making use of this workaround unnecessary. In general, the use of this mechanism is discouraged. Making it clear that being able to process unicast replies from the DHCP server is not only an optional behavior, but a recommended solution. Therefore, introduce a support for unicast DHCPv4 in Zephyr. To achieve this, add additional filtering rule at the IPv4 level - in case DHCPv4 is enabled, there is an active query and the packet is destined for the DHCPv4 module, let it through for the DHCPv4 module to process, regardless of the destination IP address. Signed-off-by: Robert Lubos (cherry picked from commit a22f7e777b01c40025e52971bbb13ce39d1d8e1e) --- subsys/net/ip/Kconfig.ipv4 | 8 +++++++ subsys/net/ip/dhcpv4.c | 47 +++++++++++++++++++++++++++++++++++++- subsys/net/ip/dhcpv4.h | 33 +++++++++++++++++++++++--- subsys/net/ip/ipv4.c | 4 +++- 4 files changed, 87 insertions(+), 5 deletions(-) diff --git a/subsys/net/ip/Kconfig.ipv4 b/subsys/net/ip/Kconfig.ipv4 index a16e025a948..a5e073051de 100644 --- a/subsys/net/ip/Kconfig.ipv4 +++ b/subsys/net/ip/Kconfig.ipv4 @@ -82,6 +82,14 @@ config NET_DHCPV4_OPTION_CALLBACKS can be added. These can be used to support otherwise DHCP options not used by the rest of the system. +config NET_DHCPV4_ACCEPT_UNICAST + bool "Accept unicast DHCPv4 traffic" + depends on NET_DHCPV4 + default y + help + If set, the network stack will accept unicast DHCPv4 responses from + servers, before the assigned address is configured on the interface. + config NET_IPV4_AUTO bool "IPv4 autoconfiguration [EXPERIMENTAL]" depends on NET_ARP diff --git a/subsys/net/ip/dhcpv4.c b/subsys/net/ip/dhcpv4.c index be01851bd89..d5aefb3ba90 100644 --- a/subsys/net/ip/dhcpv4.c +++ b/subsys/net/ip/dhcpv4.c @@ -211,7 +211,8 @@ static struct net_pkt *dhcpv4_create_message(struct net_if *iface, uint8_t type, msg->htype = HARDWARE_ETHERNET_TYPE; msg->hlen = net_if_get_link_addr(iface)->len; msg->xid = htonl(iface->config.dhcpv4.xid); - msg->flags = htons(DHCPV4_MSG_BROADCAST); + 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 @@ -1397,3 +1398,47 @@ int net_dhcpv4_init(void) #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 */ diff --git a/subsys/net/ip/dhcpv4.h b/subsys/net/ip/dhcpv4.h index 52663f7ee07..a274e3a6aea 100644 --- a/subsys/net/ip/dhcpv4.h +++ b/subsys/net/ip/dhcpv4.h @@ -81,9 +81,7 @@ struct dhcp_msg { /* TODO: - * 1) Support for UNICAST flag (some dhcpv4 servers will not reply if - * DISCOVER message contains BROADCAST FLAG). - * 2) Support T2(Rebind) timer. + * 1) Support T2(Rebind) timer. */ /* Maximum number of REQUEST or RENEWAL retransmits before reverting @@ -115,4 +113,33 @@ int net_dhcpv4_init(void); #endif /* CONFIG_NET_DHCPV4 */ +#if defined(CONFIG_NET_DHCPV4) && defined(CONFIG_NET_DHCPV4_ACCEPT_UNICAST) + +/** + * @brief Verify if the incoming packet should be accepted for the DHCPv4 + * module to process. + * + * In case server responds with an unicast IP packet, the IP stack needs to + * pass it through for the DHCPv4 module to process, before the actual + * destination IP address is configured on an interface. + * This function allows to determine whether there is an active DHCPv4 query on + * the interface and the packet is destined for the DHCPv4 module to process. + * + * @param pkt A packet to analyze + * + * @return true if the packet shall be accepted, false otherwise + */ +bool net_dhcpv4_accept_unicast(struct net_pkt *pkt); + +#else + +static inline bool net_dhcpv4_accept_unicast(struct net_pkt *pkt) +{ + ARG_UNUSED(pkt); + + return false; +} + +#endif /* CONFIG_NET_DHCPV4 && CONFIG_NET_DHCPV4_ACCEPT_UNICAST */ + #endif /* __INTERNAL_DHCPV4_H */ diff --git a/subsys/net/ip/ipv4.c b/subsys/net/ip/ipv4.c index 83827303f6e..922068274f7 100644 --- a/subsys/net/ip/ipv4.c +++ b/subsys/net/ip/ipv4.c @@ -23,6 +23,7 @@ LOG_MODULE_REGISTER(net_ipv4, CONFIG_NET_IPV4_LOG_LEVEL); #include "icmpv4.h" #include "udp_internal.h" #include "tcp_internal.h" +#include "dhcpv4.h" #include "ipv4.h" BUILD_ASSERT(sizeof(struct in_addr) == NET_IPV4_ADDR_SIZE); @@ -318,7 +319,8 @@ enum net_verdict net_ipv4_input(struct net_pkt *pkt) /* RFC 1122 ch. 3.3.6 The 0.0.0.0 is non-standard bcast addr */ (IS_ENABLED(CONFIG_NET_IPV4_ACCEPT_ZERO_BROADCAST) && net_ipv4_addr_cmp((struct in_addr *)hdr->dst, - net_ipv4_unspecified_address()))))) || + net_ipv4_unspecified_address())) || + net_dhcpv4_accept_unicast(pkt)))) || (hdr->proto == IPPROTO_TCP && net_ipv4_is_addr_bcast(net_pkt_iface(pkt), (struct in_addr *)hdr->dst))) { NET_DBG("DROP: not for me");