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

Reply via email to