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
