[PATCH 2/2] test: dm: eth: add a DHCPv6 zero-length IA option test

Shahriyar Jalayeri <[email protected]>
Newsgroups gmane.comp.boot-loaders.u-boot
Message-ID <20260807-poc-dhcp6-ia-loop-v1-2-9d3fd3171c59__43124.9851522566$1786119191$gmane$org@byteray.co.uk>
Answer a DHCPv6 SOLICIT with an ADVERTISE whose IA_NA holds a zero-length
encapsulated sub-option and check the client parses past it and
re-solicits instead of stalling in the IA option parser.

Signed-off-by: Shahriyar Jalayeri <[email protected]>
---
 test/dm/eth.c | 120 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 120 insertions(+)

diff --git a/test/dm/eth.c b/test/dm/eth.c
index ed0b57d8861..4708f0a4676 100644
--- a/test/dm/eth.c
+++ b/test/dm/eth.c
@@ -621,6 +621,126 @@ static int dm_test_eth_async_ping_reply(struct unit_test_state *uts)
 	return 0;
 }
 DM_TEST(dm_test_eth_async_ping_reply, UTF_SCAN_FDT);
+
+#if IS_ENABLED(CONFIG_CMD_DHCP6) && IS_ENABLED(CONFIG_IPV6)
+static bool dhcp6_advertise_sent;
+static bool dhcp6_resolicited;
+
+/*
+ * Answer a DHCPv6 SOLICIT with an ADVERTISE whose IA_NA holds an encapsulated
+ * sub-option of length zero. A correct client bounds the sub-option, finds the
+ * ADVERTISE carries no SERVERID and re-solicits.
+ */
+static int sb_dhcp6_ia_option_handler(struct udevice *dev, void *packet,
+				      unsigned int len)
+{
+	struct eth_sandbox_priv *priv = dev_get_priv(dev);
+	struct ethernet_hdr *seth = packet;
+	struct ethernet_hdr *eth;
+	struct ip6_hdr *sip6, *ip6;
+	struct udp_hdr *sudp, *udp;
+	uchar *sdhcp6, *d, *opt, *rx;
+	int msglen;
+	u16 udptot;
+
+	if (ntohs(seth->et_protlen) != PROT_IP6)
+		return 0;
+	sip6 = (struct ip6_hdr *)((uchar *)packet + ETHER_HDR_SIZE);
+	if (sip6->nexthdr != IPPROTO_UDP)
+		return 0;
+	sudp = (struct udp_hdr *)((uchar *)sip6 + IP6_HDR_SIZE);
+	if (ntohs(sudp->udp_dst) != 547 || ntohs(sudp->udp_src) != 546)
+		return 0;
+	sdhcp6 = (uchar *)sudp + UDP_HDR_SIZE;
+
+	if (sdhcp6[0] != 1)		/* DHCP6_MSG_SOLICIT */
+		return 0;
+	if (dhcp6_advertise_sent) {
+		/* the client parsed the ADVERTISE and re-solicited */
+		dhcp6_resolicited = true;
+		net_set_state(NETLOOP_FAIL);
+		return 0;
+	}
+	dhcp6_advertise_sent = true;
+	if (priv->recv_packets >= PKTBUFSRX)
+		return 0;
+
+	rx = priv->recv_packet_buffer[priv->recv_packets];
+	memset(rx, 0, PKTSIZE);
+
+	eth = (struct ethernet_hdr *)rx;
+	memcpy(eth->et_dest, seth->et_src, ARP_HLEN);
+	memcpy(eth->et_src, priv->fake_host_hwaddr, ARP_HLEN);
+	eth->et_protlen = htons(PROT_IP6);
+
+	ip6 = (struct ip6_hdr *)(rx + ETHER_HDR_SIZE);
+	ip6->version = 6;
+	ip6->nexthdr = IPPROTO_UDP;
+	ip6->hop_limit = 255;
+	memcpy(&ip6->saddr, &sip6->daddr, sizeof(struct in6_addr));
+	memcpy(&ip6->daddr, &sip6->saddr, sizeof(struct in6_addr));
+
+	udp = (struct udp_hdr *)((uchar *)ip6 + IP6_HDR_SIZE);
+	udp->udp_src = htons(547);
+	udp->udp_dst = htons(546);
+
+	d = (uchar *)udp + UDP_HDR_SIZE;
+	opt = d;
+	/* dhcp6 header: reuse the SOLICIT trans_id, msg_type = ADVERTISE */
+	memcpy(opt, sdhcp6, 4);
+	opt[0] = 2;			/* DHCP6_MSG_ADVERTISE */
+	opt += 4;
+	/* CLIENTID copied from the SOLICIT (hdr 4 + DUID-LL 10) */
+	memcpy(opt, sdhcp6 + 4, 14);
+	opt += 14;
+	/*
+	 * IA_NA (option 3), data length 20: IA_ID/T1/T2 (12) plus one
+	 * encapsulated sub-option header (4) of length 0, plus 4 trailing bytes.
+	 * Copy the client's IA_ID from its SOLICIT so the option is accepted.
+	 */
+	opt[0] = 0; opt[1] = 3;
+	opt[2] = 0; opt[3] = 20;
+	memcpy(opt + 4, sdhcp6 + 4 + 14 + 6 + 4, 4);	/* IA_ID */
+	opt[16] = 0; opt[17] = 5;	/* sub-option DHCP6_OPTION_IAADDR */
+	opt[18] = 0; opt[19] = 0;	/* sub-option length 0 */
+	opt += 24;
+
+	msglen = opt - d;
+	udptot = UDP_HDR_SIZE + msglen;
+	ip6->payload_len = htons(udptot);
+	udp->udp_len = htons(udptot);
+	udp->udp_xsum = 0;
+	udp->udp_xsum = csum_ipv6_magic(&ip6->saddr, &ip6->daddr, udptot,
+					IPPROTO_UDP,
+					csum_partial((u8 *)udp, udptot, 0));
+
+	priv->recv_packet_length[priv->recv_packets] =
+		ETHER_HDR_SIZE + IP6_HDR_SIZE + udptot;
+	priv->recv_packets++;
+
+	return 0;
+}
+
+/* A zero-length IA sub-option must not stall the DHCPv6 option parser */
+static int dm_test_dhcp6_ia_zero_len_option(struct unit_test_state *uts)
+{
+	dhcp6_advertise_sent = false;
+	dhcp6_resolicited = false;
+	sandbox_eth_set_tx_handler(0, sb_dhcp6_ia_option_handler);
+	sandbox_eth_skip_timeout();
+
+	env_set("ethact", "eth@10002000");
+	net_loop(DHCP6);
+
+	sandbox_eth_set_tx_handler(0, NULL);
+
+	/* the client parsed past the zero-length sub-option and continued */
+	ut_assert(dhcp6_resolicited);
+
+	return 0;
+}
+DM_TEST(dm_test_dhcp6_ia_zero_len_option, UTF_SCAN_FDT);
+#endif
 #endif
 
 #if IS_ENABLED(CONFIG_IPV6_ROUTER_DISCOVERY)

-- 
2.43.0
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.