[PATCH] net: ipv6: Add support for default gateway discovery.

From: Ehsan Mohandesi emohandesi@microsoft.com
In IPv6, the default gateway and prefix length are determined by receiving a router advertisement as defined in - https://www.rfc-editor.org/rfc/rfc4861.
Add support for sending router solicitation (RS) and processing router advertisements (RA).
If the RA has prefix info option and following conditions are met, then gatewayip6 and net_prefix_length of ip6addr env variables are initialized. These are later consumed by IPv6 code for non-local destination IP.
- "Router Lifetime" != 0 - Prefix is NOT link-local prefix (0xfe80::/10) - L flag is 1 - "Valid Lifetime" != 0
Timing Parameters: - MAX_RTR_SOLICITATION_DELAY (0-1s) - RTR_SOLICITATION_INTERVAL (4s) (min retransmit delay) - MAX_RTR_SOLICITATIONS (3 RS transmissions)
The functionality is enabled by CONFIG_IPV6_ROUTER_DISCOVERY and invoked automatically from net_init_loop().
Signed-off-by: Ehsan Mohandesi emohandesi@microsoft.com
Conflicts: cmd/Kconfig include/net.h net/net.c --- cmd/Kconfig | 7 ++ include/ndisc.h | 23 ++++++ include/net.h | 2 +- include/net6.h | 40 ++++++++++ net/ndisc.c | 243 +++++++++++++++++++++++++++++++++++++++++++++++++++++--- net/net.c | 23 +++++- net/net6.c | 1 + 7 files changed, 327 insertions(+), 12 deletions(-)
diff --git a/cmd/Kconfig b/cmd/Kconfig index 2caa4af..c46613e 100644 --- a/cmd/Kconfig +++ b/cmd/Kconfig @@ -1894,6 +1894,13 @@ config CMD_NCSI Normally this happens automatically before other network operations.
+config IPV6_ROUTER_DISCOVERY + bool "Do router discovery" + depends on IPV6 + help + Will automatically perform router solicitation on first IPv6 + network operation + endif
config CMD_ETHSW diff --git a/include/ndisc.h b/include/ndisc.h index f6f8eb6..362d707 100644 --- a/include/ndisc.h +++ b/include/ndisc.h @@ -19,6 +19,20 @@ struct nd_msg { __u8 opt[0]; };
+/* struct rs_msg - ICMPv6 Router Solicitation message format */ +struct rs_msg { + struct icmp6hdr icmph; + __u8 opt[0]; +}; + +/* struct ra_msg - ICMPv6 Router Advertisement message format */ +struct ra_msg { + struct icmp6hdr icmph; + __u32 reachable_time; + __u32 retransmission_timer; + __u8 opt[0]; +}; + /* struct echo_msg - ICMPv6 echo request/reply message format */ struct echo_msg { struct icmp6hdr icmph; @@ -57,6 +71,11 @@ extern int net_nd_try; */ void ndisc_init(void);
+/* + * ip6_send_rs() - Send IPv6 Router Solicitation Message + */ +void ip6_send_rs(void); + /** * ndisc_receive() - Handle ND packet * @@ -97,6 +116,10 @@ static inline int ndisc_timeout_check(void) { return 0; } + +void ip6_send_rs(void) +{ +} #endif
#endif /* __NDISC_H__ */ diff --git a/include/net.h b/include/net.h index 399af5e..25c43b3 100644 --- a/include/net.h +++ b/include/net.h @@ -505,7 +505,7 @@ extern int net_restart_wrap; /* Tried all network devices */
enum proto_t { BOOTP, RARP, ARP, TFTPGET, DHCP, PING, PING6, DNS, NFS, CDP, NETCONS, - SNTP, TFTPSRV, TFTPPUT, LINKLOCAL, FASTBOOT, WOL, UDP, NCSI, WGET + SNTP, TFTPSRV, TFTPPUT, LINKLOCAL, FASTBOOT, WOL, UDP, NCSI, WGET, RS };
extern char net_boot_file_name[1024];/* Boot File name */ diff --git a/include/net6.h b/include/net6.h index 2d7c5a0..beafc05 100644 --- a/include/net6.h +++ b/include/net6.h @@ -81,8 +81,17 @@ struct udp_hdr { 0x00, 0x00, 0x00, 0x00, \ 0x00, 0x00, 0x00, 0x00, \ 0x00, 0x00, 0x00, 0x00 } } } +/* + * All-routers multicast address is the link-local scope address to reach all + * routers. + */ +#define ALL_ROUTERS_MULT_ADDR { { { 0xFF, 0x02, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x02 } } }
#define IPV6_LINK_LOCAL_PREFIX 0xfe80 +#define IPV6_LINK_LOCAL_MASK 0xffb0 /* The first 10-bit of address mask. */
/* hop limit for neighbour discovery packets */ #define IPV6_NDISC_HOPLIMIT 255 @@ -166,6 +175,37 @@ struct icmp6hdr { #define icmp6_rt_lifetime icmp6_dataun.u_nd_ra.rt_lifetime } __packed;
+/* + * struct icmp6_ra_prefix_info - Prefix Information option of the ICMPv6 message + * The Prefix Information option provides hosts with on-link prefixes and + * prefixes for Address Autoconfiguration. Refer to RFC 4861 for more info. + */ +struct icmp6_ra_prefix_info { + u8 type; /* Type is 3 for Prefix Information. */ + u8 len; /* Len is 4 for Prefix Information. */ + /* The number of leading bits in the Prefix that are valid. */ + u8 prefix_len; + u8 reserved1:6, /* MUST be ignored by the receiver. */ + aac:1, /* autonomous address-configuration flag */ + /* Indicates that this prefix can be used for on-link determination. */ + on_link:1; + /* + * The length of time in seconds that the prefix is valid for the + * purpose of on-link determination. + */ + __be32 valid_lifetime; + /* The length of time addresses remain preferred. */ + __be32 preferred_lifetime; + __be32 reserved2; /* MUST be ignored by the receiver. */ + /* + * Prefix is an IP address or a prefix of an IP address. The Prefix + * Length field contains the number of valid leading bits in the prefix. + * The bits in the prefix after the prefix length are reserved and MUST + * be initialized to zero by the sender and ignored by the receiver. + */ + struct in6_addr prefix; +}; + extern struct in6_addr const net_null_addr_ip6; /* NULL IPv6 address */ extern struct in6_addr net_gateway6; /* Our gateways IPv6 address */ extern struct in6_addr net_ip6; /* Our IPv6 addr (0 = unknown) */ diff --git a/net/ndisc.c b/net/ndisc.c index 367dae7..db76c4b 100644 --- a/net/ndisc.c +++ b/net/ndisc.c @@ -13,6 +13,8 @@ #include <net.h> #include <net6.h> #include <ndisc.h> +#include <stdlib.h> +#include <linux/delay.h>
/* IPv6 destination address of packet waiting for ND */ struct in6_addr net_nd_sol_packet_ip6 = ZERO_IPV6_ADDR; @@ -29,31 +31,37 @@ int net_nd_tx_packet_size; ulong net_nd_timer_start; /* the number of requests we have sent so far */ int net_nd_try; +struct in6_addr all_routers = ALL_ROUTERS_MULT_ADDR; + +#define MAX_RTR_SOLICITATIONS 3 +/* The maximum time to delay sending the first router solicitation message. */ +#define MAX_SOLICITATION_DELAY 1 // 1 second +/* The time to wait before sending the next router solicitation message. */ +#define RTR_SOLICITATION_INTERVAL 4000 // 4 seconds
#define IP6_NDISC_OPT_SPACE(len) (((len) + 2 + 7) & ~7)
/** * ndisc_insert_option() - Insert an option into a neighbor discovery packet * - * @ndisc: pointer to ND packet + * @opt: pointer to the option element of the neighbor discovery packet * @type: option type to insert * @data: option data to insert * @len: data length * Return: the number of bytes inserted (which may be >= len) */ -static int -ndisc_insert_option(struct nd_msg *ndisc, int type, u8 *data, int len) +static int ndisc_insert_option(__u8 *opt, int type, u8 *data, int len) { int space = IP6_NDISC_OPT_SPACE(len);
- ndisc->opt[0] = type; - ndisc->opt[1] = space >> 3; - memcpy(&ndisc->opt[2], data, len); + opt[0] = type; + opt[1] = space >> 3; + memcpy(&opt[2], data, len); len += 2;
/* fill the remainder with 0 */ if (space - len > 0) - memset(&ndisc->opt[len], '\0', space - len); + memset(&opt[len], '\0', space - len);
return space; } @@ -123,7 +131,7 @@ static void ip6_send_ns(struct in6_addr *neigh_addr)
/* Set the target address and llsaddr option */ net_copy_ip6(&msg->target, neigh_addr); - ndisc_insert_option(msg, ND_OPT_SOURCE_LL_ADDR, net_ethaddr, + ndisc_insert_option(msg->opt, ND_OPT_SOURCE_LL_ADDR, net_ethaddr, INETHADDRSZ);
/* checksum */ @@ -137,6 +145,76 @@ static void ip6_send_ns(struct in6_addr *neigh_addr) net_send_packet(net_tx_packet, (pkt - net_tx_packet)); }
+/* + * ip6_send_rs() - Send IPv6 Router Solicitation Message. + * + * A router solicitation is sent to discover a router. RS message creation is + * based on RFC 4861 section 4.1. Router Solicitation Message Format. + */ +void ip6_send_rs(void) +{ + unsigned char enetaddr[6]; + struct rs_msg *msg; + __u16 icmp_len; + uchar *pkt; + unsigned short csum; + unsigned int pcsum; + static unsigned int retry_count; + + if (!ip6_is_unspecified_addr(&net_gateway6) && + net_prefix_length != 0) { + net_set_state(NETLOOP_SUCCESS); + return; + } else if (retry_count >= MAX_RTR_SOLICITATIONS) { + net_set_state(NETLOOP_FAIL); + net_set_timeout_handler(0, 0); + retry_count = 0; + return; + } + + printf("ROUTER SOLICITATION %d\n", retry_count + 1); + + ip6_make_mult_ethdstaddr(enetaddr, &all_routers); + /* + * ICMP length is the size of ICMP header (8) + one option (8) = 16. + * The option is 2 bytes of type and length + 6 bytes for MAC. + */ + icmp_len = sizeof(struct icmp6hdr) + IP6_NDISC_OPT_SPACE(INETHADDRSZ); + + pkt = (uchar *)net_tx_packet; + pkt += net_set_ether(pkt, enetaddr, PROT_IP6); + pkt += ip6_add_hdr(pkt, &net_link_local_ip6, &all_routers, PROT_ICMPV6, + IPV6_NDISC_HOPLIMIT, icmp_len); + + /* ICMPv6 - RS */ + msg = (struct rs_msg *)pkt; + msg->icmph.icmp6_type = IPV6_NDISC_ROUTER_SOLICITATION; + msg->icmph.icmp6_code = 0; + memset(&msg->icmph.icmp6_cksum, 0, sizeof(__be16)); + memset(&msg->icmph.icmp6_unused, 0, sizeof(__be32)); + + /* Set the llsaddr option */ + ndisc_insert_option(msg->opt, ND_OPT_SOURCE_LL_ADDR, net_ethaddr, + INETHADDRSZ); + + /* checksum */ + pcsum = csum_partial((__u8 *)msg, icmp_len, 0); + csum = csum_ipv6_magic(&net_link_local_ip6, &all_routers, + icmp_len, PROT_ICMPV6, pcsum); + msg->icmph.icmp6_cksum = csum; + pkt += icmp_len; + + /* Wait up to 1 second if it is the first try to get the RA */ + if (retry_count == 0) + udelay(((unsigned int)rand() % 1000000) * MAX_SOLICITATION_DELAY); + + /* send it! */ + net_send_packet(net_tx_packet, (pkt - net_tx_packet)); + + retry_count++; + net_set_timeout_handler(RTR_SOLICITATION_INTERVAL, ip6_send_rs); +} + static void ip6_send_na(uchar *eth_dst_addr, struct in6_addr *neigh_addr, struct in6_addr *target) @@ -167,7 +245,7 @@ ip6_send_na(uchar *eth_dst_addr, struct in6_addr *neigh_addr, msg->icmph.icmp6_dataun.u_nd_advt.override = 1; /* Set the target address and lltargetaddr option */ net_copy_ip6(&msg->target, target); - ndisc_insert_option(msg, ND_OPT_TARGET_LL_ADDR, net_ethaddr, + ndisc_insert_option(msg->opt, ND_OPT_TARGET_LL_ADDR, net_ethaddr, INETHADDRSZ);
/* checksum */ @@ -223,6 +301,10 @@ int ndisc_timeout_check(void) return 1; }
+/* + * ndisc_init() - Make initial steps for ND state machine. + * Usually move variables into initial state. + */ void ndisc_init(void) { net_nd_packet_mac = NULL; @@ -234,12 +316,125 @@ void ndisc_init(void) net_nd_tx_packet -= (ulong)net_nd_tx_packet % PKTALIGN; }
+/* + * validate_ra() - Validate the router advertisement message. + * + * @ip6: Pointer to the router advertisement packet + * @len: Length of the router advertisement packet + * + * Check if the router advertisement message is valid. Conditions are + * according to RFC 4861 section 6.1.2. Validation of Router Advertisement + * Messages. + * + * Return: true if the message is valid and false if it is invalid. + */ +static bool validate_ra(struct ip6_hdr *ip6, int len) +{ + struct icmp6hdr *icmp = (struct icmp6hdr *)(ip6 + 1); + + /* ICMP length (derived from the IP length) should be 16 or more octets. */ + if (ip6->payload_len < 16) + return false; + + /* Source IP Address should be a valid link-local address. */ + if ((ntohs(ip6->saddr.s6_addr16[0]) & IPV6_LINK_LOCAL_MASK) != + IPV6_LINK_LOCAL_PREFIX) + return false; + + /* + * The IP Hop Limit field should have a value of 255, i.e., the packet + * could not possibly have been forwarded by a router. + */ + if (ip6->hop_limit != 255) + return false; + + /* ICMP checksum has already been checked in net_ip6_handler. */ + + if (icmp->icmp6_code != 0) + return false; + + return true; +} + +/* + * process_ra() - Process the router advertisement packet. + * + * @ip6: Pointer to the router advertisement packet + * @len: Length of the router advertisement packet + * + * Process the received router advertisement message. + * Although RFC 4861 requires retaining at least two router addresses, we only + * keep one because of the U-Boot limitations and its goal of lightweight code. + * + * Return: 0 - RA is a default router and contains valid prefix information. + * Non-zero - RA options are invalid or do not indicate it is a default router + * or do not contain valid prefix information. + */ +static int process_ra(struct ip6_hdr *ip6, int len) +{ + /* Pointer to the ICMP section of the packet */ + struct icmp6hdr *icmp = (struct icmp6hdr *)(ip6 + 1); + struct ra_msg *msg = (struct ra_msg *)icmp; + int remaining_option_len = len - IP6_HDR_SIZE - sizeof(struct ra_msg); + unsigned short int option_len; /* Length of each option */ + /* Pointer to the ICMPv6 message options */ + unsigned char *option = NULL; + /* 8-bit identifier of the type of ICMPv6 option */ + unsigned char type = 0; + struct icmp6_ra_prefix_info *prefix = NULL; + + /* Ignore the packet if router lifetime is 0. */ + if (!icmp->icmp6_rt_lifetime) + return -EOPNOTSUPP; + + /* Processing the options */ + option = msg->opt; + while (remaining_option_len > 0) { + /* The 2nd byte of the option is its length. */ + option_len = option[1]; + /* All included options should have a positive length. */ + if (option_len == 0) + return -EINVAL; + + type = option[0]; + /* All option types except Prefix Information are ignored. */ + switch (type) { + case ND_OPT_SOURCE_LL_ADDR: + case ND_OPT_TARGET_LL_ADDR: + case ND_OPT_REDIRECT_HDR: + case ND_OPT_MTU: + break; + case ND_OPT_PREFIX_INFO: + prefix = (struct icmp6_ra_prefix_info *)option; + /* The link-local prefix 0xfe80::/10 is ignored. */ + if ((ntohs(prefix->prefix.s6_addr16[0]) & + IPV6_LINK_LOCAL_MASK) == IPV6_LINK_LOCAL_PREFIX) + break; + if (prefix->on_link && ntohl(prefix->valid_lifetime)) { + net_prefix_length = prefix->prefix_len; + net_gateway6 = ip6->saddr; + return 0; + } + break; + default: + debug("Unknown IPv6 Neighbor Discovery Option 0x%x\n", + type); + } + + option_len <<= 3; /* Option length is a multiple of 8. */ + remaining_option_len -= option_len; + option += option_len; + } + return -EADDRNOTAVAIL; +} + int ndisc_receive(struct ethernet_hdr *et, struct ip6_hdr *ip6, int len) { struct icmp6hdr *icmp = (struct icmp6hdr *)(((uchar *)ip6) + IP6_HDR_SIZE); struct nd_msg *ndisc = (struct nd_msg *)icmp; uchar neigh_eth_addr[6]; + int err = 0; // The error code returned calling functions.
switch (icmp->icmp6_type) { case IPV6_NDISC_NEIGHBOUR_SOLICITATION: @@ -280,6 +475,36 @@ int ndisc_receive(struct ethernet_hdr *et, struct ip6_hdr *ip6, int len) net_nd_packet_mac = NULL; } break; + case IPV6_NDISC_ROUTER_SOLICITATION: + break; + case IPV6_NDISC_ROUTER_ADVERTISEMENT: + debug("Received router advertisement for %pI6c from %pI6c\n", + &ip6->daddr, &ip6->saddr); + /* + * If gateway and prefix are set, the RA packet is ignored. The + * reason is that the U-Boot code is supposed to be as compact + * as possible and does not need to take care of multiple + * routers. In addition to that, U-Boot does not want to handle + * scenarios like a router setting its lifetime to zero to + * indicate it is not routing anymore. U-Boot program has a + * short life when the system boots up and does not need such + * sophistication. + */ + if (!ip6_is_unspecified_addr(&net_gateway6) && + net_prefix_length != 0) { + break; + } + if (!validate_ra(ip6, len)) { + debug("Invalid router advertisement message.\n"); + break; + } + err = process_ra(ip6, len); + if (err) + debug("Ignored router advertisement. Error: %d\n", err); + else + printf("Set gatewayip6: %pI6c, prefix_length: %d\n", + &net_gateway6, net_prefix_length); + break; default: debug("Unexpected ICMPv6 type 0x%x\n", icmp->icmp6_type); return -1; diff --git a/net/net.c b/net/net.c index c9a749f..39f0b81 100644 --- a/net/net.c +++ b/net/net.c @@ -24,7 +24,7 @@ * - name of bootfile * Next step: ARP * - * LINK_LOCAL: + * LINKLOCAL: * * Prerequisites: - own ethernet address * We want: - own IP address @@ -122,6 +122,7 @@ #endif #include <net/tcp.h> #include <net/wget.h> +#include "net_rand.h"
/** BOOTP EXTENTIONS **/
@@ -346,6 +347,8 @@ void net_auto_load(void)
static int net_init_loop(void) { + static bool first_call = true; + if (eth_get_dev()) { memcpy(net_ethaddr, eth_get_ethaddr(), 6);
@@ -365,6 +368,12 @@ static int net_init_loop(void) */ return -ENONET;
+ if (IS_ENABLED(CONFIG_IPV6_ROUTER_DISCOVERY)) + if (first_call && use_ip6) { + first_call = false; + srand_mac(); /* This is for rand used in ip6_send_rs. */ + net_loop(RS); + } return 0; }
@@ -574,6 +583,10 @@ restart: ncsi_probe_packages(); break; #endif + case RS: + if (IS_ENABLED(CONFIG_IPV6_ROUTER_DISCOVERY)) + ip6_send_rs(); + break; default: break; } @@ -671,7 +684,13 @@ restart: x = time_handler; time_handler = (thand_f *)0; (*x)(); - } + } else if (IS_ENABLED(CONFIG_IPV6_ROUTER_DISCOVERY)) + if (time_handler && protocol == RS) + if (!ip6_is_unspecified_addr(&net_gateway6) && + net_prefix_length != 0) { + net_set_state(NETLOOP_SUCCESS); + net_set_timeout_handler(0, 0); + }
if (net_state == NETLOOP_FAIL) ret = net_start_again(); diff --git a/net/net6.c b/net/net6.c index 75577bc..2dd64c0 100644 --- a/net/net6.c +++ b/net/net6.c @@ -413,6 +413,7 @@ int net_ip6_handler(struct ethernet_hdr *et, struct ip6_hdr *ip6, int len) break; case IPV6_NDISC_NEIGHBOUR_SOLICITATION: case IPV6_NDISC_NEIGHBOUR_ADVERTISEMENT: + case IPV6_NDISC_ROUTER_ADVERTISEMENT: ndisc_receive(et, ip6, len); break; default:

On Thu, 2023-03-02 at 08:58 -0800, emohandesi@linux.microsoft.com wrote:
From: Ehsan Mohandesi emohandesi@microsoft.com
In IPv6, the default gateway and prefix length are determined by receiving a router advertisement as defined in - https://www.rfc-editor.org/rfc/rfc4861.
Add support for sending router solicitation (RS) and processing router advertisements (RA).
If the RA has prefix info option and following conditions are met, then gatewayip6 and net_prefix_length of ip6addr env variables are initialized. These are later consumed by IPv6 code for non-local destination IP.
- "Router Lifetime" != 0
- Prefix is NOT link-local prefix (0xfe80::/10)
- L flag is 1
- "Valid Lifetime" != 0
Timing Parameters:
- MAX_RTR_SOLICITATION_DELAY (0-1s)
- RTR_SOLICITATION_INTERVAL (4s) (min retransmit delay)
- MAX_RTR_SOLICITATIONS (3 RS transmissions)
The functionality is enabled by CONFIG_IPV6_ROUTER_DISCOVERY and invoked automatically from net_init_loop().
Signed-off-by: Ehsan Mohandesi emohandesi@microsoft.com
Conflicts: cmd/Kconfig include/net.h net/net.c
cmd/Kconfig | 7 ++ include/ndisc.h | 23 ++++++ include/net.h | 2 +- include/net6.h | 40 ++++++++++ net/ndisc.c | 243 +++++++++++++++++++++++++++++++++++++++++++++++++++++--- net/net.c | 23 +++++- net/net6.c | 1 + 7 files changed, 327 insertions(+), 12 deletions(-)
I reviewed this patch and it looks good. I have no critical remarks, only some small notes.
I've tested it on SiFive Unmatched board.
+config IPV6_ROUTER_DISCOVERY
bool "Do router discovery"
depends on IPV6
help
Will automatically perform router solicitation on first
IPv6
network operation
endif
I think it is better to write sth like Do IPv6 router discovery because IPv4 has also router discovery protocol and it could lead to misunderstanding
net_set_timeout_handler(0, 0);
Maybe net_set_timeout_handler(0, NULL); is better
+/*
- validate_ra() - Validate the router advertisement message.
- @ip6:
- @len: Length of the router advertisement packet
- Check if the router advertisement message is valid. Conditions
are
- according to RFC 4861 section 6.1.2. Validation of Router
Advertisement
- Messages.
- Return: true if the message is valid and false if it is invalid.
- */
+static bool validate_ra(struct ip6_hdr *ip6, int len) +{
struct icmp6hdr *icmp = (struct icmp6hdr *)(ip6 + 1);
/* ICMP length (derived from the IP length) should be 16 or
more octets. */
if (ip6->payload_len < 16)
return false;
/* Source IP Address should be a valid link-local address. */
if ((ntohs(ip6->saddr.s6_addr16[0]) & IPV6_LINK_LOCAL_MASK)
!=
IPV6_LINK_LOCAL_PREFIX)
return false;
/*
* The IP Hop Limit field should have a value of 255, i.e.,
the packet
* could not possibly have been forwarded by a router.
*/
if (ip6->hop_limit != 255)
return false;
Unicast hop limit only?
diff --git a/net/net.c b/net/net.c index c9a749f..39f0b81 100644 --- a/net/net.c +++ b/net/net.c @@ -24,7 +24,7 @@
- name of bootfile
Next step: ARP
- LINK_LOCAL:
- LINKLOCAL:
Maybe it is better to move to other patch?!
Reviewed-by: Viacheslav Mitrofanov v.v.mitrofanov@yadro.com Tested-by: Viacheslav Mitrofanov v.v.mitrofanov@yadro.com

Hi Viacheslav,
-----Original Message----- From: Vyacheslav V. Mitrofanov v.v.mitrofanov@yadro.com Sent: Thursday, March 16, 2023 3:47 AM To: u-boot@lists.denx.de; emohandesi@linux.microsoft.com Cc: joe.hershberger@ni.com; xypron.glpk@gmx.de; dphadke@linux.microsoft.com; saproj@gmail.com; rfried.dev@gmail.com; ilias.apalodimas@linaro.org; Ehsan Mohandesi emohandesi@microsoft.com; john@metanate.com; sjg@chromium.org; masahisa.kojima@linaro.org Subject: [EXTERNAL] Re: [PATCH] net: ipv6: Add support for default gateway discovery.
On Thu, 2023-03-02 at 08:58 -0800, emohandesi@linux.microsoft.com wrote:
From: Ehsan Mohandesi emohandesi@microsoft.com
In IPv6, the default gateway and prefix length are determined by receiving a router advertisement as defined in -
https://www.rf/ c- editor.org%2Frfc%2Frfc4861&data=05%7C01%7Cemohandesi%40microsoft.co m%7C6dec635abc8c4861feb708db25fb05d6%7C72f988bf86f141af91ab2d7cd01 1db47%7C1%7C0%7C638145532341238481%7CUnknown%7CTWFpbGZsb3d8ey JWIjoiMC4wLjAwMDAiLCJQIjoiV2luMzIiLCJBTiI6Ik1haWwiLCJXVCI6Mn0%3D%7C 3000%7C%7C%7C&sdata=tAhREBvBgVQKOFqEQT2%2FKphGxYXUMo3UF5vvQpY B%2Be0%3D&reserved=0.
Add support for sending router solicitation (RS) and processing router advertisements (RA).
If the RA has prefix info option and following conditions are met, then gatewayip6 and net_prefix_length of ip6addr env variables are initialized. These are later consumed by IPv6 code for non-local destination IP.
- "Router Lifetime" != 0
- Prefix is NOT link-local prefix (0xfe80::/10)
- L flag is 1
- "Valid Lifetime" != 0
Timing Parameters:
- MAX_RTR_SOLICITATION_DELAY (0-1s)
- RTR_SOLICITATION_INTERVAL (4s) (min retransmit delay)
- MAX_RTR_SOLICITATIONS (3 RS transmissions)
The functionality is enabled by CONFIG_IPV6_ROUTER_DISCOVERY and invoked automatically from net_init_loop().
Signed-off-by: Ehsan Mohandesi emohandesi@microsoft.com
Conflicts: cmd/Kconfig include/net.h net/net.c
cmd/Kconfig | 7 ++ include/ndisc.h | 23 ++++++ include/net.h | 2 +- include/net6.h | 40 ++++++++++ net/ndisc.c | 243 +++++++++++++++++++++++++++++++++++++++++++++++++++++--- net/net.c | 23 +++++- net/net6.c | 1 + 7 files changed, 327 insertions(+), 12 deletions(-)
I reviewed this patch and it looks good. I have no critical remarks, only some small notes.
I've tested it on SiFive Unmatched board.
+config IPV6_ROUTER_DISCOVERY
bool "Do router discovery"
depends on IPV6
help
Will automatically perform router solicitation on first
IPv6
network operation
endif
I think it is better to write sth like Do IPv6 router discovery because IPv4 has also router discovery protocol and it could lead to misunderstanding
net_set_timeout_handler(0, 0);
Maybe net_set_timeout_handler(0, NULL); is better
+/*
- validate_ra() - Validate the router advertisement message.
- @ip6:
- @len: Length of the router advertisement packet
- Check if the router advertisement message is valid. Conditions
are
- according to RFC 4861 section 6.1.2. Validation of Router
Advertisement
- Messages.
- Return: true if the message is valid and false if it is invalid.
- */
+static bool validate_ra(struct ip6_hdr *ip6, int len) {
struct icmp6hdr *icmp = (struct icmp6hdr *)(ip6 + 1);
/* ICMP length (derived from the IP length) should be 16 or
more octets. */
if (ip6->payload_len < 16)
return false;
/* Source IP Address should be a valid link-local address. */
if ((ntohs(ip6->saddr.s6_addr16[0]) & IPV6_LINK_LOCAL_MASK)
!=
IPV6_LINK_LOCAL_PREFIX)
return false;
/*
* The IP Hop Limit field should have a value of 255, i.e.,
the packet
* could not possibly have been forwarded by a router.
*/
if (ip6->hop_limit != 255)
return false;
Unicast hop limit only?
Sorry, I do not understand what you mean here. What kind of scenario are you talking about? It does not matter if the router advertisement is unicast or multicast. In both cases, the hop limit needs to be 255. A router always sets the hop limit to 255. We do not want an advertisement that is forwarded from another node. Refer to this for more information. https://www.rfc-editor.org/rfc/rfc4861#section-6.1.2
diff --git a/net/net.c b/net/net.c index c9a749f..39f0b81 100644 --- a/net/net.c +++ b/net/net.c @@ -24,7 +24,7 @@
- name of bootfile
Next step: ARP
- LINK_LOCAL:
- LINKLOCAL:
Maybe it is better to move to other patch?!
The underscore change on this line is a needed change. LINKLOCAL is used this way in the code. In the comment, it has an extra _ in it. It causes confusion. An important feature of Linux coding is being able to grep for strings. When I grepped for LINK_LOCAL I did not find what I was looking for. If there are more important reasons not to make this change, please let me know to revert it. I did not directly change any LINKLOCAL code, but I needed it for writing my code. It could happen to anyone. I think it is better to fix it here and save everyone's time now.
Reviewed-by: Viacheslav Mitrofanov v.v.mitrofanov@yadro.com Tested-by: Viacheslav Mitrofanov v.v.mitrofanov@yadro.com

On Thu, 2023-03-23 at 16:44 +0000, Ehsan Mohandesi wrote:
Hi Viacheslav,
-----Original Message----- From: Vyacheslav V. Mitrofanov v.v.mitrofanov@yadro.com Sent: Thursday, March 16, 2023 3:47 AM To: u-boot@lists.denx.de; emohandesi@linux.microsoft.com Cc: joe.hershberger@ni.com; xypron.glpk@gmx.de; dphadke@linux.microsoft.com; saproj@gmail.com; rfried.dev@gmail.com ; ilias.apalodimas@linaro.org; Ehsan Mohandesi < emohandesi@microsoft.com>; john@metanate.com; sjg@chromium.org; masahisa.kojima@linaro.org Subject: [EXTERNAL] Re: [PATCH] net: ipv6: Add support for default gateway discovery.
On Thu, 2023-03-02 at 08:58 -0800, emohandesi@linux.microsoft.com wrote:
From: Ehsan Mohandesi emohandesi@microsoft.com
In IPv6, the default gateway and prefix length are determined by receiving a router advertisement as defined in -
https://www.rf/ c- editor.org%2Frfc%2Frfc4861&data=05%7C01%7Cemohandesi%40microsoft.co m%7C6dec635abc8c4861feb708db25fb05d6%7C72f988bf86f141af91ab2d7cd01 1db47%7C1%7C0%7C638145532341238481%7CUnknown%7CTWFpbGZsb3d8ey JWIjoiMC4wLjAwMDAiLCJQIjoiV2luMzIiLCJBTiI6Ik1haWwiLCJXVCI6Mn0%3D%7C 3000%7C%7C%7C&sdata=tAhREBvBgVQKOFqEQT2%2FKphGxYXUMo3UF5vvQpY B%2Be0%3D&reserved=0.
Add support for sending router solicitation (RS) and processing router advertisements (RA).
If the RA has prefix info option and following conditions are met, then gatewayip6 and net_prefix_length of ip6addr env variables are initialized. These are later consumed by IPv6 code for non-local destination IP.
- "Router Lifetime" != 0
- Prefix is NOT link-local prefix (0xfe80::/10)
- L flag is 1
- "Valid Lifetime" != 0
Timing Parameters:
- MAX_RTR_SOLICITATION_DELAY (0-1s)
- RTR_SOLICITATION_INTERVAL (4s) (min retransmit delay)
- MAX_RTR_SOLICITATIONS (3 RS transmissions)
The functionality is enabled by CONFIG_IPV6_ROUTER_DISCOVERY and invoked automatically from net_init_loop().
Signed-off-by: Ehsan Mohandesi emohandesi@microsoft.com
Conflicts: cmd/Kconfig include/net.h net/net.c
cmd/Kconfig | 7 ++ include/ndisc.h | 23 ++++++ include/net.h | 2 +- include/net6.h | 40 ++++++++++ net/ndisc.c | 243 +++++++++++++++++++++++++++++++++++++++++++++++++++++--- net/net.c | 23 +++++- net/net6.c | 1 + 7 files changed, 327 insertions(+), 12 deletions(-)
I reviewed this patch and it looks good. I have no critical remarks, only some small notes.
I've tested it on SiFive Unmatched board.
+config IPV6_ROUTER_DISCOVERY
bool "Do router discovery"
depends on IPV6
help
Will automatically perform router solicitation on first
IPv6
network operation
endif
I think it is better to write sth like Do IPv6 router discovery because IPv4 has also router discovery protocol and it could lead to misunderstanding
net_set_timeout_handler(0, 0);
Maybe net_set_timeout_handler(0, NULL); is better
+/*
- validate_ra() - Validate the router advertisement message.
- @ip6:
- @len: Length of the router advertisement packet
- Check if the router advertisement message is valid.
Conditions are
- according to RFC 4861 section 6.1.2. Validation of Router
Advertisement
- Messages.
- Return: true if the message is valid and false if it is
invalid.
- */
+static bool validate_ra(struct ip6_hdr *ip6, int len) {
struct icmp6hdr *icmp = (struct icmp6hdr *)(ip6 + 1);
/* ICMP length (derived from the IP length) should be 16
or more octets. */
if (ip6->payload_len < 16)
return false;
/* Source IP Address should be a valid link-local
address. */
if ((ntohs(ip6->saddr.s6_addr16[0]) &
IPV6_LINK_LOCAL_MASK) !=
IPV6_LINK_LOCAL_PREFIX)
return false;
/*
* The IP Hop Limit field should have a value of 255,
i.e., the packet
* could not possibly have been forwarded by a router.
*/
if (ip6->hop_limit != 255)
return false;
Unicast hop limit only?
Sorry, I do not understand what you mean here. What kind of scenario are you talking about? It does not matter if the router advertisement is unicast or multicast. In both cases, the hop limit needs to be 255. A router always sets the hop limit to 255. We do not want an advertisement that is forwarded from another node. Refer to this for more information. https://www.rfc-editor.org/rfc/rfc4861#section-6.1.2
Yes, I got you. I have no objections. Thanks!

On Thu, Mar 2, 2023 at 6:58 PM emohandesi@linux.microsoft.com wrote:
From: Ehsan Mohandesi emohandesi@microsoft.com
In IPv6, the default gateway and prefix length are determined by receiving a router advertisement as defined in - https://www.rfc-editor.org/rfc/rfc4861.
Add support for sending router solicitation (RS) and processing router advertisements (RA).
If the RA has prefix info option and following conditions are met, then gatewayip6 and net_prefix_length of ip6addr env variables are initialized. These are later consumed by IPv6 code for non-local destination IP.
- "Router Lifetime" != 0
- Prefix is NOT link-local prefix (0xfe80::/10)
- L flag is 1
- "Valid Lifetime" != 0
Timing Parameters:
- MAX_RTR_SOLICITATION_DELAY (0-1s)
- RTR_SOLICITATION_INTERVAL (4s) (min retransmit delay)
- MAX_RTR_SOLICITATIONS (3 RS transmissions)
The functionality is enabled by CONFIG_IPV6_ROUTER_DISCOVERY and invoked automatically from net_init_loop().
Signed-off-by: Ehsan Mohandesi emohandesi@microsoft.com
Conflicts: cmd/Kconfig include/net.h net/net.c
cmd/Kconfig | 7 ++ include/ndisc.h | 23 ++++++ include/net.h | 2 +- include/net6.h | 40 ++++++++++ net/ndisc.c | 243 +++++++++++++++++++++++++++++++++++++++++++++++++++++--- net/net.c | 23 +++++- net/net6.c | 1 + 7 files changed, 327 insertions(+), 12 deletions(-)
diff --git a/cmd/Kconfig b/cmd/Kconfig index 2caa4af..c46613e 100644 --- a/cmd/Kconfig +++ b/cmd/Kconfig @@ -1894,6 +1894,13 @@ config CMD_NCSI Normally this happens automatically before other network operations.
+config IPV6_ROUTER_DISCOVERY
bool "Do router discovery"
depends on IPV6
help
Will automatically perform router solicitation on first IPv6
network operation
endif
config CMD_ETHSW diff --git a/include/ndisc.h b/include/ndisc.h index f6f8eb6..362d707 100644 --- a/include/ndisc.h +++ b/include/ndisc.h @@ -19,6 +19,20 @@ struct nd_msg { __u8 opt[0]; };
+/* struct rs_msg - ICMPv6 Router Solicitation message format */ +struct rs_msg {
struct icmp6hdr icmph;
__u8 opt[0];
+};
+/* struct ra_msg - ICMPv6 Router Advertisement message format */ +struct ra_msg {
struct icmp6hdr icmph;
__u32 reachable_time;
__u32 retransmission_timer;
__u8 opt[0];
+};
/* struct echo_msg - ICMPv6 echo request/reply message format */ struct echo_msg { struct icmp6hdr icmph; @@ -57,6 +71,11 @@ extern int net_nd_try; */ void ndisc_init(void);
+/*
- ip6_send_rs() - Send IPv6 Router Solicitation Message
- */
+void ip6_send_rs(void);
/**
- ndisc_receive() - Handle ND packet
@@ -97,6 +116,10 @@ static inline int ndisc_timeout_check(void) { return 0; }
+void ip6_send_rs(void) +{ +} #endif
#endif /* __NDISC_H__ */ diff --git a/include/net.h b/include/net.h index 399af5e..25c43b3 100644 --- a/include/net.h +++ b/include/net.h @@ -505,7 +505,7 @@ extern int net_restart_wrap; /* Tried all network devices */
enum proto_t { BOOTP, RARP, ARP, TFTPGET, DHCP, PING, PING6, DNS, NFS, CDP, NETCONS,
SNTP, TFTPSRV, TFTPPUT, LINKLOCAL, FASTBOOT, WOL, UDP, NCSI, WGET
SNTP, TFTPSRV, TFTPPUT, LINKLOCAL, FASTBOOT, WOL, UDP, NCSI, WGET, RS
};
extern char net_boot_file_name[1024];/* Boot File name */ diff --git a/include/net6.h b/include/net6.h index 2d7c5a0..beafc05 100644 --- a/include/net6.h +++ b/include/net6.h @@ -81,8 +81,17 @@ struct udp_hdr { 0x00, 0x00, 0x00, 0x00, \ 0x00, 0x00, 0x00, 0x00, \ 0x00, 0x00, 0x00, 0x00 } } } +/*
- All-routers multicast address is the link-local scope address to reach all
- routers.
- */
+#define ALL_ROUTERS_MULT_ADDR { { { 0xFF, 0x02, 0x00, 0x00, \
0x00, 0x00, 0x00, 0x00, \
0x00, 0x00, 0x00, 0x00, \
0x00, 0x00, 0x00, 0x02 } } }
#define IPV6_LINK_LOCAL_PREFIX 0xfe80 +#define IPV6_LINK_LOCAL_MASK 0xffb0 /* The first 10-bit of address mask. */
/* hop limit for neighbour discovery packets */ #define IPV6_NDISC_HOPLIMIT 255 @@ -166,6 +175,37 @@ struct icmp6hdr { #define icmp6_rt_lifetime icmp6_dataun.u_nd_ra.rt_lifetime } __packed;
+/*
- struct icmp6_ra_prefix_info - Prefix Information option of the ICMPv6 message
- The Prefix Information option provides hosts with on-link prefixes and
- prefixes for Address Autoconfiguration. Refer to RFC 4861 for more info.
- */
+struct icmp6_ra_prefix_info {
u8 type; /* Type is 3 for Prefix Information. */
u8 len; /* Len is 4 for Prefix Information. */
/* The number of leading bits in the Prefix that are valid. */
u8 prefix_len;
u8 reserved1:6, /* MUST be ignored by the receiver. */
aac:1, /* autonomous address-configuration flag */
/* Indicates that this prefix can be used for on-link determination. */
on_link:1;
/*
* The length of time in seconds that the prefix is valid for the
* purpose of on-link determination.
*/
__be32 valid_lifetime;
/* The length of time addresses remain preferred. */
__be32 preferred_lifetime;
__be32 reserved2; /* MUST be ignored by the receiver. */
/*
* Prefix is an IP address or a prefix of an IP address. The Prefix
* Length field contains the number of valid leading bits in the prefix.
* The bits in the prefix after the prefix length are reserved and MUST
* be initialized to zero by the sender and ignored by the receiver.
*/
struct in6_addr prefix;
+};
extern struct in6_addr const net_null_addr_ip6; /* NULL IPv6 address */ extern struct in6_addr net_gateway6; /* Our gateways IPv6 address */ extern struct in6_addr net_ip6; /* Our IPv6 addr (0 = unknown) */ diff --git a/net/ndisc.c b/net/ndisc.c index 367dae7..db76c4b 100644 --- a/net/ndisc.c +++ b/net/ndisc.c @@ -13,6 +13,8 @@ #include <net.h> #include <net6.h> #include <ndisc.h> +#include <stdlib.h> +#include <linux/delay.h>
/* IPv6 destination address of packet waiting for ND */ struct in6_addr net_nd_sol_packet_ip6 = ZERO_IPV6_ADDR; @@ -29,31 +31,37 @@ int net_nd_tx_packet_size; ulong net_nd_timer_start; /* the number of requests we have sent so far */ int net_nd_try; +struct in6_addr all_routers = ALL_ROUTERS_MULT_ADDR;
+#define MAX_RTR_SOLICITATIONS 3 +/* The maximum time to delay sending the first router solicitation message. */ +#define MAX_SOLICITATION_DELAY 1 // 1 second +/* The time to wait before sending the next router solicitation message. */ +#define RTR_SOLICITATION_INTERVAL 4000 // 4 seconds
#define IP6_NDISC_OPT_SPACE(len) (((len) + 2 + 7) & ~7)
/**
- ndisc_insert_option() - Insert an option into a neighbor discovery packet
- @ndisc: pointer to ND packet
*/
- @opt: pointer to the option element of the neighbor discovery packet
- @type: option type to insert
- @data: option data to insert
- @len: data length
- Return: the number of bytes inserted (which may be >= len)
-static int -ndisc_insert_option(struct nd_msg *ndisc, int type, u8 *data, int len) +static int ndisc_insert_option(__u8 *opt, int type, u8 *data, int len) { int space = IP6_NDISC_OPT_SPACE(len);
ndisc->opt[0] = type;
ndisc->opt[1] = space >> 3;
memcpy(&ndisc->opt[2], data, len);
opt[0] = type;
opt[1] = space >> 3;
memcpy(&opt[2], data, len); len += 2; /* fill the remainder with 0 */ if (space - len > 0)
memset(&ndisc->opt[len], '\0', space - len);
memset(&opt[len], '\0', space - len); return space;
} @@ -123,7 +131,7 @@ static void ip6_send_ns(struct in6_addr *neigh_addr)
/* Set the target address and llsaddr option */ net_copy_ip6(&msg->target, neigh_addr);
ndisc_insert_option(msg, ND_OPT_SOURCE_LL_ADDR, net_ethaddr,
ndisc_insert_option(msg->opt, ND_OPT_SOURCE_LL_ADDR, net_ethaddr, INETHADDRSZ); /* checksum */
@@ -137,6 +145,76 @@ static void ip6_send_ns(struct in6_addr *neigh_addr) net_send_packet(net_tx_packet, (pkt - net_tx_packet)); }
+/*
- ip6_send_rs() - Send IPv6 Router Solicitation Message.
- A router solicitation is sent to discover a router. RS message creation is
- based on RFC 4861 section 4.1. Router Solicitation Message Format.
- */
+void ip6_send_rs(void) +{
unsigned char enetaddr[6];
struct rs_msg *msg;
__u16 icmp_len;
uchar *pkt;
unsigned short csum;
unsigned int pcsum;
static unsigned int retry_count;
if (!ip6_is_unspecified_addr(&net_gateway6) &&
net_prefix_length != 0) {
net_set_state(NETLOOP_SUCCESS);
return;
} else if (retry_count >= MAX_RTR_SOLICITATIONS) {
net_set_state(NETLOOP_FAIL);
net_set_timeout_handler(0, 0);
retry_count = 0;
return;
}
printf("ROUTER SOLICITATION %d\n", retry_count + 1);
ip6_make_mult_ethdstaddr(enetaddr, &all_routers);
/*
* ICMP length is the size of ICMP header (8) + one option (8) = 16.
* The option is 2 bytes of type and length + 6 bytes for MAC.
*/
icmp_len = sizeof(struct icmp6hdr) + IP6_NDISC_OPT_SPACE(INETHADDRSZ);
pkt = (uchar *)net_tx_packet;
pkt += net_set_ether(pkt, enetaddr, PROT_IP6);
pkt += ip6_add_hdr(pkt, &net_link_local_ip6, &all_routers, PROT_ICMPV6,
IPV6_NDISC_HOPLIMIT, icmp_len);
/* ICMPv6 - RS */
msg = (struct rs_msg *)pkt;
msg->icmph.icmp6_type = IPV6_NDISC_ROUTER_SOLICITATION;
msg->icmph.icmp6_code = 0;
memset(&msg->icmph.icmp6_cksum, 0, sizeof(__be16));
memset(&msg->icmph.icmp6_unused, 0, sizeof(__be32));
/* Set the llsaddr option */
ndisc_insert_option(msg->opt, ND_OPT_SOURCE_LL_ADDR, net_ethaddr,
INETHADDRSZ);
/* checksum */
pcsum = csum_partial((__u8 *)msg, icmp_len, 0);
csum = csum_ipv6_magic(&net_link_local_ip6, &all_routers,
icmp_len, PROT_ICMPV6, pcsum);
msg->icmph.icmp6_cksum = csum;
pkt += icmp_len;
/* Wait up to 1 second if it is the first try to get the RA */
if (retry_count == 0)
udelay(((unsigned int)rand() % 1000000) * MAX_SOLICITATION_DELAY);
/* send it! */
net_send_packet(net_tx_packet, (pkt - net_tx_packet));
retry_count++;
net_set_timeout_handler(RTR_SOLICITATION_INTERVAL, ip6_send_rs);
+}
static void ip6_send_na(uchar *eth_dst_addr, struct in6_addr *neigh_addr, struct in6_addr *target) @@ -167,7 +245,7 @@ ip6_send_na(uchar *eth_dst_addr, struct in6_addr *neigh_addr, msg->icmph.icmp6_dataun.u_nd_advt.override = 1; /* Set the target address and lltargetaddr option */ net_copy_ip6(&msg->target, target);
ndisc_insert_option(msg, ND_OPT_TARGET_LL_ADDR, net_ethaddr,
ndisc_insert_option(msg->opt, ND_OPT_TARGET_LL_ADDR, net_ethaddr, INETHADDRSZ); /* checksum */
@@ -223,6 +301,10 @@ int ndisc_timeout_check(void) return 1; }
+/*
- ndisc_init() - Make initial steps for ND state machine.
- Usually move variables into initial state.
- */
void ndisc_init(void) { net_nd_packet_mac = NULL; @@ -234,12 +316,125 @@ void ndisc_init(void) net_nd_tx_packet -= (ulong)net_nd_tx_packet % PKTALIGN; }
+/*
- validate_ra() - Validate the router advertisement message.
- @ip6: Pointer to the router advertisement packet
- @len: Length of the router advertisement packet
- Check if the router advertisement message is valid. Conditions are
- according to RFC 4861 section 6.1.2. Validation of Router Advertisement
- Messages.
- Return: true if the message is valid and false if it is invalid.
- */
+static bool validate_ra(struct ip6_hdr *ip6, int len) +{
struct icmp6hdr *icmp = (struct icmp6hdr *)(ip6 + 1);
/* ICMP length (derived from the IP length) should be 16 or more octets. */
if (ip6->payload_len < 16)
return false;
/* Source IP Address should be a valid link-local address. */
if ((ntohs(ip6->saddr.s6_addr16[0]) & IPV6_LINK_LOCAL_MASK) !=
IPV6_LINK_LOCAL_PREFIX)
return false;
/*
* The IP Hop Limit field should have a value of 255, i.e., the packet
* could not possibly have been forwarded by a router.
*/
if (ip6->hop_limit != 255)
return false;
/* ICMP checksum has already been checked in net_ip6_handler. */
if (icmp->icmp6_code != 0)
return false;
return true;
+}
+/*
- process_ra() - Process the router advertisement packet.
- @ip6: Pointer to the router advertisement packet
- @len: Length of the router advertisement packet
- Process the received router advertisement message.
- Although RFC 4861 requires retaining at least two router addresses, we only
- keep one because of the U-Boot limitations and its goal of lightweight code.
- Return: 0 - RA is a default router and contains valid prefix information.
- Non-zero - RA options are invalid or do not indicate it is a default router
- or do not contain valid prefix information.
- */
+static int process_ra(struct ip6_hdr *ip6, int len) +{
/* Pointer to the ICMP section of the packet */
struct icmp6hdr *icmp = (struct icmp6hdr *)(ip6 + 1);
struct ra_msg *msg = (struct ra_msg *)icmp;
int remaining_option_len = len - IP6_HDR_SIZE - sizeof(struct ra_msg);
unsigned short int option_len; /* Length of each option */
/* Pointer to the ICMPv6 message options */
unsigned char *option = NULL;
/* 8-bit identifier of the type of ICMPv6 option */
unsigned char type = 0;
struct icmp6_ra_prefix_info *prefix = NULL;
/* Ignore the packet if router lifetime is 0. */
if (!icmp->icmp6_rt_lifetime)
return -EOPNOTSUPP;
/* Processing the options */
option = msg->opt;
while (remaining_option_len > 0) {
/* The 2nd byte of the option is its length. */
option_len = option[1];
/* All included options should have a positive length. */
if (option_len == 0)
return -EINVAL;
type = option[0];
/* All option types except Prefix Information are ignored. */
switch (type) {
case ND_OPT_SOURCE_LL_ADDR:
case ND_OPT_TARGET_LL_ADDR:
case ND_OPT_REDIRECT_HDR:
case ND_OPT_MTU:
break;
case ND_OPT_PREFIX_INFO:
prefix = (struct icmp6_ra_prefix_info *)option;
/* The link-local prefix 0xfe80::/10 is ignored. */
if ((ntohs(prefix->prefix.s6_addr16[0]) &
IPV6_LINK_LOCAL_MASK) == IPV6_LINK_LOCAL_PREFIX)
break;
if (prefix->on_link && ntohl(prefix->valid_lifetime)) {
net_prefix_length = prefix->prefix_len;
net_gateway6 = ip6->saddr;
return 0;
}
break;
default:
debug("Unknown IPv6 Neighbor Discovery Option 0x%x\n",
type);
}
option_len <<= 3; /* Option length is a multiple of 8. */
remaining_option_len -= option_len;
option += option_len;
}
return -EADDRNOTAVAIL;
+}
int ndisc_receive(struct ethernet_hdr *et, struct ip6_hdr *ip6, int len) { struct icmp6hdr *icmp = (struct icmp6hdr *)(((uchar *)ip6) + IP6_HDR_SIZE); struct nd_msg *ndisc = (struct nd_msg *)icmp; uchar neigh_eth_addr[6];
int err = 0; // The error code returned calling functions. switch (icmp->icmp6_type) { case IPV6_NDISC_NEIGHBOUR_SOLICITATION:
@@ -280,6 +475,36 @@ int ndisc_receive(struct ethernet_hdr *et, struct ip6_hdr *ip6, int len) net_nd_packet_mac = NULL; } break;
case IPV6_NDISC_ROUTER_SOLICITATION:
break;
case IPV6_NDISC_ROUTER_ADVERTISEMENT:
debug("Received router advertisement for %pI6c from %pI6c\n",
&ip6->daddr, &ip6->saddr);
/*
* If gateway and prefix are set, the RA packet is ignored. The
* reason is that the U-Boot code is supposed to be as compact
* as possible and does not need to take care of multiple
* routers. In addition to that, U-Boot does not want to handle
* scenarios like a router setting its lifetime to zero to
* indicate it is not routing anymore. U-Boot program has a
* short life when the system boots up and does not need such
* sophistication.
*/
if (!ip6_is_unspecified_addr(&net_gateway6) &&
net_prefix_length != 0) {
break;
}
if (!validate_ra(ip6, len)) {
debug("Invalid router advertisement message.\n");
break;
}
err = process_ra(ip6, len);
if (err)
debug("Ignored router advertisement. Error: %d\n", err);
else
printf("Set gatewayip6: %pI6c, prefix_length: %d\n",
&net_gateway6, net_prefix_length);
break; default: debug("Unexpected ICMPv6 type 0x%x\n", icmp->icmp6_type); return -1;
diff --git a/net/net.c b/net/net.c index c9a749f..39f0b81 100644 --- a/net/net.c +++ b/net/net.c @@ -24,7 +24,7 @@
- name of bootfile
Next step: ARP
- LINK_LOCAL:
- LINKLOCAL:
Prerequisites: - own ethernet address
We want: - own IP address
@@ -122,6 +122,7 @@ #endif #include <net/tcp.h> #include <net/wget.h> +#include "net_rand.h"
/** BOOTP EXTENTIONS **/
@@ -346,6 +347,8 @@ void net_auto_load(void)
static int net_init_loop(void) {
static bool first_call = true;
if (eth_get_dev()) { memcpy(net_ethaddr, eth_get_ethaddr(), 6);
@@ -365,6 +368,12 @@ static int net_init_loop(void) */ return -ENONET;
if (IS_ENABLED(CONFIG_IPV6_ROUTER_DISCOVERY))
if (first_call && use_ip6) {
first_call = false;
srand_mac(); /* This is for rand used in ip6_send_rs. */
net_loop(RS);
} return 0;
}
@@ -574,6 +583,10 @@ restart: ncsi_probe_packages(); break; #endif
case RS:
if (IS_ENABLED(CONFIG_IPV6_ROUTER_DISCOVERY))
ip6_send_rs();
break; default: break; }
@@ -671,7 +684,13 @@ restart: x = time_handler; time_handler = (thand_f *)0; (*x)();
}
} else if (IS_ENABLED(CONFIG_IPV6_ROUTER_DISCOVERY))
if (time_handler && protocol == RS)
if (!ip6_is_unspecified_addr(&net_gateway6) &&
net_prefix_length != 0) {
net_set_state(NETLOOP_SUCCESS);
net_set_timeout_handler(0, 0);
} if (net_state == NETLOOP_FAIL) ret = net_start_again();
diff --git a/net/net6.c b/net/net6.c index 75577bc..2dd64c0 100644 --- a/net/net6.c +++ b/net/net6.c @@ -413,6 +413,7 @@ int net_ip6_handler(struct ethernet_hdr *et, struct ip6_hdr *ip6, int len) break; case IPV6_NDISC_NEIGHBOUR_SOLICITATION: case IPV6_NDISC_NEIGHBOUR_ADVERTISEMENT:
case IPV6_NDISC_ROUTER_ADVERTISEMENT: ndisc_receive(et, ip6, len); break; default:
-- 1.8.3.1
Reviewed-by: Ramon Fried rfried.dev@gmail.com

From: Ehsan Mohandesi emohandesi@linux.microsoft.com
This series adds IPv6 network discovery to U-Boot. When an IPv6 command is run in U-Boot, it sends a router solicitation (RS) message to the network. The router on the network responds with a router advertisement (RA) message. Then U-Boot processes the RA message and sets the gatewayip6 and net_prefix_length environment variables. It is based on RFC 4861, but not everything in the RFC is supported here. https://www.rfc-editor.org/rfc/rfc4861
Changes in v2: - Improved IPv6 network discovery code. - Added IPv6 network discovery feature test (Python test). - Added unit tests (C code).
Ehsan Mohandesi (4): Revert "net: ipv6: Add support for default gateway discovery." net: ipv6: Add support for default gateway discovery. test/py: IPv6 network discovery test test: eth: IPv6 network discovery unit test
cmd/Kconfig | 6 + configs/sandbox64_defconfig | 2 + configs/sandbox_defconfig | 2 + configs/sandbox_flattree_defconfig | 2 + include/ndisc.h | 35 ++++++ include/net.h | 5 +- include/net6.h | 40 ++++++ net/ndisc.c | 243 +++++++++++++++++++++++++++++++++++-- net/net.c | 26 +++- net/net6.c | 1 + test/dm/eth.c | 88 ++++++++++++++ test/py/tests/test_net.py | 31 ++++- 12 files changed, 467 insertions(+), 14 deletions(-)

From: Ehsan Mohandesi emohandesi@microsoft.com
This reverts commit 0af1035a55d9c1486b2db43ee70ff0a63affd4f4.
Signed-off-by: Ehsan Mohandesi emohandesi@microsoft.com
Conflicts: cmd/Kconfig include/net.h include/net6.h net/net.c --- include/net.h | 4 ++-- net/net.c | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-)
diff --git a/include/net.h b/include/net.h index 399af5e..8ba50a0 100644 --- a/include/net.h +++ b/include/net.h @@ -504,8 +504,8 @@ extern ushort net_native_vlan; /* Our Native VLAN */ extern int net_restart_wrap; /* Tried all network devices */
enum proto_t { - BOOTP, RARP, ARP, TFTPGET, DHCP, PING, PING6, DNS, NFS, CDP, NETCONS, - SNTP, TFTPSRV, TFTPPUT, LINKLOCAL, FASTBOOT, WOL, UDP, NCSI, WGET + BOOTP, RARP, ARP, TFTPGET, DHCP, DHCP6, PING, PING6, DNS, NFS, CDP, + NETCONS, SNTP, TFTPSRV, TFTPPUT, LINKLOCAL, FASTBOOT, WOL, UDP, NCSI, WGET };
extern char net_boot_file_name[1024];/* Boot File name */ diff --git a/net/net.c b/net/net.c index c9a749f..d69bfb0 100644 --- a/net/net.c +++ b/net/net.c @@ -122,6 +122,9 @@ #endif #include <net/tcp.h> #include <net/wget.h> +#if defined(CONFIG_CMD_DHCP6) +#include "dhcpv6.h" +#endif
/** BOOTP EXTENTIONS **/

On Mon, 2023-04-10 at 12:34 -0700, emohandesi@linux.microsoft.com wrote:
From: Ehsan Mohandesi emohandesi@microsoft.com
This reverts commit 0af1035a55d9c1486b2db43ee70ff0a63affd4f4.
Signed-off-by: Ehsan Mohandesi emohandesi@microsoft.com
Conflicts: cmd/Kconfig include/net.h include/net6.h net/net.c
include/net.h | 4 ++-- net/net.c | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-)
diff --git a/include/net.h b/include/net.h index 399af5e..8ba50a0 100644 --- a/include/net.h +++ b/include/net.h @@ -504,8 +504,8 @@ extern ushort net_native_vlan; /* Our Native VLAN */ extern int net_restart_wrap; /* Tried all network devices */
enum proto_t {
BOOTP, RARP, ARP, TFTPGET, DHCP, PING, PING6, DNS, NFS, CDP,
NETCONS,
SNTP, TFTPSRV, TFTPPUT, LINKLOCAL, FASTBOOT, WOL, UDP, NCSI,
WGET
BOOTP, RARP, ARP, TFTPGET, DHCP, DHCP6, PING, PING6, DNS,
NFS, CDP,
NETCONS, SNTP, TFTPSRV, TFTPPUT, LINKLOCAL, FASTBOOT, WOL,
UDP, NCSI, WGET };
extern char net_boot_file_name[1024];/* Boot File name */ diff --git a/net/net.c b/net/net.c index c9a749f..d69bfb0 100644 --- a/net/net.c +++ b/net/net.c @@ -122,6 +122,9 @@ #endif #include <net/tcp.h> #include <net/wget.h> +#if defined(CONFIG_CMD_DHCP6) +#include "dhcpv6.h" +#endif
/** BOOTP EXTENTIONS **/
-- 1.8.3.1
Hello, Ehsan!
Is everything ok with this patch set? You reverted the commit that is absent in upstream.

You are right, Viacheslav. I should not have included the revert commit in my patch. I think I need to remove it and send a v3 of my patch series.
On 4/11/2023 2:01 AM, Vyacheslav V. Mitrofanov wrote:
On Mon, 2023-04-10 at 12:34 -0700, emohandesi@linux.microsoft.com wrote:
From: Ehsan Mohandesi emohandesi@microsoft.com
This reverts commit 0af1035a55d9c1486b2db43ee70ff0a63affd4f4.
Signed-off-by: Ehsan Mohandesi emohandesi@microsoft.com
Conflicts: cmd/Kconfig include/net.h include/net6.h net/net.c
include/net.h | 4 ++-- net/net.c | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-)
diff --git a/include/net.h b/include/net.h index 399af5e..8ba50a0 100644 --- a/include/net.h +++ b/include/net.h @@ -504,8 +504,8 @@ extern ushort net_native_vlan; /* Our Native VLAN */ extern int net_restart_wrap; /* Tried all network devices */
enum proto_t {
BOOTP, RARP, ARP, TFTPGET, DHCP, PING, PING6, DNS, NFS, CDP,
NETCONS,
SNTP, TFTPSRV, TFTPPUT, LINKLOCAL, FASTBOOT, WOL, UDP, NCSI,
WGET
BOOTP, RARP, ARP, TFTPGET, DHCP, DHCP6, PING, PING6, DNS,
NFS, CDP,
NETCONS, SNTP, TFTPSRV, TFTPPUT, LINKLOCAL, FASTBOOT, WOL,
UDP, NCSI, WGET };
extern char net_boot_file_name[1024];/* Boot File name */ diff --git a/net/net.c b/net/net.c index c9a749f..d69bfb0 100644 --- a/net/net.c +++ b/net/net.c @@ -122,6 +122,9 @@ #endif #include <net/tcp.h> #include <net/wget.h> +#if defined(CONFIG_CMD_DHCP6) +#include "dhcpv6.h" +#endif
/** BOOTP EXTENTIONS **/
-- 1.8.3.1
Hello, Ehsan!
Is everything ok with this patch set? You reverted the commit that is absent in upstream.

From: Ehsan Mohandesi emohandesi@microsoft.com
In IPv6, the default gateway and prefix length are determined by receiving a router advertisement as defined in - https://www.rfc-editor.org/rfc/rfc4861.
Add support for sending router solicitation (RS) and processing router advertisements (RA).
If the RA has prefix info option and following conditions are met, then gatewayip6 and net_prefix_length of ip6addr env variables are initialized. These are later consumed by IPv6 code for non-local destination IP.
- "Router Lifetime" != 0 - Prefix is NOT link-local prefix (0xfe80::/10) - L flag is 1 - "Valid Lifetime" != 0
Timing Parameters: - MAX_RTR_SOLICITATION_DELAY (0-1s) - RTR_SOLICITATION_INTERVAL (4s) (min retransmit delay) - MAX_RTR_SOLICITATIONS (3 RS transmissions)
The functionality is enabled by CONFIG_IPV6_ROUTER_DISCOVERY and invoked automatically from net_init_loop().
Signed-off-by: Ehsan Mohandesi emohandesi@microsoft.com
Conflicts: cmd/Kconfig net/net.c --- Changes in v2: - A few of cosmetic changes. - Made some functions not static in order to be able to test them.
cmd/Kconfig | 6 ++ include/ndisc.h | 35 ++++++++ include/net.h | 3 +- include/net6.h | 40 ++++++++++ net/ndisc.c | 243 +++++++++++++++++++++++++++++++++++++++++++++++++++++--- net/net.c | 23 +++++- net/net6.c | 1 + 7 files changed, 339 insertions(+), 12 deletions(-)
diff --git a/cmd/Kconfig b/cmd/Kconfig index e45b884..6919d31 100644 --- a/cmd/Kconfig +++ b/cmd/Kconfig @@ -1916,6 +1916,12 @@ config CMD_NCSI Normally this happens automatically before other network operations.
+config IPV6_ROUTER_DISCOVERY + bool "Do IPv6 router discovery" + depends on IPV6 + help + Will automatically perform router solicitation on first IPv6 + network operation endif
config CMD_ETHSW diff --git a/include/ndisc.h b/include/ndisc.h index f6f8eb6..12fa9e7 100644 --- a/include/ndisc.h +++ b/include/ndisc.h @@ -19,6 +19,20 @@ struct nd_msg { __u8 opt[0]; };
+/* struct rs_msg - ICMPv6 Router Solicitation message format */ +struct rs_msg { + struct icmp6hdr icmph; + __u8 opt[0]; +}; + +/* struct ra_msg - ICMPv6 Router Advertisement message format */ +struct ra_msg { + struct icmp6hdr icmph; + __u32 reachable_time; + __u32 retransmission_timer; + __u8 opt[0]; +}; + /* struct echo_msg - ICMPv6 echo request/reply message format */ struct echo_msg { struct icmp6hdr icmph; @@ -57,6 +71,11 @@ extern int net_nd_try; */ void ndisc_init(void);
+/* + * ip6_send_rs() - Send IPv6 Router Solicitation Message + */ +void ip6_send_rs(void); + /** * ndisc_receive() - Handle ND packet * @@ -78,6 +97,8 @@ void ndisc_request(void); * Return: 0 if no timeout, -1 otherwise */ int ndisc_timeout_check(void); +bool validate_ra(struct ip6_hdr *ip6); +int process_ra(struct ip6_hdr *ip6, int len); #else static inline void ndisc_init(void) { @@ -97,6 +118,20 @@ static inline int ndisc_timeout_check(void) { return 0; } + +void ip6_send_rs(void) +{ +} + +static inline bool validate_ra(struct ip6_hdr *ip6) +{ + return true; +} + +static inline int process_ra(struct ip6_hdr *ip6, int len) +{ + return 0; +} #endif
#endif /* __NDISC_H__ */ diff --git a/include/net.h b/include/net.h index 8ba50a0..58774f6 100644 --- a/include/net.h +++ b/include/net.h @@ -505,7 +505,8 @@ extern int net_restart_wrap; /* Tried all network devices */
enum proto_t { BOOTP, RARP, ARP, TFTPGET, DHCP, DHCP6, PING, PING6, DNS, NFS, CDP, - NETCONS, SNTP, TFTPSRV, TFTPPUT, LINKLOCAL, FASTBOOT, WOL, UDP, NCSI, WGET + NETCONS, SNTP, TFTPSRV, TFTPPUT, LINKLOCAL, FASTBOOT, WOL, UDP, NCSI, + WGET, RS };
extern char net_boot_file_name[1024];/* Boot File name */ diff --git a/include/net6.h b/include/net6.h index 2d7c5a0..beafc05 100644 --- a/include/net6.h +++ b/include/net6.h @@ -81,8 +81,17 @@ struct udp_hdr { 0x00, 0x00, 0x00, 0x00, \ 0x00, 0x00, 0x00, 0x00, \ 0x00, 0x00, 0x00, 0x00 } } } +/* + * All-routers multicast address is the link-local scope address to reach all + * routers. + */ +#define ALL_ROUTERS_MULT_ADDR { { { 0xFF, 0x02, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x02 } } }
#define IPV6_LINK_LOCAL_PREFIX 0xfe80 +#define IPV6_LINK_LOCAL_MASK 0xffb0 /* The first 10-bit of address mask. */
/* hop limit for neighbour discovery packets */ #define IPV6_NDISC_HOPLIMIT 255 @@ -166,6 +175,37 @@ struct icmp6hdr { #define icmp6_rt_lifetime icmp6_dataun.u_nd_ra.rt_lifetime } __packed;
+/* + * struct icmp6_ra_prefix_info - Prefix Information option of the ICMPv6 message + * The Prefix Information option provides hosts with on-link prefixes and + * prefixes for Address Autoconfiguration. Refer to RFC 4861 for more info. + */ +struct icmp6_ra_prefix_info { + u8 type; /* Type is 3 for Prefix Information. */ + u8 len; /* Len is 4 for Prefix Information. */ + /* The number of leading bits in the Prefix that are valid. */ + u8 prefix_len; + u8 reserved1:6, /* MUST be ignored by the receiver. */ + aac:1, /* autonomous address-configuration flag */ + /* Indicates that this prefix can be used for on-link determination. */ + on_link:1; + /* + * The length of time in seconds that the prefix is valid for the + * purpose of on-link determination. + */ + __be32 valid_lifetime; + /* The length of time addresses remain preferred. */ + __be32 preferred_lifetime; + __be32 reserved2; /* MUST be ignored by the receiver. */ + /* + * Prefix is an IP address or a prefix of an IP address. The Prefix + * Length field contains the number of valid leading bits in the prefix. + * The bits in the prefix after the prefix length are reserved and MUST + * be initialized to zero by the sender and ignored by the receiver. + */ + struct in6_addr prefix; +}; + extern struct in6_addr const net_null_addr_ip6; /* NULL IPv6 address */ extern struct in6_addr net_gateway6; /* Our gateways IPv6 address */ extern struct in6_addr net_ip6; /* Our IPv6 addr (0 = unknown) */ diff --git a/net/ndisc.c b/net/ndisc.c index 367dae7..0b27779 100644 --- a/net/ndisc.c +++ b/net/ndisc.c @@ -13,6 +13,8 @@ #include <net.h> #include <net6.h> #include <ndisc.h> +#include <stdlib.h> +#include <linux/delay.h>
/* IPv6 destination address of packet waiting for ND */ struct in6_addr net_nd_sol_packet_ip6 = ZERO_IPV6_ADDR; @@ -29,31 +31,37 @@ int net_nd_tx_packet_size; ulong net_nd_timer_start; /* the number of requests we have sent so far */ int net_nd_try; +struct in6_addr all_routers = ALL_ROUTERS_MULT_ADDR; + +#define MAX_RTR_SOLICITATIONS 3 +/* The maximum time to delay sending the first router solicitation message. */ +#define MAX_SOLICITATION_DELAY 1 // 1 second +/* The time to wait before sending the next router solicitation message. */ +#define RTR_SOLICITATION_INTERVAL 4000 // 4 seconds
#define IP6_NDISC_OPT_SPACE(len) (((len) + 2 + 7) & ~7)
/** * ndisc_insert_option() - Insert an option into a neighbor discovery packet * - * @ndisc: pointer to ND packet + * @opt: pointer to the option element of the neighbor discovery packet * @type: option type to insert * @data: option data to insert * @len: data length * Return: the number of bytes inserted (which may be >= len) */ -static int -ndisc_insert_option(struct nd_msg *ndisc, int type, u8 *data, int len) +static int ndisc_insert_option(__u8 *opt, int type, u8 *data, int len) { int space = IP6_NDISC_OPT_SPACE(len);
- ndisc->opt[0] = type; - ndisc->opt[1] = space >> 3; - memcpy(&ndisc->opt[2], data, len); + opt[0] = type; + opt[1] = space >> 3; + memcpy(&opt[2], data, len); len += 2;
/* fill the remainder with 0 */ if (space - len > 0) - memset(&ndisc->opt[len], '\0', space - len); + memset(&opt[len], '\0', space - len);
return space; } @@ -123,7 +131,7 @@ static void ip6_send_ns(struct in6_addr *neigh_addr)
/* Set the target address and llsaddr option */ net_copy_ip6(&msg->target, neigh_addr); - ndisc_insert_option(msg, ND_OPT_SOURCE_LL_ADDR, net_ethaddr, + ndisc_insert_option(msg->opt, ND_OPT_SOURCE_LL_ADDR, net_ethaddr, INETHADDRSZ);
/* checksum */ @@ -137,6 +145,76 @@ static void ip6_send_ns(struct in6_addr *neigh_addr) net_send_packet(net_tx_packet, (pkt - net_tx_packet)); }
+/* + * ip6_send_rs() - Send IPv6 Router Solicitation Message. + * + * A router solicitation is sent to discover a router. RS message creation is + * based on RFC 4861 section 4.1. Router Solicitation Message Format. + */ +void ip6_send_rs(void) +{ + unsigned char enetaddr[6]; + struct rs_msg *msg; + __u16 icmp_len; + uchar *pkt; + unsigned short csum; + unsigned int pcsum; + static unsigned int retry_count; + + if (!ip6_is_unspecified_addr(&net_gateway6) && + net_prefix_length != 0) { + net_set_state(NETLOOP_SUCCESS); + return; + } else if (retry_count >= MAX_RTR_SOLICITATIONS) { + net_set_state(NETLOOP_FAIL); + net_set_timeout_handler(0, NULL); + retry_count = 0; + return; + } + + printf("ROUTER SOLICITATION %d\n", retry_count + 1); + + ip6_make_mult_ethdstaddr(enetaddr, &all_routers); + /* + * ICMP length is the size of ICMP header (8) + one option (8) = 16. + * The option is 2 bytes of type and length + 6 bytes for MAC. + */ + icmp_len = sizeof(struct icmp6hdr) + IP6_NDISC_OPT_SPACE(INETHADDRSZ); + + pkt = (uchar *)net_tx_packet; + pkt += net_set_ether(pkt, enetaddr, PROT_IP6); + pkt += ip6_add_hdr(pkt, &net_link_local_ip6, &all_routers, PROT_ICMPV6, + IPV6_NDISC_HOPLIMIT, icmp_len); + + /* ICMPv6 - RS */ + msg = (struct rs_msg *)pkt; + msg->icmph.icmp6_type = IPV6_NDISC_ROUTER_SOLICITATION; + msg->icmph.icmp6_code = 0; + memset(&msg->icmph.icmp6_cksum, 0, sizeof(__be16)); + memset(&msg->icmph.icmp6_unused, 0, sizeof(__be32)); + + /* Set the llsaddr option */ + ndisc_insert_option(msg->opt, ND_OPT_SOURCE_LL_ADDR, net_ethaddr, + INETHADDRSZ); + + /* checksum */ + pcsum = csum_partial((__u8 *)msg, icmp_len, 0); + csum = csum_ipv6_magic(&net_link_local_ip6, &all_routers, + icmp_len, PROT_ICMPV6, pcsum); + msg->icmph.icmp6_cksum = csum; + pkt += icmp_len; + + /* Wait up to 1 second if it is the first try to get the RA */ + if (retry_count == 0) + udelay(((unsigned int)rand() % 1000000) * MAX_SOLICITATION_DELAY); + + /* send it! */ + net_send_packet(net_tx_packet, (pkt - net_tx_packet)); + + retry_count++; + net_set_timeout_handler(RTR_SOLICITATION_INTERVAL, ip6_send_rs); +} + static void ip6_send_na(uchar *eth_dst_addr, struct in6_addr *neigh_addr, struct in6_addr *target) @@ -167,7 +245,7 @@ ip6_send_na(uchar *eth_dst_addr, struct in6_addr *neigh_addr, msg->icmph.icmp6_dataun.u_nd_advt.override = 1; /* Set the target address and lltargetaddr option */ net_copy_ip6(&msg->target, target); - ndisc_insert_option(msg, ND_OPT_TARGET_LL_ADDR, net_ethaddr, + ndisc_insert_option(msg->opt, ND_OPT_TARGET_LL_ADDR, net_ethaddr, INETHADDRSZ);
/* checksum */ @@ -223,6 +301,10 @@ int ndisc_timeout_check(void) return 1; }
+/* + * ndisc_init() - Make initial steps for ND state machine. + * Usually move variables into initial state. + */ void ndisc_init(void) { net_nd_packet_mac = NULL; @@ -234,12 +316,125 @@ void ndisc_init(void) net_nd_tx_packet -= (ulong)net_nd_tx_packet % PKTALIGN; }
+/* + * validate_ra() - Validate the router advertisement message. + * + * @ip6: Pointer to the router advertisement packet + * + * Check if the router advertisement message is valid. Conditions are + * according to RFC 4861 section 6.1.2. Validation of Router Advertisement + * Messages. + * + * Return: true if the message is valid and false if it is invalid. + */ +bool validate_ra(struct ip6_hdr *ip6) +{ + struct icmp6hdr *icmp = (struct icmp6hdr *)(ip6 + 1); + + /* ICMP length (derived from the IP length) should be 16 or more octets. */ + if (ip6->payload_len < 16) + return false; + + /* Source IP Address should be a valid link-local address. */ + if ((ntohs(ip6->saddr.s6_addr16[0]) & IPV6_LINK_LOCAL_MASK) != + IPV6_LINK_LOCAL_PREFIX) + return false; + + /* + * The IP Hop Limit field should have a value of 255, i.e., the packet + * could not possibly have been forwarded by a router. + */ + if (ip6->hop_limit != 255) + return false; + + /* ICMP checksum has already been checked in net_ip6_handler. */ + + if (icmp->icmp6_code != 0) + return false; + + return true; +} + +/* + * process_ra() - Process the router advertisement packet. + * + * @ip6: Pointer to the router advertisement packet + * @len: Length of the router advertisement packet + * + * Process the received router advertisement message. + * Although RFC 4861 requires retaining at least two router addresses, we only + * keep one because of the U-Boot limitations and its goal of lightweight code. + * + * Return: 0 - RA is a default router and contains valid prefix information. + * Non-zero - RA options are invalid or do not indicate it is a default router + * or do not contain valid prefix information. + */ +int process_ra(struct ip6_hdr *ip6, int len) +{ + /* Pointer to the ICMP section of the packet */ + struct icmp6hdr *icmp = (struct icmp6hdr *)(ip6 + 1); + struct ra_msg *msg = (struct ra_msg *)icmp; + int remaining_option_len = len - IP6_HDR_SIZE - sizeof(struct ra_msg); + unsigned short int option_len; /* Length of each option */ + /* Pointer to the ICMPv6 message options */ + unsigned char *option = NULL; + /* 8-bit identifier of the type of ICMPv6 option */ + unsigned char type = 0; + struct icmp6_ra_prefix_info *prefix = NULL; + + /* Ignore the packet if router lifetime is 0. */ + if (!icmp->icmp6_rt_lifetime) + return -EOPNOTSUPP; + + /* Processing the options */ + option = msg->opt; + while (remaining_option_len > 0) { + /* The 2nd byte of the option is its length. */ + option_len = option[1]; + /* All included options should have a positive length. */ + if (option_len == 0) + return -EINVAL; + + type = option[0]; + /* All option types except Prefix Information are ignored. */ + switch (type) { + case ND_OPT_SOURCE_LL_ADDR: + case ND_OPT_TARGET_LL_ADDR: + case ND_OPT_REDIRECT_HDR: + case ND_OPT_MTU: + break; + case ND_OPT_PREFIX_INFO: + prefix = (struct icmp6_ra_prefix_info *)option; + /* The link-local prefix 0xfe80::/10 is ignored. */ + if ((ntohs(prefix->prefix.s6_addr16[0]) & + IPV6_LINK_LOCAL_MASK) == IPV6_LINK_LOCAL_PREFIX) + break; + if (prefix->on_link && ntohl(prefix->valid_lifetime)) { + net_prefix_length = prefix->prefix_len; + net_gateway6 = ip6->saddr; + return 0; + } + break; + default: + debug("Unknown IPv6 Neighbor Discovery Option 0x%x\n", + type); + } + + option_len <<= 3; /* Option length is a multiple of 8. */ + remaining_option_len -= option_len; + option += option_len; + } + + return -EADDRNOTAVAIL; +} + int ndisc_receive(struct ethernet_hdr *et, struct ip6_hdr *ip6, int len) { struct icmp6hdr *icmp = (struct icmp6hdr *)(((uchar *)ip6) + IP6_HDR_SIZE); struct nd_msg *ndisc = (struct nd_msg *)icmp; uchar neigh_eth_addr[6]; + int err = 0; // The error code returned calling functions.
switch (icmp->icmp6_type) { case IPV6_NDISC_NEIGHBOUR_SOLICITATION: @@ -280,6 +475,36 @@ int ndisc_receive(struct ethernet_hdr *et, struct ip6_hdr *ip6, int len) net_nd_packet_mac = NULL; } break; + case IPV6_NDISC_ROUTER_SOLICITATION: + break; + case IPV6_NDISC_ROUTER_ADVERTISEMENT: + debug("Received router advertisement for %pI6c from %pI6c\n", + &ip6->daddr, &ip6->saddr); + /* + * If gateway and prefix are set, the RA packet is ignored. The + * reason is that the U-Boot code is supposed to be as compact + * as possible and does not need to take care of multiple + * routers. In addition to that, U-Boot does not want to handle + * scenarios like a router setting its lifetime to zero to + * indicate it is not routing anymore. U-Boot program has a + * short life when the system boots up and does not need such + * sophistication. + */ + if (!ip6_is_unspecified_addr(&net_gateway6) && + net_prefix_length != 0) { + break; + } + if (!validate_ra(ip6)) { + debug("Invalid router advertisement message.\n"); + break; + } + err = process_ra(ip6, len); + if (err) + debug("Ignored router advertisement. Error: %d\n", err); + else + printf("Set gatewayip6: %pI6c, prefix_length: %d\n", + &net_gateway6, net_prefix_length); + break; default: debug("Unexpected ICMPv6 type 0x%x\n", icmp->icmp6_type); return -1; diff --git a/net/net.c b/net/net.c index d69bfb0..abdb7e4 100644 --- a/net/net.c +++ b/net/net.c @@ -24,7 +24,7 @@ * - name of bootfile * Next step: ARP * - * LINK_LOCAL: + * LINKLOCAL: * * Prerequisites: - own ethernet address * We want: - own IP address @@ -125,6 +125,7 @@ #if defined(CONFIG_CMD_DHCP6) #include "dhcpv6.h" #endif +#include "net_rand.h"
/** BOOTP EXTENTIONS **/
@@ -349,6 +350,8 @@ void net_auto_load(void)
static int net_init_loop(void) { + static bool first_call = true; + if (eth_get_dev()) { memcpy(net_ethaddr, eth_get_ethaddr(), 6);
@@ -368,6 +371,12 @@ static int net_init_loop(void) */ return -ENONET;
+ if (IS_ENABLED(CONFIG_IPV6_ROUTER_DISCOVERY)) + if (first_call && use_ip6) { + first_call = false; + srand_mac(); /* This is for rand used in ip6_send_rs. */ + net_loop(RS); + } return 0; }
@@ -577,6 +586,10 @@ restart: ncsi_probe_packages(); break; #endif + case RS: + if (IS_ENABLED(CONFIG_IPV6_ROUTER_DISCOVERY)) + ip6_send_rs(); + break; default: break; } @@ -674,7 +687,13 @@ restart: x = time_handler; time_handler = (thand_f *)0; (*x)(); - } + } else if (IS_ENABLED(CONFIG_IPV6_ROUTER_DISCOVERY)) + if (time_handler && protocol == RS) + if (!ip6_is_unspecified_addr(&net_gateway6) && + net_prefix_length != 0) { + net_set_state(NETLOOP_SUCCESS); + net_set_timeout_handler(0, NULL); + }
if (net_state == NETLOOP_FAIL) ret = net_start_again(); diff --git a/net/net6.c b/net/net6.c index 75577bc..2dd64c0 100644 --- a/net/net6.c +++ b/net/net6.c @@ -413,6 +413,7 @@ int net_ip6_handler(struct ethernet_hdr *et, struct ip6_hdr *ip6, int len) break; case IPV6_NDISC_NEIGHBOUR_SOLICITATION: case IPV6_NDISC_NEIGHBOUR_ADVERTISEMENT: + case IPV6_NDISC_ROUTER_ADVERTISEMENT: ndisc_receive(et, ip6, len); break; default:

From: Ehsan Mohandesi emohandesi@microsoft.com
Test the IPv6 network discovery feature if indicated by boardenv file.
Signed-off-by: Ehsan Mohandesi emohandesi@microsoft.com
Conflicts: configs/sandbox64_defconfig configs/sandbox_defconfig configs/sandbox_flattree_defconfig --- configs/sandbox64_defconfig | 2 ++ configs/sandbox_defconfig | 2 ++ configs/sandbox_flattree_defconfig | 2 ++ test/py/tests/test_net.py | 31 ++++++++++++++++++++++++++++++- 4 files changed, 36 insertions(+), 1 deletion(-)
diff --git a/configs/sandbox64_defconfig b/configs/sandbox64_defconfig index af2c56a..be36ede 100644 --- a/configs/sandbox64_defconfig +++ b/configs/sandbox64_defconfig @@ -260,3 +260,5 @@ CONFIG_FWU_MULTI_BANK_UPDATE=y CONFIG_UNIT_TEST=y CONFIG_UT_TIME=y CONFIG_UT_DM=y +CONFIG_IPV6=y +CONFIG_IPV6_ROUTER_DISCOVERY=y diff --git a/configs/sandbox_defconfig b/configs/sandbox_defconfig index ca95b2c..0673c69 100644 --- a/configs/sandbox_defconfig +++ b/configs/sandbox_defconfig @@ -341,3 +341,5 @@ CONFIG_UNIT_TEST=y CONFIG_UT_TIME=y CONFIG_UT_DM=y CONFIG_CMD_2048=y +CONFIG_IPV6=y +CONFIG_IPV6_ROUTER_DISCOVERY=y diff --git a/configs/sandbox_flattree_defconfig b/configs/sandbox_flattree_defconfig index e9fcc5b..d6c8dd2 100644 --- a/configs/sandbox_flattree_defconfig +++ b/configs/sandbox_flattree_defconfig @@ -229,3 +229,5 @@ CONFIG_EFI_CAPSULE_FIRMWARE_FIT=y CONFIG_UNIT_TEST=y CONFIG_UT_TIME=y CONFIG_UT_DM=y +CONFIG_IPV6=y +CONFIG_IPV6_ROUTER_DISCOVERY=y diff --git a/test/py/tests/test_net.py b/test/py/tests/test_net.py index 9ca6743..f85071d 100644 --- a/test/py/tests/test_net.py +++ b/test/py/tests/test_net.py @@ -9,7 +9,7 @@ import u_boot_utils
""" Note: This test relies on boardenv_* containing configuration values to define -which the network environment available for testing. Without this, this test +which network environment is available for testing. Without this, this test will be automatically skipped.
For example: @@ -55,6 +55,11 @@ env__net_nfs_readable_file = { 'size': 5058624, 'crc32': 'c2244b26', } + +# True if a router advertisement service is connected to the network, and should +# be tested. If router advertisement testing is not possible or desired, this +variable may be omitted or set to False. +env__router_on_net = True """
net_set_up = False @@ -126,6 +131,30 @@ def test_net_ping(u_boot_console): output = u_boot_console.run_command('ping $serverip') assert 'is alive' in output
+@pytest.mark.buildconfigspec('IPV6_ROUTER_DISCOVERY') +def test_net_network_discovery(u_boot_console): + """Test the network discovery feature of IPv6. + + An IPv6 network command (ping6 in this case) is run to make U-Boot send a + router solicitation packet, receive a router advertisement message, and + parse it. + A router advertisement service needs to be running for this test to succeed. + U-Boot receives the RA, processes it, and if successful, assigns the gateway + IP and prefix length. + The configuration is provided by the boardenv_* file; see the comment at + the beginning of this file. + """ + + router_on_net = u_boot_console.config.env.get('env__router_on_net', False) + if not router_on_net: + pytest.skip('No router on network') + + fake_host_ip = 'fe80::215:5dff:fef6:2ec6' + output = u_boot_console.run_command('ping6 ' + fake_host_ip) + assert 'ROUTER SOLICITATION 1' in output + assert 'Set gatewayip6:' in output + assert '0000:0000:0000:0000:0000:0000:0000:0000' not in output + @pytest.mark.buildconfigspec('cmd_net') def test_net_tftpboot(u_boot_console): """Test the tftpboot command.

From: Ehsan Mohandesi emohandesi@microsoft.com
Test router advertisement validation and processing functions.
Signed-off-by: Ehsan Mohandesi emohandesi@microsoft.com --- test/dm/eth.c | 88 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+)
diff --git a/test/dm/eth.c b/test/dm/eth.c index ebf01d8..d05d2a9 100644 --- a/test/dm/eth.c +++ b/test/dm/eth.c @@ -20,6 +20,7 @@ #include <dm/uclass-internal.h> #include <test/test.h> #include <test/ut.h> +#include <ndisc.h>
#define DM_TEST_ETH_NUM 4
@@ -607,3 +608,90 @@ static int dm_test_eth_async_ping_reply(struct unit_test_state *uts) }
DM_TEST(dm_test_eth_async_ping_reply, UT_TESTF_SCAN_FDT); + +#if IS_ENABLED(CONFIG_IPV6_ROUTER_DISCOVERY) + +static u8 ip6_ra_buf[] = {0x60, 0xf, 0xc5, 0x4a, 0x0, 0x38, 0x3a, 0xff, 0xfe, + 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x6, 0x85, 0xe6, + 0x29, 0x77, 0xcb, 0xc8, 0x53, 0xff, 0x2, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x1, 0x86, 0x0, 0xdc, 0x90, 0x40, 0x80, 0x15, 0x18, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3, 0x4, + 0x40, 0xc0, 0x0, 0x0, 0x37, 0xdc, 0x0, 0x0, 0x37, + 0x78, 0x0, 0x0, 0x0, 0x0, 0x20, 0x1, 0xca, 0xfe, 0xca, + 0xfe, 0xca, 0xfe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x1, 0x1, 0x0, 0x15, 0x5d, 0xe2, 0x8a, 0x2}; + +static int dm_test_validate_ra(struct unit_test_state *uts) +{ + struct ip6_hdr *ip6 = (struct ip6_hdr *)ip6_ra_buf; + struct icmp6hdr *icmp = (struct icmp6hdr *)(ip6 + 1); + __be16 temp = 0; + + ut_assert(validate_ra(ip6) == true); + + temp = ip6->payload_len; + ip6->payload_len = 15; + ut_assert(validate_ra(ip6) == false); + ip6->payload_len = temp; + + temp = ip6->saddr.s6_addr16[0]; + ip6->saddr.s6_addr16[0] = 0x2001; + ut_assert(validate_ra(ip6) == false); + ip6->saddr.s6_addr16[0] = temp; + + temp = ip6->hop_limit; + ip6->hop_limit = 15; + ut_assert(validate_ra(ip6) == false); + ip6->hop_limit = temp; + + temp = icmp->icmp6_code; + icmp->icmp6_code = 15; + ut_assert(validate_ra(ip6) == false); + icmp->icmp6_code = temp; + + return 0; +} + +DM_TEST(dm_test_validate_ra, 0); + +static int dm_test_process_ra(struct unit_test_state *uts) +{ + int len = sizeof(ip6_ra_buf); + struct ip6_hdr *ip6 = (struct ip6_hdr *)ip6_ra_buf; + struct icmp6hdr *icmp = (struct icmp6hdr *)(ip6 + 1); + struct ra_msg *msg = (struct ra_msg *)icmp; + unsigned char *option = msg->opt; + struct icmp6_ra_prefix_info *prefix = + (struct icmp6_ra_prefix_info *)option; + __be16 temp = 0; + unsigned char option_len = option[1]; + + ut_assert(process_ra(ip6, len) == 0); + + temp = icmp->icmp6_rt_lifetime; + icmp->icmp6_rt_lifetime = 0; + ut_assert(process_ra(ip6, len) != 0); + icmp->icmp6_rt_lifetime = temp; + + ut_assert(process_ra(ip6, 0) != 0); + + option[1] = 0; + ut_assert(process_ra(ip6, len) != 0); + option[1] = option_len; + + prefix->on_link = false; + ut_assert(process_ra(ip6, len) != 0); + prefix->on_link = true; + + temp = prefix->prefix.s6_addr16[0]; + prefix->prefix.s6_addr16[0] = 0x80fe; + ut_assert(process_ra(ip6, len) != 0); + prefix->prefix.s6_addr16[0] = temp; + + return 0; +} + +DM_TEST(dm_test_process_ra, 0); + +#endif

From: Ehsan Mohandesi emohandesi@linux.microsoft.com
This series adds IPv6 network discovery to U-Boot. When an IPv6 command is run in U-Boot, it sends a router solicitation (RS) message to the network. The router on the network responds with a router advertisement (RA) message. Then U-Boot processes the RA message and sets the gatewayip6 and net_prefix_length environment variables. It is based on RFC 4861, but not everything in the RFC is supported here. https://www.rfc-editor.org/rfc/rfc4861
Changes in v3: - Removed the extra revert commit that was mistakenly added in v2.
Changes in v2: - Improved IPv6 network discovery code. - Added IPv6 network discovery feature test (Python test). - Added unit tests (C code).
Ehsan Mohandesi (3): net: ipv6: Add support for default gateway discovery. test/py: IPv6 network discovery test test: eth: IPv6 network discovery unit test
cmd/Kconfig | 6 + configs/sandbox64_defconfig | 2 + configs/sandbox_defconfig | 2 + configs/sandbox_flattree_defconfig | 2 + include/ndisc.h | 35 ++++++ include/net.h | 3 +- include/net6.h | 40 ++++++ net/ndisc.c | 243 +++++++++++++++++++++++++++++++++++-- net/net.c | 23 +++- net/net6.c | 1 + test/dm/eth.c | 88 ++++++++++++++ test/py/tests/test_net.py | 31 ++++- 12 files changed, 463 insertions(+), 13 deletions(-)

From: Ehsan Mohandesi emohandesi@microsoft.com
In IPv6, the default gateway and prefix length are determined by receiving a router advertisement as defined in - https://www.rfc-editor.org/rfc/rfc4861.
Add support for sending router solicitation (RS) and processing router advertisements (RA).
If the RA has prefix info option and following conditions are met, then gatewayip6 and net_prefix_length of ip6addr env variables are initialized. These are later consumed by IPv6 code for non-local destination IP.
- "Router Lifetime" != 0 - Prefix is NOT link-local prefix (0xfe80::/10) - L flag is 1 - "Valid Lifetime" != 0
Timing Parameters: - MAX_RTR_SOLICITATION_DELAY (0-1s) - RTR_SOLICITATION_INTERVAL (4s) (min retransmit delay) - MAX_RTR_SOLICITATIONS (3 RS transmissions)
The functionality is enabled by CONFIG_IPV6_ROUTER_DISCOVERY and invoked automatically from net_init_loop().
Signed-off-by: Ehsan Mohandesi emohandesi@microsoft.com
Conflicts: cmd/Kconfig net/net.c --- cmd/Kconfig | 6 ++ include/ndisc.h | 35 ++++++++ include/net.h | 3 +- include/net6.h | 40 ++++++++++ net/ndisc.c | 243 +++++++++++++++++++++++++++++++++++++++++++++++++++++--- net/net.c | 23 +++++- net/net6.c | 1 + 7 files changed, 339 insertions(+), 12 deletions(-)
diff --git a/cmd/Kconfig b/cmd/Kconfig index e45b884..6919d31 100644 --- a/cmd/Kconfig +++ b/cmd/Kconfig @@ -1916,6 +1916,12 @@ config CMD_NCSI Normally this happens automatically before other network operations.
+config IPV6_ROUTER_DISCOVERY + bool "Do IPv6 router discovery" + depends on IPV6 + help + Will automatically perform router solicitation on first IPv6 + network operation endif
config CMD_ETHSW diff --git a/include/ndisc.h b/include/ndisc.h index f6f8eb6..12fa9e7 100644 --- a/include/ndisc.h +++ b/include/ndisc.h @@ -19,6 +19,20 @@ struct nd_msg { __u8 opt[0]; };
+/* struct rs_msg - ICMPv6 Router Solicitation message format */ +struct rs_msg { + struct icmp6hdr icmph; + __u8 opt[0]; +}; + +/* struct ra_msg - ICMPv6 Router Advertisement message format */ +struct ra_msg { + struct icmp6hdr icmph; + __u32 reachable_time; + __u32 retransmission_timer; + __u8 opt[0]; +}; + /* struct echo_msg - ICMPv6 echo request/reply message format */ struct echo_msg { struct icmp6hdr icmph; @@ -57,6 +71,11 @@ extern int net_nd_try; */ void ndisc_init(void);
+/* + * ip6_send_rs() - Send IPv6 Router Solicitation Message + */ +void ip6_send_rs(void); + /** * ndisc_receive() - Handle ND packet * @@ -78,6 +97,8 @@ void ndisc_request(void); * Return: 0 if no timeout, -1 otherwise */ int ndisc_timeout_check(void); +bool validate_ra(struct ip6_hdr *ip6); +int process_ra(struct ip6_hdr *ip6, int len); #else static inline void ndisc_init(void) { @@ -97,6 +118,20 @@ static inline int ndisc_timeout_check(void) { return 0; } + +void ip6_send_rs(void) +{ +} + +static inline bool validate_ra(struct ip6_hdr *ip6) +{ + return true; +} + +static inline int process_ra(struct ip6_hdr *ip6, int len) +{ + return 0; +} #endif
#endif /* __NDISC_H__ */ diff --git a/include/net.h b/include/net.h index 8ba50a0..58774f6 100644 --- a/include/net.h +++ b/include/net.h @@ -505,7 +505,8 @@ extern int net_restart_wrap; /* Tried all network devices */
enum proto_t { BOOTP, RARP, ARP, TFTPGET, DHCP, DHCP6, PING, PING6, DNS, NFS, CDP, - NETCONS, SNTP, TFTPSRV, TFTPPUT, LINKLOCAL, FASTBOOT, WOL, UDP, NCSI, WGET + NETCONS, SNTP, TFTPSRV, TFTPPUT, LINKLOCAL, FASTBOOT, WOL, UDP, NCSI, + WGET, RS };
extern char net_boot_file_name[1024];/* Boot File name */ diff --git a/include/net6.h b/include/net6.h index 2d7c5a0..beafc05 100644 --- a/include/net6.h +++ b/include/net6.h @@ -81,8 +81,17 @@ struct udp_hdr { 0x00, 0x00, 0x00, 0x00, \ 0x00, 0x00, 0x00, 0x00, \ 0x00, 0x00, 0x00, 0x00 } } } +/* + * All-routers multicast address is the link-local scope address to reach all + * routers. + */ +#define ALL_ROUTERS_MULT_ADDR { { { 0xFF, 0x02, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x02 } } }
#define IPV6_LINK_LOCAL_PREFIX 0xfe80 +#define IPV6_LINK_LOCAL_MASK 0xffb0 /* The first 10-bit of address mask. */
/* hop limit for neighbour discovery packets */ #define IPV6_NDISC_HOPLIMIT 255 @@ -166,6 +175,37 @@ struct icmp6hdr { #define icmp6_rt_lifetime icmp6_dataun.u_nd_ra.rt_lifetime } __packed;
+/* + * struct icmp6_ra_prefix_info - Prefix Information option of the ICMPv6 message + * The Prefix Information option provides hosts with on-link prefixes and + * prefixes for Address Autoconfiguration. Refer to RFC 4861 for more info. + */ +struct icmp6_ra_prefix_info { + u8 type; /* Type is 3 for Prefix Information. */ + u8 len; /* Len is 4 for Prefix Information. */ + /* The number of leading bits in the Prefix that are valid. */ + u8 prefix_len; + u8 reserved1:6, /* MUST be ignored by the receiver. */ + aac:1, /* autonomous address-configuration flag */ + /* Indicates that this prefix can be used for on-link determination. */ + on_link:1; + /* + * The length of time in seconds that the prefix is valid for the + * purpose of on-link determination. + */ + __be32 valid_lifetime; + /* The length of time addresses remain preferred. */ + __be32 preferred_lifetime; + __be32 reserved2; /* MUST be ignored by the receiver. */ + /* + * Prefix is an IP address or a prefix of an IP address. The Prefix + * Length field contains the number of valid leading bits in the prefix. + * The bits in the prefix after the prefix length are reserved and MUST + * be initialized to zero by the sender and ignored by the receiver. + */ + struct in6_addr prefix; +}; + extern struct in6_addr const net_null_addr_ip6; /* NULL IPv6 address */ extern struct in6_addr net_gateway6; /* Our gateways IPv6 address */ extern struct in6_addr net_ip6; /* Our IPv6 addr (0 = unknown) */ diff --git a/net/ndisc.c b/net/ndisc.c index 367dae7..0b27779 100644 --- a/net/ndisc.c +++ b/net/ndisc.c @@ -13,6 +13,8 @@ #include <net.h> #include <net6.h> #include <ndisc.h> +#include <stdlib.h> +#include <linux/delay.h>
/* IPv6 destination address of packet waiting for ND */ struct in6_addr net_nd_sol_packet_ip6 = ZERO_IPV6_ADDR; @@ -29,31 +31,37 @@ int net_nd_tx_packet_size; ulong net_nd_timer_start; /* the number of requests we have sent so far */ int net_nd_try; +struct in6_addr all_routers = ALL_ROUTERS_MULT_ADDR; + +#define MAX_RTR_SOLICITATIONS 3 +/* The maximum time to delay sending the first router solicitation message. */ +#define MAX_SOLICITATION_DELAY 1 // 1 second +/* The time to wait before sending the next router solicitation message. */ +#define RTR_SOLICITATION_INTERVAL 4000 // 4 seconds
#define IP6_NDISC_OPT_SPACE(len) (((len) + 2 + 7) & ~7)
/** * ndisc_insert_option() - Insert an option into a neighbor discovery packet * - * @ndisc: pointer to ND packet + * @opt: pointer to the option element of the neighbor discovery packet * @type: option type to insert * @data: option data to insert * @len: data length * Return: the number of bytes inserted (which may be >= len) */ -static int -ndisc_insert_option(struct nd_msg *ndisc, int type, u8 *data, int len) +static int ndisc_insert_option(__u8 *opt, int type, u8 *data, int len) { int space = IP6_NDISC_OPT_SPACE(len);
- ndisc->opt[0] = type; - ndisc->opt[1] = space >> 3; - memcpy(&ndisc->opt[2], data, len); + opt[0] = type; + opt[1] = space >> 3; + memcpy(&opt[2], data, len); len += 2;
/* fill the remainder with 0 */ if (space - len > 0) - memset(&ndisc->opt[len], '\0', space - len); + memset(&opt[len], '\0', space - len);
return space; } @@ -123,7 +131,7 @@ static void ip6_send_ns(struct in6_addr *neigh_addr)
/* Set the target address and llsaddr option */ net_copy_ip6(&msg->target, neigh_addr); - ndisc_insert_option(msg, ND_OPT_SOURCE_LL_ADDR, net_ethaddr, + ndisc_insert_option(msg->opt, ND_OPT_SOURCE_LL_ADDR, net_ethaddr, INETHADDRSZ);
/* checksum */ @@ -137,6 +145,76 @@ static void ip6_send_ns(struct in6_addr *neigh_addr) net_send_packet(net_tx_packet, (pkt - net_tx_packet)); }
+/* + * ip6_send_rs() - Send IPv6 Router Solicitation Message. + * + * A router solicitation is sent to discover a router. RS message creation is + * based on RFC 4861 section 4.1. Router Solicitation Message Format. + */ +void ip6_send_rs(void) +{ + unsigned char enetaddr[6]; + struct rs_msg *msg; + __u16 icmp_len; + uchar *pkt; + unsigned short csum; + unsigned int pcsum; + static unsigned int retry_count; + + if (!ip6_is_unspecified_addr(&net_gateway6) && + net_prefix_length != 0) { + net_set_state(NETLOOP_SUCCESS); + return; + } else if (retry_count >= MAX_RTR_SOLICITATIONS) { + net_set_state(NETLOOP_FAIL); + net_set_timeout_handler(0, NULL); + retry_count = 0; + return; + } + + printf("ROUTER SOLICITATION %d\n", retry_count + 1); + + ip6_make_mult_ethdstaddr(enetaddr, &all_routers); + /* + * ICMP length is the size of ICMP header (8) + one option (8) = 16. + * The option is 2 bytes of type and length + 6 bytes for MAC. + */ + icmp_len = sizeof(struct icmp6hdr) + IP6_NDISC_OPT_SPACE(INETHADDRSZ); + + pkt = (uchar *)net_tx_packet; + pkt += net_set_ether(pkt, enetaddr, PROT_IP6); + pkt += ip6_add_hdr(pkt, &net_link_local_ip6, &all_routers, PROT_ICMPV6, + IPV6_NDISC_HOPLIMIT, icmp_len); + + /* ICMPv6 - RS */ + msg = (struct rs_msg *)pkt; + msg->icmph.icmp6_type = IPV6_NDISC_ROUTER_SOLICITATION; + msg->icmph.icmp6_code = 0; + memset(&msg->icmph.icmp6_cksum, 0, sizeof(__be16)); + memset(&msg->icmph.icmp6_unused, 0, sizeof(__be32)); + + /* Set the llsaddr option */ + ndisc_insert_option(msg->opt, ND_OPT_SOURCE_LL_ADDR, net_ethaddr, + INETHADDRSZ); + + /* checksum */ + pcsum = csum_partial((__u8 *)msg, icmp_len, 0); + csum = csum_ipv6_magic(&net_link_local_ip6, &all_routers, + icmp_len, PROT_ICMPV6, pcsum); + msg->icmph.icmp6_cksum = csum; + pkt += icmp_len; + + /* Wait up to 1 second if it is the first try to get the RA */ + if (retry_count == 0) + udelay(((unsigned int)rand() % 1000000) * MAX_SOLICITATION_DELAY); + + /* send it! */ + net_send_packet(net_tx_packet, (pkt - net_tx_packet)); + + retry_count++; + net_set_timeout_handler(RTR_SOLICITATION_INTERVAL, ip6_send_rs); +} + static void ip6_send_na(uchar *eth_dst_addr, struct in6_addr *neigh_addr, struct in6_addr *target) @@ -167,7 +245,7 @@ ip6_send_na(uchar *eth_dst_addr, struct in6_addr *neigh_addr, msg->icmph.icmp6_dataun.u_nd_advt.override = 1; /* Set the target address and lltargetaddr option */ net_copy_ip6(&msg->target, target); - ndisc_insert_option(msg, ND_OPT_TARGET_LL_ADDR, net_ethaddr, + ndisc_insert_option(msg->opt, ND_OPT_TARGET_LL_ADDR, net_ethaddr, INETHADDRSZ);
/* checksum */ @@ -223,6 +301,10 @@ int ndisc_timeout_check(void) return 1; }
+/* + * ndisc_init() - Make initial steps for ND state machine. + * Usually move variables into initial state. + */ void ndisc_init(void) { net_nd_packet_mac = NULL; @@ -234,12 +316,125 @@ void ndisc_init(void) net_nd_tx_packet -= (ulong)net_nd_tx_packet % PKTALIGN; }
+/* + * validate_ra() - Validate the router advertisement message. + * + * @ip6: Pointer to the router advertisement packet + * + * Check if the router advertisement message is valid. Conditions are + * according to RFC 4861 section 6.1.2. Validation of Router Advertisement + * Messages. + * + * Return: true if the message is valid and false if it is invalid. + */ +bool validate_ra(struct ip6_hdr *ip6) +{ + struct icmp6hdr *icmp = (struct icmp6hdr *)(ip6 + 1); + + /* ICMP length (derived from the IP length) should be 16 or more octets. */ + if (ip6->payload_len < 16) + return false; + + /* Source IP Address should be a valid link-local address. */ + if ((ntohs(ip6->saddr.s6_addr16[0]) & IPV6_LINK_LOCAL_MASK) != + IPV6_LINK_LOCAL_PREFIX) + return false; + + /* + * The IP Hop Limit field should have a value of 255, i.e., the packet + * could not possibly have been forwarded by a router. + */ + if (ip6->hop_limit != 255) + return false; + + /* ICMP checksum has already been checked in net_ip6_handler. */ + + if (icmp->icmp6_code != 0) + return false; + + return true; +} + +/* + * process_ra() - Process the router advertisement packet. + * + * @ip6: Pointer to the router advertisement packet + * @len: Length of the router advertisement packet + * + * Process the received router advertisement message. + * Although RFC 4861 requires retaining at least two router addresses, we only + * keep one because of the U-Boot limitations and its goal of lightweight code. + * + * Return: 0 - RA is a default router and contains valid prefix information. + * Non-zero - RA options are invalid or do not indicate it is a default router + * or do not contain valid prefix information. + */ +int process_ra(struct ip6_hdr *ip6, int len) +{ + /* Pointer to the ICMP section of the packet */ + struct icmp6hdr *icmp = (struct icmp6hdr *)(ip6 + 1); + struct ra_msg *msg = (struct ra_msg *)icmp; + int remaining_option_len = len - IP6_HDR_SIZE - sizeof(struct ra_msg); + unsigned short int option_len; /* Length of each option */ + /* Pointer to the ICMPv6 message options */ + unsigned char *option = NULL; + /* 8-bit identifier of the type of ICMPv6 option */ + unsigned char type = 0; + struct icmp6_ra_prefix_info *prefix = NULL; + + /* Ignore the packet if router lifetime is 0. */ + if (!icmp->icmp6_rt_lifetime) + return -EOPNOTSUPP; + + /* Processing the options */ + option = msg->opt; + while (remaining_option_len > 0) { + /* The 2nd byte of the option is its length. */ + option_len = option[1]; + /* All included options should have a positive length. */ + if (option_len == 0) + return -EINVAL; + + type = option[0]; + /* All option types except Prefix Information are ignored. */ + switch (type) { + case ND_OPT_SOURCE_LL_ADDR: + case ND_OPT_TARGET_LL_ADDR: + case ND_OPT_REDIRECT_HDR: + case ND_OPT_MTU: + break; + case ND_OPT_PREFIX_INFO: + prefix = (struct icmp6_ra_prefix_info *)option; + /* The link-local prefix 0xfe80::/10 is ignored. */ + if ((ntohs(prefix->prefix.s6_addr16[0]) & + IPV6_LINK_LOCAL_MASK) == IPV6_LINK_LOCAL_PREFIX) + break; + if (prefix->on_link && ntohl(prefix->valid_lifetime)) { + net_prefix_length = prefix->prefix_len; + net_gateway6 = ip6->saddr; + return 0; + } + break; + default: + debug("Unknown IPv6 Neighbor Discovery Option 0x%x\n", + type); + } + + option_len <<= 3; /* Option length is a multiple of 8. */ + remaining_option_len -= option_len; + option += option_len; + } + + return -EADDRNOTAVAIL; +} + int ndisc_receive(struct ethernet_hdr *et, struct ip6_hdr *ip6, int len) { struct icmp6hdr *icmp = (struct icmp6hdr *)(((uchar *)ip6) + IP6_HDR_SIZE); struct nd_msg *ndisc = (struct nd_msg *)icmp; uchar neigh_eth_addr[6]; + int err = 0; // The error code returned calling functions.
switch (icmp->icmp6_type) { case IPV6_NDISC_NEIGHBOUR_SOLICITATION: @@ -280,6 +475,36 @@ int ndisc_receive(struct ethernet_hdr *et, struct ip6_hdr *ip6, int len) net_nd_packet_mac = NULL; } break; + case IPV6_NDISC_ROUTER_SOLICITATION: + break; + case IPV6_NDISC_ROUTER_ADVERTISEMENT: + debug("Received router advertisement for %pI6c from %pI6c\n", + &ip6->daddr, &ip6->saddr); + /* + * If gateway and prefix are set, the RA packet is ignored. The + * reason is that the U-Boot code is supposed to be as compact + * as possible and does not need to take care of multiple + * routers. In addition to that, U-Boot does not want to handle + * scenarios like a router setting its lifetime to zero to + * indicate it is not routing anymore. U-Boot program has a + * short life when the system boots up and does not need such + * sophistication. + */ + if (!ip6_is_unspecified_addr(&net_gateway6) && + net_prefix_length != 0) { + break; + } + if (!validate_ra(ip6)) { + debug("Invalid router advertisement message.\n"); + break; + } + err = process_ra(ip6, len); + if (err) + debug("Ignored router advertisement. Error: %d\n", err); + else + printf("Set gatewayip6: %pI6c, prefix_length: %d\n", + &net_gateway6, net_prefix_length); + break; default: debug("Unexpected ICMPv6 type 0x%x\n", icmp->icmp6_type); return -1; diff --git a/net/net.c b/net/net.c index d69bfb0..abdb7e4 100644 --- a/net/net.c +++ b/net/net.c @@ -24,7 +24,7 @@ * - name of bootfile * Next step: ARP * - * LINK_LOCAL: + * LINKLOCAL: * * Prerequisites: - own ethernet address * We want: - own IP address @@ -125,6 +125,7 @@ #if defined(CONFIG_CMD_DHCP6) #include "dhcpv6.h" #endif +#include "net_rand.h"
/** BOOTP EXTENTIONS **/
@@ -349,6 +350,8 @@ void net_auto_load(void)
static int net_init_loop(void) { + static bool first_call = true; + if (eth_get_dev()) { memcpy(net_ethaddr, eth_get_ethaddr(), 6);
@@ -368,6 +371,12 @@ static int net_init_loop(void) */ return -ENONET;
+ if (IS_ENABLED(CONFIG_IPV6_ROUTER_DISCOVERY)) + if (first_call && use_ip6) { + first_call = false; + srand_mac(); /* This is for rand used in ip6_send_rs. */ + net_loop(RS); + } return 0; }
@@ -577,6 +586,10 @@ restart: ncsi_probe_packages(); break; #endif + case RS: + if (IS_ENABLED(CONFIG_IPV6_ROUTER_DISCOVERY)) + ip6_send_rs(); + break; default: break; } @@ -674,7 +687,13 @@ restart: x = time_handler; time_handler = (thand_f *)0; (*x)(); - } + } else if (IS_ENABLED(CONFIG_IPV6_ROUTER_DISCOVERY)) + if (time_handler && protocol == RS) + if (!ip6_is_unspecified_addr(&net_gateway6) && + net_prefix_length != 0) { + net_set_state(NETLOOP_SUCCESS); + net_set_timeout_handler(0, NULL); + }
if (net_state == NETLOOP_FAIL) ret = net_start_again(); diff --git a/net/net6.c b/net/net6.c index 75577bc..2dd64c0 100644 --- a/net/net6.c +++ b/net/net6.c @@ -413,6 +413,7 @@ int net_ip6_handler(struct ethernet_hdr *et, struct ip6_hdr *ip6, int len) break; case IPV6_NDISC_NEIGHBOUR_SOLICITATION: case IPV6_NDISC_NEIGHBOUR_ADVERTISEMENT: + case IPV6_NDISC_ROUTER_ADVERTISEMENT: ndisc_receive(et, ip6, len); break; default:

Hello Ehsan! I tried to apply your patches and found out that there are some conflicts. I think you use your custom version. Check it please.
Thanks!
On Wed, 2023-04-12 at 09:10 -0700, emohandesi@linux.microsoft.co wrote:
From: Ehsan Mohandesi emohandesi@microsoft.com
#endif /* __NDISC_H__ */ diff --git a/include/net.h b/include/net.h index 8ba50a0..58774f6 100644 --- a/include/net.h +++ b/include/net.h @@ -505,7 +505,8 @@ extern int net_restart_wrap; /* Tried all network devices */
enum proto_t { BOOTP, RARP, ARP, TFTPGET, DHCP, DHCP6, PING, PING6, DNS,
Here DHCP6
diff --git a/net/net.c b/net/net.c index d69bfb0..abdb7e4 100644 --- a/net/net.c +++ b/net/net.c @@ -24,7 +24,7 @@
- name of bootfile
Next step: ARP
- LINK_LOCAL:
- LINKLOCAL:
Prerequisites: - own ethernet address
We want: - own IP address
@@ -125,6 +125,7 @@ #if defined(CONFIG_CMD_DHCP6) #include "dhcpv6.h"
Here DHCP6
And maybe somewhere else

From: Ehsan Mohandesi emohandesi@microsoft.com
Test the IPv6 network discovery feature if indicated by boardenv file.
Signed-off-by: Ehsan Mohandesi emohandesi@microsoft.com
Conflicts: configs/sandbox64_defconfig configs/sandbox_defconfig configs/sandbox_flattree_defconfig --- configs/sandbox64_defconfig | 2 ++ configs/sandbox_defconfig | 2 ++ configs/sandbox_flattree_defconfig | 2 ++ test/py/tests/test_net.py | 31 ++++++++++++++++++++++++++++++- 4 files changed, 36 insertions(+), 1 deletion(-)
diff --git a/configs/sandbox64_defconfig b/configs/sandbox64_defconfig index af2c56a..be36ede 100644 --- a/configs/sandbox64_defconfig +++ b/configs/sandbox64_defconfig @@ -260,3 +260,5 @@ CONFIG_FWU_MULTI_BANK_UPDATE=y CONFIG_UNIT_TEST=y CONFIG_UT_TIME=y CONFIG_UT_DM=y +CONFIG_IPV6=y +CONFIG_IPV6_ROUTER_DISCOVERY=y diff --git a/configs/sandbox_defconfig b/configs/sandbox_defconfig index ca95b2c..0673c69 100644 --- a/configs/sandbox_defconfig +++ b/configs/sandbox_defconfig @@ -341,3 +341,5 @@ CONFIG_UNIT_TEST=y CONFIG_UT_TIME=y CONFIG_UT_DM=y CONFIG_CMD_2048=y +CONFIG_IPV6=y +CONFIG_IPV6_ROUTER_DISCOVERY=y diff --git a/configs/sandbox_flattree_defconfig b/configs/sandbox_flattree_defconfig index e9fcc5b..d6c8dd2 100644 --- a/configs/sandbox_flattree_defconfig +++ b/configs/sandbox_flattree_defconfig @@ -229,3 +229,5 @@ CONFIG_EFI_CAPSULE_FIRMWARE_FIT=y CONFIG_UNIT_TEST=y CONFIG_UT_TIME=y CONFIG_UT_DM=y +CONFIG_IPV6=y +CONFIG_IPV6_ROUTER_DISCOVERY=y diff --git a/test/py/tests/test_net.py b/test/py/tests/test_net.py index 9ca6743..f85071d 100644 --- a/test/py/tests/test_net.py +++ b/test/py/tests/test_net.py @@ -9,7 +9,7 @@ import u_boot_utils
""" Note: This test relies on boardenv_* containing configuration values to define -which the network environment available for testing. Without this, this test +which network environment is available for testing. Without this, this test will be automatically skipped.
For example: @@ -55,6 +55,11 @@ env__net_nfs_readable_file = { 'size': 5058624, 'crc32': 'c2244b26', } + +# True if a router advertisement service is connected to the network, and should +# be tested. If router advertisement testing is not possible or desired, this +variable may be omitted or set to False. +env__router_on_net = True """
net_set_up = False @@ -126,6 +131,30 @@ def test_net_ping(u_boot_console): output = u_boot_console.run_command('ping $serverip') assert 'is alive' in output
+@pytest.mark.buildconfigspec('IPV6_ROUTER_DISCOVERY') +def test_net_network_discovery(u_boot_console): + """Test the network discovery feature of IPv6. + + An IPv6 network command (ping6 in this case) is run to make U-Boot send a + router solicitation packet, receive a router advertisement message, and + parse it. + A router advertisement service needs to be running for this test to succeed. + U-Boot receives the RA, processes it, and if successful, assigns the gateway + IP and prefix length. + The configuration is provided by the boardenv_* file; see the comment at + the beginning of this file. + """ + + router_on_net = u_boot_console.config.env.get('env__router_on_net', False) + if not router_on_net: + pytest.skip('No router on network') + + fake_host_ip = 'fe80::215:5dff:fef6:2ec6' + output = u_boot_console.run_command('ping6 ' + fake_host_ip) + assert 'ROUTER SOLICITATION 1' in output + assert 'Set gatewayip6:' in output + assert '0000:0000:0000:0000:0000:0000:0000:0000' not in output + @pytest.mark.buildconfigspec('cmd_net') def test_net_tftpboot(u_boot_console): """Test the tftpboot command.

Hi,
On Wed, 12 Apr 2023 at 10:10, emohandesi@linux.microsoft.com wrote:
From: Ehsan Mohandesi emohandesi@microsoft.com
Test the IPv6 network discovery feature if indicated by boardenv file.
Signed-off-by: Ehsan Mohandesi emohandesi@microsoft.com
Conflicts: configs/sandbox64_defconfig configs/sandbox_defconfig configs/sandbox_flattree_defconfig
You can drop these
configs/sandbox64_defconfig | 2 ++ configs/sandbox_defconfig | 2 ++ configs/sandbox_flattree_defconfig | 2 ++ test/py/tests/test_net.py | 31 ++++++++++++++++++++++++++++++- 4 files changed, 36 insertions(+), 1 deletion(-)
Reviewed-by: Simon Glass sjg@chromium.org
Regards, Simon

From: Ehsan Mohandesi emohandesi@microsoft.com
Test router advertisement validation and processing functions.
Signed-off-by: Ehsan Mohandesi emohandesi@microsoft.com --- test/dm/eth.c | 88 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+)
diff --git a/test/dm/eth.c b/test/dm/eth.c index ebf01d8..d05d2a9 100644 --- a/test/dm/eth.c +++ b/test/dm/eth.c @@ -20,6 +20,7 @@ #include <dm/uclass-internal.h> #include <test/test.h> #include <test/ut.h> +#include <ndisc.h>
#define DM_TEST_ETH_NUM 4
@@ -607,3 +608,90 @@ static int dm_test_eth_async_ping_reply(struct unit_test_state *uts) }
DM_TEST(dm_test_eth_async_ping_reply, UT_TESTF_SCAN_FDT); + +#if IS_ENABLED(CONFIG_IPV6_ROUTER_DISCOVERY) + +static u8 ip6_ra_buf[] = {0x60, 0xf, 0xc5, 0x4a, 0x0, 0x38, 0x3a, 0xff, 0xfe, + 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x6, 0x85, 0xe6, + 0x29, 0x77, 0xcb, 0xc8, 0x53, 0xff, 0x2, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x1, 0x86, 0x0, 0xdc, 0x90, 0x40, 0x80, 0x15, 0x18, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3, 0x4, + 0x40, 0xc0, 0x0, 0x0, 0x37, 0xdc, 0x0, 0x0, 0x37, + 0x78, 0x0, 0x0, 0x0, 0x0, 0x20, 0x1, 0xca, 0xfe, 0xca, + 0xfe, 0xca, 0xfe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x1, 0x1, 0x0, 0x15, 0x5d, 0xe2, 0x8a, 0x2}; + +static int dm_test_validate_ra(struct unit_test_state *uts) +{ + struct ip6_hdr *ip6 = (struct ip6_hdr *)ip6_ra_buf; + struct icmp6hdr *icmp = (struct icmp6hdr *)(ip6 + 1); + __be16 temp = 0; + + ut_assert(validate_ra(ip6) == true); + + temp = ip6->payload_len; + ip6->payload_len = 15; + ut_assert(validate_ra(ip6) == false); + ip6->payload_len = temp; + + temp = ip6->saddr.s6_addr16[0]; + ip6->saddr.s6_addr16[0] = 0x2001; + ut_assert(validate_ra(ip6) == false); + ip6->saddr.s6_addr16[0] = temp; + + temp = ip6->hop_limit; + ip6->hop_limit = 15; + ut_assert(validate_ra(ip6) == false); + ip6->hop_limit = temp; + + temp = icmp->icmp6_code; + icmp->icmp6_code = 15; + ut_assert(validate_ra(ip6) == false); + icmp->icmp6_code = temp; + + return 0; +} + +DM_TEST(dm_test_validate_ra, 0); + +static int dm_test_process_ra(struct unit_test_state *uts) +{ + int len = sizeof(ip6_ra_buf); + struct ip6_hdr *ip6 = (struct ip6_hdr *)ip6_ra_buf; + struct icmp6hdr *icmp = (struct icmp6hdr *)(ip6 + 1); + struct ra_msg *msg = (struct ra_msg *)icmp; + unsigned char *option = msg->opt; + struct icmp6_ra_prefix_info *prefix = + (struct icmp6_ra_prefix_info *)option; + __be16 temp = 0; + unsigned char option_len = option[1]; + + ut_assert(process_ra(ip6, len) == 0); + + temp = icmp->icmp6_rt_lifetime; + icmp->icmp6_rt_lifetime = 0; + ut_assert(process_ra(ip6, len) != 0); + icmp->icmp6_rt_lifetime = temp; + + ut_assert(process_ra(ip6, 0) != 0); + + option[1] = 0; + ut_assert(process_ra(ip6, len) != 0); + option[1] = option_len; + + prefix->on_link = false; + ut_assert(process_ra(ip6, len) != 0); + prefix->on_link = true; + + temp = prefix->prefix.s6_addr16[0]; + prefix->prefix.s6_addr16[0] = 0x80fe; + ut_assert(process_ra(ip6, len) != 0); + prefix->prefix.s6_addr16[0] = temp; + + return 0; +} + +DM_TEST(dm_test_process_ra, 0); + +#endif

On Wed, 12 Apr 2023 at 10:10, emohandesi@linux.microsoft.com wrote:
From: Ehsan Mohandesi emohandesi@microsoft.com
Test router advertisement validation and processing functions.
Signed-off-by: Ehsan Mohandesi emohandesi@microsoft.com
test/dm/eth.c | 88 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+)
Reviewed-by: Simon Glass sjg@chromium.org

From: Ehsan Mohandesi emohandesi@linux.microsoft.com
This series adds IPv6 network discovery to U-Boot. When an IPv6 command is run in U-Boot, it sends a router solicitation (RS) message to the network. The router on the network responds with a router advertisement (RA) message. Then U-Boot processes the RA message and sets the gatewayip6 and net_prefix_length environment variables. It is based on RFC 4861, but not everything in the RFC is supported here. https://www.rfc-editor.org/rfc/rfc4861
Changes in v4: - Removed the changes that were mistakenly pulled from the local workspace.
Changes in v3: - Removed the extra revert commit that was mistakenly added in v2.
Changes in v2: - Improved IPv6 network discovery code. - Added IPv6 network discovery feature test (Python test). - Added unit tests (C code).
Ehsan Mohandesi (3): net: ipv6: Add support for default gateway discovery. test/py: IPv6 network discovery test test: eth: IPv6 network discovery unit test
cmd/Kconfig | 6 + configs/sandbox64_defconfig | 2 + configs/sandbox_defconfig | 2 + configs/sandbox_flattree_defconfig | 2 + include/ndisc.h | 35 ++++++ include/net.h | 2 +- include/net6.h | 40 ++++++ net/ndisc.c | 243 +++++++++++++++++++++++++++++++++++-- net/net.c | 23 +++- net/net6.c | 1 + test/dm/eth.c | 88 ++++++++++++++ test/py/tests/test_net.py | 31 ++++- 12 files changed, 462 insertions(+), 13 deletions(-)

From: Ehsan Mohandesi emohandesi@linux.microsoft.com
In IPv6, the default gateway and prefix length are determined by receiving a router advertisement as defined in - https://www.rfc-editor.org/rfc/rfc4861.
Add support for sending router solicitation (RS) and processing router advertisements (RA).
If the RA has prefix info option and following conditions are met, then gatewayip6 and net_prefix_length of ip6addr env variables are initialized. These are later consumed by IPv6 code for non-local destination IP.
- "Router Lifetime" != 0 - Prefix is NOT link-local prefix (0xfe80::/10) - L flag is 1 - "Valid Lifetime" != 0
Timing Parameters: - MAX_RTR_SOLICITATION_DELAY (0-1s) - RTR_SOLICITATION_INTERVAL (4s) (min retransmit delay) - MAX_RTR_SOLICITATIONS (3 RS transmissions)
The functionality is enabled by CONFIG_IPV6_ROUTER_DISCOVERY and invoked automatically from net_init_loop().
Signed-off-by: Ehsan Mohandesi emohandesi@linux.microsoft.com --- cmd/Kconfig | 6 ++ include/ndisc.h | 35 ++++++++ include/net.h | 2 +- include/net6.h | 40 ++++++++++ net/ndisc.c | 243 +++++++++++++++++++++++++++++++++++++++++++++++++++++--- net/net.c | 23 +++++- net/net6.c | 1 + 7 files changed, 338 insertions(+), 12 deletions(-)
diff --git a/cmd/Kconfig b/cmd/Kconfig index e45b884..6919d31 100644 --- a/cmd/Kconfig +++ b/cmd/Kconfig @@ -1916,6 +1916,12 @@ config CMD_NCSI Normally this happens automatically before other network operations.
+config IPV6_ROUTER_DISCOVERY + bool "Do IPv6 router discovery" + depends on IPV6 + help + Will automatically perform router solicitation on first IPv6 + network operation endif
config CMD_ETHSW diff --git a/include/ndisc.h b/include/ndisc.h index f6f8eb6..12fa9e7 100644 --- a/include/ndisc.h +++ b/include/ndisc.h @@ -19,6 +19,20 @@ struct nd_msg { __u8 opt[0]; };
+/* struct rs_msg - ICMPv6 Router Solicitation message format */ +struct rs_msg { + struct icmp6hdr icmph; + __u8 opt[0]; +}; + +/* struct ra_msg - ICMPv6 Router Advertisement message format */ +struct ra_msg { + struct icmp6hdr icmph; + __u32 reachable_time; + __u32 retransmission_timer; + __u8 opt[0]; +}; + /* struct echo_msg - ICMPv6 echo request/reply message format */ struct echo_msg { struct icmp6hdr icmph; @@ -57,6 +71,11 @@ extern int net_nd_try; */ void ndisc_init(void);
+/* + * ip6_send_rs() - Send IPv6 Router Solicitation Message + */ +void ip6_send_rs(void); + /** * ndisc_receive() - Handle ND packet * @@ -78,6 +97,8 @@ void ndisc_request(void); * Return: 0 if no timeout, -1 otherwise */ int ndisc_timeout_check(void); +bool validate_ra(struct ip6_hdr *ip6); +int process_ra(struct ip6_hdr *ip6, int len); #else static inline void ndisc_init(void) { @@ -97,6 +118,20 @@ static inline int ndisc_timeout_check(void) { return 0; } + +void ip6_send_rs(void) +{ +} + +static inline bool validate_ra(struct ip6_hdr *ip6) +{ + return true; +} + +static inline int process_ra(struct ip6_hdr *ip6, int len) +{ + return 0; +} #endif
#endif /* __NDISC_H__ */ diff --git a/include/net.h b/include/net.h index 399af5e..25c43b3 100644 --- a/include/net.h +++ b/include/net.h @@ -505,7 +505,7 @@ extern int net_restart_wrap; /* Tried all network devices */
enum proto_t { BOOTP, RARP, ARP, TFTPGET, DHCP, PING, PING6, DNS, NFS, CDP, NETCONS, - SNTP, TFTPSRV, TFTPPUT, LINKLOCAL, FASTBOOT, WOL, UDP, NCSI, WGET + SNTP, TFTPSRV, TFTPPUT, LINKLOCAL, FASTBOOT, WOL, UDP, NCSI, WGET, RS };
extern char net_boot_file_name[1024];/* Boot File name */ diff --git a/include/net6.h b/include/net6.h index 2d7c5a0..beafc05 100644 --- a/include/net6.h +++ b/include/net6.h @@ -81,8 +81,17 @@ struct udp_hdr { 0x00, 0x00, 0x00, 0x00, \ 0x00, 0x00, 0x00, 0x00, \ 0x00, 0x00, 0x00, 0x00 } } } +/* + * All-routers multicast address is the link-local scope address to reach all + * routers. + */ +#define ALL_ROUTERS_MULT_ADDR { { { 0xFF, 0x02, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x02 } } }
#define IPV6_LINK_LOCAL_PREFIX 0xfe80 +#define IPV6_LINK_LOCAL_MASK 0xffb0 /* The first 10-bit of address mask. */
/* hop limit for neighbour discovery packets */ #define IPV6_NDISC_HOPLIMIT 255 @@ -166,6 +175,37 @@ struct icmp6hdr { #define icmp6_rt_lifetime icmp6_dataun.u_nd_ra.rt_lifetime } __packed;
+/* + * struct icmp6_ra_prefix_info - Prefix Information option of the ICMPv6 message + * The Prefix Information option provides hosts with on-link prefixes and + * prefixes for Address Autoconfiguration. Refer to RFC 4861 for more info. + */ +struct icmp6_ra_prefix_info { + u8 type; /* Type is 3 for Prefix Information. */ + u8 len; /* Len is 4 for Prefix Information. */ + /* The number of leading bits in the Prefix that are valid. */ + u8 prefix_len; + u8 reserved1:6, /* MUST be ignored by the receiver. */ + aac:1, /* autonomous address-configuration flag */ + /* Indicates that this prefix can be used for on-link determination. */ + on_link:1; + /* + * The length of time in seconds that the prefix is valid for the + * purpose of on-link determination. + */ + __be32 valid_lifetime; + /* The length of time addresses remain preferred. */ + __be32 preferred_lifetime; + __be32 reserved2; /* MUST be ignored by the receiver. */ + /* + * Prefix is an IP address or a prefix of an IP address. The Prefix + * Length field contains the number of valid leading bits in the prefix. + * The bits in the prefix after the prefix length are reserved and MUST + * be initialized to zero by the sender and ignored by the receiver. + */ + struct in6_addr prefix; +}; + extern struct in6_addr const net_null_addr_ip6; /* NULL IPv6 address */ extern struct in6_addr net_gateway6; /* Our gateways IPv6 address */ extern struct in6_addr net_ip6; /* Our IPv6 addr (0 = unknown) */ diff --git a/net/ndisc.c b/net/ndisc.c index 367dae7..0b27779 100644 --- a/net/ndisc.c +++ b/net/ndisc.c @@ -13,6 +13,8 @@ #include <net.h> #include <net6.h> #include <ndisc.h> +#include <stdlib.h> +#include <linux/delay.h>
/* IPv6 destination address of packet waiting for ND */ struct in6_addr net_nd_sol_packet_ip6 = ZERO_IPV6_ADDR; @@ -29,31 +31,37 @@ int net_nd_tx_packet_size; ulong net_nd_timer_start; /* the number of requests we have sent so far */ int net_nd_try; +struct in6_addr all_routers = ALL_ROUTERS_MULT_ADDR; + +#define MAX_RTR_SOLICITATIONS 3 +/* The maximum time to delay sending the first router solicitation message. */ +#define MAX_SOLICITATION_DELAY 1 // 1 second +/* The time to wait before sending the next router solicitation message. */ +#define RTR_SOLICITATION_INTERVAL 4000 // 4 seconds
#define IP6_NDISC_OPT_SPACE(len) (((len) + 2 + 7) & ~7)
/** * ndisc_insert_option() - Insert an option into a neighbor discovery packet * - * @ndisc: pointer to ND packet + * @opt: pointer to the option element of the neighbor discovery packet * @type: option type to insert * @data: option data to insert * @len: data length * Return: the number of bytes inserted (which may be >= len) */ -static int -ndisc_insert_option(struct nd_msg *ndisc, int type, u8 *data, int len) +static int ndisc_insert_option(__u8 *opt, int type, u8 *data, int len) { int space = IP6_NDISC_OPT_SPACE(len);
- ndisc->opt[0] = type; - ndisc->opt[1] = space >> 3; - memcpy(&ndisc->opt[2], data, len); + opt[0] = type; + opt[1] = space >> 3; + memcpy(&opt[2], data, len); len += 2;
/* fill the remainder with 0 */ if (space - len > 0) - memset(&ndisc->opt[len], '\0', space - len); + memset(&opt[len], '\0', space - len);
return space; } @@ -123,7 +131,7 @@ static void ip6_send_ns(struct in6_addr *neigh_addr)
/* Set the target address and llsaddr option */ net_copy_ip6(&msg->target, neigh_addr); - ndisc_insert_option(msg, ND_OPT_SOURCE_LL_ADDR, net_ethaddr, + ndisc_insert_option(msg->opt, ND_OPT_SOURCE_LL_ADDR, net_ethaddr, INETHADDRSZ);
/* checksum */ @@ -137,6 +145,76 @@ static void ip6_send_ns(struct in6_addr *neigh_addr) net_send_packet(net_tx_packet, (pkt - net_tx_packet)); }
+/* + * ip6_send_rs() - Send IPv6 Router Solicitation Message. + * + * A router solicitation is sent to discover a router. RS message creation is + * based on RFC 4861 section 4.1. Router Solicitation Message Format. + */ +void ip6_send_rs(void) +{ + unsigned char enetaddr[6]; + struct rs_msg *msg; + __u16 icmp_len; + uchar *pkt; + unsigned short csum; + unsigned int pcsum; + static unsigned int retry_count; + + if (!ip6_is_unspecified_addr(&net_gateway6) && + net_prefix_length != 0) { + net_set_state(NETLOOP_SUCCESS); + return; + } else if (retry_count >= MAX_RTR_SOLICITATIONS) { + net_set_state(NETLOOP_FAIL); + net_set_timeout_handler(0, NULL); + retry_count = 0; + return; + } + + printf("ROUTER SOLICITATION %d\n", retry_count + 1); + + ip6_make_mult_ethdstaddr(enetaddr, &all_routers); + /* + * ICMP length is the size of ICMP header (8) + one option (8) = 16. + * The option is 2 bytes of type and length + 6 bytes for MAC. + */ + icmp_len = sizeof(struct icmp6hdr) + IP6_NDISC_OPT_SPACE(INETHADDRSZ); + + pkt = (uchar *)net_tx_packet; + pkt += net_set_ether(pkt, enetaddr, PROT_IP6); + pkt += ip6_add_hdr(pkt, &net_link_local_ip6, &all_routers, PROT_ICMPV6, + IPV6_NDISC_HOPLIMIT, icmp_len); + + /* ICMPv6 - RS */ + msg = (struct rs_msg *)pkt; + msg->icmph.icmp6_type = IPV6_NDISC_ROUTER_SOLICITATION; + msg->icmph.icmp6_code = 0; + memset(&msg->icmph.icmp6_cksum, 0, sizeof(__be16)); + memset(&msg->icmph.icmp6_unused, 0, sizeof(__be32)); + + /* Set the llsaddr option */ + ndisc_insert_option(msg->opt, ND_OPT_SOURCE_LL_ADDR, net_ethaddr, + INETHADDRSZ); + + /* checksum */ + pcsum = csum_partial((__u8 *)msg, icmp_len, 0); + csum = csum_ipv6_magic(&net_link_local_ip6, &all_routers, + icmp_len, PROT_ICMPV6, pcsum); + msg->icmph.icmp6_cksum = csum; + pkt += icmp_len; + + /* Wait up to 1 second if it is the first try to get the RA */ + if (retry_count == 0) + udelay(((unsigned int)rand() % 1000000) * MAX_SOLICITATION_DELAY); + + /* send it! */ + net_send_packet(net_tx_packet, (pkt - net_tx_packet)); + + retry_count++; + net_set_timeout_handler(RTR_SOLICITATION_INTERVAL, ip6_send_rs); +} + static void ip6_send_na(uchar *eth_dst_addr, struct in6_addr *neigh_addr, struct in6_addr *target) @@ -167,7 +245,7 @@ ip6_send_na(uchar *eth_dst_addr, struct in6_addr *neigh_addr, msg->icmph.icmp6_dataun.u_nd_advt.override = 1; /* Set the target address and lltargetaddr option */ net_copy_ip6(&msg->target, target); - ndisc_insert_option(msg, ND_OPT_TARGET_LL_ADDR, net_ethaddr, + ndisc_insert_option(msg->opt, ND_OPT_TARGET_LL_ADDR, net_ethaddr, INETHADDRSZ);
/* checksum */ @@ -223,6 +301,10 @@ int ndisc_timeout_check(void) return 1; }
+/* + * ndisc_init() - Make initial steps for ND state machine. + * Usually move variables into initial state. + */ void ndisc_init(void) { net_nd_packet_mac = NULL; @@ -234,12 +316,125 @@ void ndisc_init(void) net_nd_tx_packet -= (ulong)net_nd_tx_packet % PKTALIGN; }
+/* + * validate_ra() - Validate the router advertisement message. + * + * @ip6: Pointer to the router advertisement packet + * + * Check if the router advertisement message is valid. Conditions are + * according to RFC 4861 section 6.1.2. Validation of Router Advertisement + * Messages. + * + * Return: true if the message is valid and false if it is invalid. + */ +bool validate_ra(struct ip6_hdr *ip6) +{ + struct icmp6hdr *icmp = (struct icmp6hdr *)(ip6 + 1); + + /* ICMP length (derived from the IP length) should be 16 or more octets. */ + if (ip6->payload_len < 16) + return false; + + /* Source IP Address should be a valid link-local address. */ + if ((ntohs(ip6->saddr.s6_addr16[0]) & IPV6_LINK_LOCAL_MASK) != + IPV6_LINK_LOCAL_PREFIX) + return false; + + /* + * The IP Hop Limit field should have a value of 255, i.e., the packet + * could not possibly have been forwarded by a router. + */ + if (ip6->hop_limit != 255) + return false; + + /* ICMP checksum has already been checked in net_ip6_handler. */ + + if (icmp->icmp6_code != 0) + return false; + + return true; +} + +/* + * process_ra() - Process the router advertisement packet. + * + * @ip6: Pointer to the router advertisement packet + * @len: Length of the router advertisement packet + * + * Process the received router advertisement message. + * Although RFC 4861 requires retaining at least two router addresses, we only + * keep one because of the U-Boot limitations and its goal of lightweight code. + * + * Return: 0 - RA is a default router and contains valid prefix information. + * Non-zero - RA options are invalid or do not indicate it is a default router + * or do not contain valid prefix information. + */ +int process_ra(struct ip6_hdr *ip6, int len) +{ + /* Pointer to the ICMP section of the packet */ + struct icmp6hdr *icmp = (struct icmp6hdr *)(ip6 + 1); + struct ra_msg *msg = (struct ra_msg *)icmp; + int remaining_option_len = len - IP6_HDR_SIZE - sizeof(struct ra_msg); + unsigned short int option_len; /* Length of each option */ + /* Pointer to the ICMPv6 message options */ + unsigned char *option = NULL; + /* 8-bit identifier of the type of ICMPv6 option */ + unsigned char type = 0; + struct icmp6_ra_prefix_info *prefix = NULL; + + /* Ignore the packet if router lifetime is 0. */ + if (!icmp->icmp6_rt_lifetime) + return -EOPNOTSUPP; + + /* Processing the options */ + option = msg->opt; + while (remaining_option_len > 0) { + /* The 2nd byte of the option is its length. */ + option_len = option[1]; + /* All included options should have a positive length. */ + if (option_len == 0) + return -EINVAL; + + type = option[0]; + /* All option types except Prefix Information are ignored. */ + switch (type) { + case ND_OPT_SOURCE_LL_ADDR: + case ND_OPT_TARGET_LL_ADDR: + case ND_OPT_REDIRECT_HDR: + case ND_OPT_MTU: + break; + case ND_OPT_PREFIX_INFO: + prefix = (struct icmp6_ra_prefix_info *)option; + /* The link-local prefix 0xfe80::/10 is ignored. */ + if ((ntohs(prefix->prefix.s6_addr16[0]) & + IPV6_LINK_LOCAL_MASK) == IPV6_LINK_LOCAL_PREFIX) + break; + if (prefix->on_link && ntohl(prefix->valid_lifetime)) { + net_prefix_length = prefix->prefix_len; + net_gateway6 = ip6->saddr; + return 0; + } + break; + default: + debug("Unknown IPv6 Neighbor Discovery Option 0x%x\n", + type); + } + + option_len <<= 3; /* Option length is a multiple of 8. */ + remaining_option_len -= option_len; + option += option_len; + } + + return -EADDRNOTAVAIL; +} + int ndisc_receive(struct ethernet_hdr *et, struct ip6_hdr *ip6, int len) { struct icmp6hdr *icmp = (struct icmp6hdr *)(((uchar *)ip6) + IP6_HDR_SIZE); struct nd_msg *ndisc = (struct nd_msg *)icmp; uchar neigh_eth_addr[6]; + int err = 0; // The error code returned calling functions.
switch (icmp->icmp6_type) { case IPV6_NDISC_NEIGHBOUR_SOLICITATION: @@ -280,6 +475,36 @@ int ndisc_receive(struct ethernet_hdr *et, struct ip6_hdr *ip6, int len) net_nd_packet_mac = NULL; } break; + case IPV6_NDISC_ROUTER_SOLICITATION: + break; + case IPV6_NDISC_ROUTER_ADVERTISEMENT: + debug("Received router advertisement for %pI6c from %pI6c\n", + &ip6->daddr, &ip6->saddr); + /* + * If gateway and prefix are set, the RA packet is ignored. The + * reason is that the U-Boot code is supposed to be as compact + * as possible and does not need to take care of multiple + * routers. In addition to that, U-Boot does not want to handle + * scenarios like a router setting its lifetime to zero to + * indicate it is not routing anymore. U-Boot program has a + * short life when the system boots up and does not need such + * sophistication. + */ + if (!ip6_is_unspecified_addr(&net_gateway6) && + net_prefix_length != 0) { + break; + } + if (!validate_ra(ip6)) { + debug("Invalid router advertisement message.\n"); + break; + } + err = process_ra(ip6, len); + if (err) + debug("Ignored router advertisement. Error: %d\n", err); + else + printf("Set gatewayip6: %pI6c, prefix_length: %d\n", + &net_gateway6, net_prefix_length); + break; default: debug("Unexpected ICMPv6 type 0x%x\n", icmp->icmp6_type); return -1; diff --git a/net/net.c b/net/net.c index c9a749f..9d15329 100644 --- a/net/net.c +++ b/net/net.c @@ -24,7 +24,7 @@ * - name of bootfile * Next step: ARP * - * LINK_LOCAL: + * LINKLOCAL: * * Prerequisites: - own ethernet address * We want: - own IP address @@ -122,6 +122,7 @@ #endif #include <net/tcp.h> #include <net/wget.h> +#include "net_rand.h"
/** BOOTP EXTENTIONS **/
@@ -346,6 +347,8 @@ void net_auto_load(void)
static int net_init_loop(void) { + static bool first_call = true; + if (eth_get_dev()) { memcpy(net_ethaddr, eth_get_ethaddr(), 6);
@@ -365,6 +368,12 @@ static int net_init_loop(void) */ return -ENONET;
+ if (IS_ENABLED(CONFIG_IPV6_ROUTER_DISCOVERY)) + if (first_call && use_ip6) { + first_call = false; + srand_mac(); /* This is for rand used in ip6_send_rs. */ + net_loop(RS); + } return 0; }
@@ -574,6 +583,10 @@ restart: ncsi_probe_packages(); break; #endif + case RS: + if (IS_ENABLED(CONFIG_IPV6_ROUTER_DISCOVERY)) + ip6_send_rs(); + break; default: break; } @@ -671,7 +684,13 @@ restart: x = time_handler; time_handler = (thand_f *)0; (*x)(); - } + } else if (IS_ENABLED(CONFIG_IPV6_ROUTER_DISCOVERY)) + if (time_handler && protocol == RS) + if (!ip6_is_unspecified_addr(&net_gateway6) && + net_prefix_length != 0) { + net_set_state(NETLOOP_SUCCESS); + net_set_timeout_handler(0, NULL); + }
if (net_state == NETLOOP_FAIL) ret = net_start_again(); diff --git a/net/net6.c b/net/net6.c index 75577bc..2dd64c0 100644 --- a/net/net6.c +++ b/net/net6.c @@ -413,6 +413,7 @@ int net_ip6_handler(struct ethernet_hdr *et, struct ip6_hdr *ip6, int len) break; case IPV6_NDISC_NEIGHBOUR_SOLICITATION: case IPV6_NDISC_NEIGHBOUR_ADVERTISEMENT: + case IPV6_NDISC_ROUTER_ADVERTISEMENT: ndisc_receive(et, ip6, len); break; default:

On Fri, 2023-04-21 at 17:08 -0700, emohandesi@linux.microsoft.com wrote:
From: Ehsan Mohandesi emohandesi@linux.microsoft.com
In IPv6, the default gateway and prefix length are determined by receiving a router advertisement as defined in - https://www.rfc-editor.org/rfc/rfc4861.
Add support for sending router solicitation (RS) and processing router advertisements (RA).
If the RA has prefix info option and following conditions are met, then gatewayip6 and net_prefix_length of ip6addr env variables are initialized. These are later consumed by IPv6 code for non-local destination IP.
- "Router Lifetime" != 0
- Prefix is NOT link-local prefix (0xfe80::/10)
- L flag is 1
- "Valid Lifetime" != 0
Timing Parameters:
- MAX_RTR_SOLICITATION_DELAY (0-1s)
- RTR_SOLICITATION_INTERVAL (4s) (min retransmit delay)
- MAX_RTR_SOLICITATIONS (3 RS transmissions)
The functionality is enabled by CONFIG_IPV6_ROUTER_DISCOVERY and invoked automatically from net_init_loop().
Signed-off-by: Ehsan Mohandesi emohandesi@linux.microsoft.com
cmd/Kconfig | 6 ++ include/ndisc.h | 35 ++++++++ include/net.h | 2 +- include/net6.h | 40 ++++++++++ net/ndisc.c | 243 +++++++++++++++++++++++++++++++++++++++++++++++++++++--- net/net.c | 23 +++++- net/net6.c | 1 + 7 files changed, 338 insertions(+), 12 deletions(-)
diff --git a/cmd/Kconfig b/cmd/Kconfig index e45b884..6919d31 100644 --- a/cmd/Kconfig +++ b/cmd/Kconfig @@ -1916,6 +1916,12 @@ config CMD_NCSI Normally this happens automatically before other network operations.
+config IPV6_ROUTER_DISCOVERY
bool "Do IPv6 router discovery"
depends on IPV6
help
Will automatically perform router solicitation on first
IPv6
network operation
endif
config CMD_ETHSW diff --git a/include/ndisc.h b/include/ndisc.h index f6f8eb6..12fa9e7 100644 --- a/include/ndisc.h +++ b/include/ndisc.h @@ -19,6 +19,20 @@ struct nd_msg { __u8 opt[0]; };
+/* struct rs_msg - ICMPv6 Router Solicitation message format */ +struct rs_msg {
struct icmp6hdr icmph;
__u8 opt[0];
+};
+/* struct ra_msg - ICMPv6 Router Advertisement message format */ +struct ra_msg {
struct icmp6hdr icmph;
__u32 reachable_time;
__u32 retransmission_timer;
__u8 opt[0];
+};
/* struct echo_msg - ICMPv6 echo request/reply message format */ struct echo_msg { struct icmp6hdr icmph; @@ -57,6 +71,11 @@ extern int net_nd_try; */ void ndisc_init(void);
+/*
- ip6_send_rs() - Send IPv6 Router Solicitation Message
- */
+void ip6_send_rs(void);
/**
- ndisc_receive() - Handle ND packet
@@ -78,6 +97,8 @@ void ndisc_request(void);
- Return: 0 if no timeout, -1 otherwise
*/ int ndisc_timeout_check(void); +bool validate_ra(struct ip6_hdr *ip6); +int process_ra(struct ip6_hdr *ip6, int len); #else static inline void ndisc_init(void) { @@ -97,6 +118,20 @@ static inline int ndisc_timeout_check(void) { return 0; }
+void ip6_send_rs(void) +{ +}
+static inline bool validate_ra(struct ip6_hdr *ip6) +{
return true;
+}
+static inline int process_ra(struct ip6_hdr *ip6, int len) +{
return 0;
+} #endif
#endif /* __NDISC_H__ */ diff --git a/include/net.h b/include/net.h index 399af5e..25c43b3 100644 --- a/include/net.h +++ b/include/net.h @@ -505,7 +505,7 @@ extern int net_restart_wrap; /* Tried all network devices */
enum proto_t { BOOTP, RARP, ARP, TFTPGET, DHCP, PING, PING6, DNS, NFS, CDP, NETCONS,
SNTP, TFTPSRV, TFTPPUT, LINKLOCAL, FASTBOOT, WOL, UDP, NCSI,
WGET
SNTP, TFTPSRV, TFTPPUT, LINKLOCAL, FASTBOOT, WOL, UDP, NCSI,
WGET, RS };
extern char net_boot_file_name[1024];/* Boot File name */ diff --git a/include/net6.h b/include/net6.h index 2d7c5a0..beafc05 100644 --- a/include/net6.h +++ b/include/net6.h @@ -81,8 +81,17 @@ struct udp_hdr { 0x00, 0x00, 0x00, 0x00, \ 0x00, 0x00, 0x00, 0x00, \ 0x00, 0x00, 0x00, 0x00 } } } +/*
- All-routers multicast address is the link-local scope address to
reach all
- routers.
- */
+#define ALL_ROUTERS_MULT_ADDR { { { 0xFF, 0x02, 0x00, 0x00, \
0x00, 0x00, 0x00, 0x00, \
0x00, 0x00, 0x00, 0x00, \
0x00, 0x00, 0x00, 0x02 } } }
#define IPV6_LINK_LOCAL_PREFIX 0xfe80 +#define IPV6_LINK_LOCAL_MASK 0xffb0 /* The first 10-bit of address mask. */
/* hop limit for neighbour discovery packets */ #define IPV6_NDISC_HOPLIMIT 255 @@ -166,6 +175,37 @@ struct icmp6hdr { #define icmp6_rt_lifetime icmp6_dataun.u_nd_ra.rt_lifetime } __packed;
+/*
- struct icmp6_ra_prefix_info - Prefix Information option of the
ICMPv6 message
- The Prefix Information option provides hosts with on-link
prefixes and
- prefixes for Address Autoconfiguration. Refer to RFC 4861 for
more info.
- */
+struct icmp6_ra_prefix_info {
u8 type; /* Type is 3 for Prefix Information.
*/
u8 len; /* Len is 4 for Prefix Information.
*/
/* The number of leading bits in the Prefix that are valid.
*/
u8 prefix_len;
u8 reserved1:6, /* MUST be ignored by the receiver.
*/
aac:1, /* autonomous address-configuration
flag */
/* Indicates that this prefix can be used for on-link
determination. */
on_link:1;
/*
* The length of time in seconds that the prefix is valid for
the
* purpose of on-link determination.
*/
__be32 valid_lifetime;
/* The length of time addresses remain preferred. */
__be32 preferred_lifetime;
__be32 reserved2; /* MUST be ignored by the receiver.
*/
/*
* Prefix is an IP address or a prefix of an IP address. The
Prefix
* Length field contains the number of valid leading bits in
the prefix.
* The bits in the prefix after the prefix length are
reserved and MUST
* be initialized to zero by the sender and ignored by the
receiver.
*/
struct in6_addr prefix;
+};
extern struct in6_addr const net_null_addr_ip6; /* NULL IPv6 address */ extern struct in6_addr net_gateway6; /* Our gateways IPv6 address */ extern struct in6_addr net_ip6; /* Our IPv6 addr (0 = unknown) */ diff --git a/net/ndisc.c b/net/ndisc.c index 367dae7..0b27779 100644 --- a/net/ndisc.c +++ b/net/ndisc.c @@ -13,6 +13,8 @@ #include <net.h> #include <net6.h> #include <ndisc.h> +#include <stdlib.h> +#include <linux/delay.h>
/* IPv6 destination address of packet waiting for ND */ struct in6_addr net_nd_sol_packet_ip6 = ZERO_IPV6_ADDR; @@ -29,31 +31,37 @@ int net_nd_tx_packet_size; ulong net_nd_timer_start; /* the number of requests we have sent so far */ int net_nd_try; +struct in6_addr all_routers = ALL_ROUTERS_MULT_ADDR;
+#define MAX_RTR_SOLICITATIONS 3 +/* The maximum time to delay sending the first router solicitation message. */ +#define MAX_SOLICITATION_DELAY 1 // 1 second +/* The time to wait before sending the next router solicitation message. */ +#define RTR_SOLICITATION_INTERVAL 4000 // 4 seconds
#define IP6_NDISC_OPT_SPACE(len) (((len) + 2 + 7) & ~7)
/**
- ndisc_insert_option() - Insert an option into a neighbor
discovery packet
- @ndisc: pointer to ND packet
- @opt: pointer to the option element of the neighbor
discovery packet
- @type: option type to insert
- @data: option data to insert
- @len: data length
- Return: the number of bytes inserted (which may be >= len)
*/ -static int -ndisc_insert_option(struct nd_msg *ndisc, int type, u8 *data, int len) +static int ndisc_insert_option(__u8 *opt, int type, u8 *data, int len) { int space = IP6_NDISC_OPT_SPACE(len);
ndisc->opt[0] = type;
ndisc->opt[1] = space >> 3;
memcpy(&ndisc->opt[2], data, len);
opt[0] = type;
opt[1] = space >> 3;
memcpy(&opt[2], data, len); len += 2; /* fill the remainder with 0 */ if (space - len > 0)
memset(&ndisc->opt[len], '\0', space - len);
memset(&opt[len], '\0', space - len); return space;
} @@ -123,7 +131,7 @@ static void ip6_send_ns(struct in6_addr *neigh_addr)
/* Set the target address and llsaddr option */ net_copy_ip6(&msg->target, neigh_addr);
ndisc_insert_option(msg, ND_OPT_SOURCE_LL_ADDR, net_ethaddr,
ndisc_insert_option(msg->opt, ND_OPT_SOURCE_LL_ADDR,
net_ethaddr, INETHADDRSZ);
/* checksum */
@@ -137,6 +145,76 @@ static void ip6_send_ns(struct in6_addr *neigh_addr) net_send_packet(net_tx_packet, (pkt - net_tx_packet)); }
+/*
- ip6_send_rs() - Send IPv6 Router Solicitation Message.
- A router solicitation is sent to discover a router. RS message
creation is
- based on RFC 4861 section 4.1. Router Solicitation Message
Format.
- */
+void ip6_send_rs(void) +{
unsigned char enetaddr[6];
struct rs_msg *msg;
__u16 icmp_len;
uchar *pkt;
unsigned short csum;
unsigned int pcsum;
static unsigned int retry_count;
if (!ip6_is_unspecified_addr(&net_gateway6) &&
net_prefix_length != 0) {
net_set_state(NETLOOP_SUCCESS);
return;
} else if (retry_count >= MAX_RTR_SOLICITATIONS) {
net_set_state(NETLOOP_FAIL);
net_set_timeout_handler(0, NULL);
retry_count = 0;
return;
}
printf("ROUTER SOLICITATION %d\n", retry_count + 1);
ip6_make_mult_ethdstaddr(enetaddr, &all_routers);
/*
* ICMP length is the size of ICMP header (8) + one option
(8) = 16.
* The option is 2 bytes of type and length + 6 bytes for
MAC.
*/
icmp_len = sizeof(struct icmp6hdr) +
IP6_NDISC_OPT_SPACE(INETHADDRSZ);
pkt = (uchar *)net_tx_packet;
pkt += net_set_ether(pkt, enetaddr, PROT_IP6);
pkt += ip6_add_hdr(pkt, &net_link_local_ip6, &all_routers,
PROT_ICMPV6,
IPV6_NDISC_HOPLIMIT, icmp_len);
/* ICMPv6 - RS */
msg = (struct rs_msg *)pkt;
msg->icmph.icmp6_type = IPV6_NDISC_ROUTER_SOLICITATION;
msg->icmph.icmp6_code = 0;
memset(&msg->icmph.icmp6_cksum, 0, sizeof(__be16));
memset(&msg->icmph.icmp6_unused, 0, sizeof(__be32));
/* Set the llsaddr option */
ndisc_insert_option(msg->opt, ND_OPT_SOURCE_LL_ADDR,
net_ethaddr,
INETHADDRSZ);
/* checksum */
pcsum = csum_partial((__u8 *)msg, icmp_len, 0);
csum = csum_ipv6_magic(&net_link_local_ip6, &all_routers,
icmp_len, PROT_ICMPV6, pcsum);
msg->icmph.icmp6_cksum = csum;
pkt += icmp_len;
/* Wait up to 1 second if it is the first try to get the RA
*/
if (retry_count == 0)
udelay(((unsigned int)rand() % 1000000) *
MAX_SOLICITATION_DELAY);
/* send it! */
net_send_packet(net_tx_packet, (pkt - net_tx_packet));
retry_count++;
net_set_timeout_handler(RTR_SOLICITATION_INTERVAL,
ip6_send_rs); +}
static void ip6_send_na(uchar *eth_dst_addr, struct in6_addr *neigh_addr, struct in6_addr *target) @@ -167,7 +245,7 @@ ip6_send_na(uchar *eth_dst_addr, struct in6_addr *neigh_addr, msg->icmph.icmp6_dataun.u_nd_advt.override = 1; /* Set the target address and lltargetaddr option */ net_copy_ip6(&msg->target, target);
ndisc_insert_option(msg, ND_OPT_TARGET_LL_ADDR, net_ethaddr,
ndisc_insert_option(msg->opt, ND_OPT_TARGET_LL_ADDR,
net_ethaddr, INETHADDRSZ);
/* checksum */
@@ -223,6 +301,10 @@ int ndisc_timeout_check(void) return 1; }
+/*
- ndisc_init() - Make initial steps for ND state machine.
- Usually move variables into initial state.
- */
void ndisc_init(void) { net_nd_packet_mac = NULL; @@ -234,12 +316,125 @@ void ndisc_init(void) net_nd_tx_packet -= (ulong)net_nd_tx_packet % PKTALIGN; }
+/*
- validate_ra() - Validate the router advertisement message.
- @ip6: Pointer to the router advertisement packet
- Check if the router advertisement message is valid. Conditions
are
- according to RFC 4861 section 6.1.2. Validation of Router
Advertisement
- Messages.
- Return: true if the message is valid and false if it is invalid.
- */
+bool validate_ra(struct ip6_hdr *ip6) +{
struct icmp6hdr *icmp = (struct icmp6hdr *)(ip6 + 1);
/* ICMP length (derived from the IP length) should be 16 or
more octets. */
if (ip6->payload_len < 16)
return false;
/* Source IP Address should be a valid link-local address. */
if ((ntohs(ip6->saddr.s6_addr16[0]) & IPV6_LINK_LOCAL_MASK)
!=
IPV6_LINK_LOCAL_PREFIX)
return false;
/*
* The IP Hop Limit field should have a value of 255, i.e.,
the packet
* could not possibly have been forwarded by a router.
*/
if (ip6->hop_limit != 255)
return false;
/* ICMP checksum has already been checked in net_ip6_handler.
*/
if (icmp->icmp6_code != 0)
return false;
return true;
+}
+/*
- process_ra() - Process the router advertisement packet.
- @ip6: Pointer to the router advertisement packet
- @len: Length of the router advertisement packet
- Process the received router advertisement message.
- Although RFC 4861 requires retaining at least two router
addresses, we only
- keep one because of the U-Boot limitations and its goal of
lightweight code.
- Return: 0 - RA is a default router and contains valid prefix
information.
- Non-zero - RA options are invalid or do not indicate it is a
default router
- or do not contain valid prefix information.
- */
+int process_ra(struct ip6_hdr *ip6, int len) +{
/* Pointer to the ICMP section of the packet */
struct icmp6hdr *icmp = (struct icmp6hdr *)(ip6 + 1);
struct ra_msg *msg = (struct ra_msg *)icmp;
int remaining_option_len = len - IP6_HDR_SIZE - sizeof(struct
ra_msg);
unsigned short int option_len; /* Length of each option */
/* Pointer to the ICMPv6 message options */
unsigned char *option = NULL;
/* 8-bit identifier of the type of ICMPv6 option */
unsigned char type = 0;
struct icmp6_ra_prefix_info *prefix = NULL;
/* Ignore the packet if router lifetime is 0. */
if (!icmp->icmp6_rt_lifetime)
return -EOPNOTSUPP;
/* Processing the options */
option = msg->opt;
while (remaining_option_len > 0) {
/* The 2nd byte of the option is its length. */
option_len = option[1];
/* All included options should have a positive
length. */
if (option_len == 0)
return -EINVAL;
type = option[0];
/* All option types except Prefix Information are
ignored. */
switch (type) {
case ND_OPT_SOURCE_LL_ADDR:
case ND_OPT_TARGET_LL_ADDR:
case ND_OPT_REDIRECT_HDR:
case ND_OPT_MTU:
break;
case ND_OPT_PREFIX_INFO:
prefix = (struct icmp6_ra_prefix_info
*)option;
/* The link-local prefix 0xfe80::/10 is
ignored. */
if ((ntohs(prefix->prefix.s6_addr16[0]) &
IPV6_LINK_LOCAL_MASK) ==
IPV6_LINK_LOCAL_PREFIX)
break;
if (prefix->on_link && ntohl(prefix-
valid_lifetime)) {
net_prefix_length = prefix-
prefix_len;
net_gateway6 = ip6->saddr;
return 0;
}
break;
default:
debug("Unknown IPv6 Neighbor Discovery Option
0x%x\n",
type);
}
option_len <<= 3; /* Option length is a multiple of
- */
remaining_option_len -= option_len;
option += option_len;
}
return -EADDRNOTAVAIL;
+}
int ndisc_receive(struct ethernet_hdr *et, struct ip6_hdr *ip6, int len) { struct icmp6hdr *icmp = (struct icmp6hdr *)(((uchar *)ip6) + IP6_HDR_SIZE); struct nd_msg *ndisc = (struct nd_msg *)icmp; uchar neigh_eth_addr[6];
int err = 0; // The error code returned calling functions. switch (icmp->icmp6_type) { case IPV6_NDISC_NEIGHBOUR_SOLICITATION:
@@ -280,6 +475,36 @@ int ndisc_receive(struct ethernet_hdr *et, struct ip6_hdr *ip6, int len) net_nd_packet_mac = NULL; } break;
case IPV6_NDISC_ROUTER_SOLICITATION:
break;
case IPV6_NDISC_ROUTER_ADVERTISEMENT:
debug("Received router advertisement for %pI6c from
%pI6c\n",
&ip6->daddr, &ip6->saddr);
/*
* If gateway and prefix are set, the RA packet is
ignored. The
* reason is that the U-Boot code is supposed to be
as compact
* as possible and does not need to take care of
multiple
* routers. In addition to that, U-Boot does not want
to handle
* scenarios like a router setting its lifetime to
zero to
* indicate it is not routing anymore. U-Boot program
has a
* short life when the system boots up and does not
need such
* sophistication.
*/
if (!ip6_is_unspecified_addr(&net_gateway6) &&
net_prefix_length != 0) {
break;
}
if (!validate_ra(ip6)) {
debug("Invalid router advertisement
message.\n");
break;
}
err = process_ra(ip6, len);
if (err)
debug("Ignored router advertisement. Error:
%d\n", err);
else
printf("Set gatewayip6: %pI6c, prefix_length:
%d\n",
&net_gateway6, net_prefix_length);
break; default: debug("Unexpected ICMPv6 type 0x%x\n", icmp-
icmp6_type);
return -1;
diff --git a/net/net.c b/net/net.c index c9a749f..9d15329 100644 --- a/net/net.c +++ b/net/net.c @@ -24,7 +24,7 @@
- name of bootfile
Next step: ARP
- LINK_LOCAL:
- LINKLOCAL:
Prerequisites: - own ethernet address
We want: - own IP address
@@ -122,6 +122,7 @@ #endif #include <net/tcp.h> #include <net/wget.h> +#include "net_rand.h"
/** BOOTP EXTENTIONS **/
@@ -346,6 +347,8 @@ void net_auto_load(void)
static int net_init_loop(void) {
static bool first_call = true;
if (eth_get_dev()) { memcpy(net_ethaddr, eth_get_ethaddr(), 6);
@@ -365,6 +368,12 @@ static int net_init_loop(void) */ return -ENONET;
if (IS_ENABLED(CONFIG_IPV6_ROUTER_DISCOVERY))
if (first_call && use_ip6) {
first_call = false;
srand_mac(); /* This is for rand used in
ip6_send_rs. */
net_loop(RS);
} return 0;
}
@@ -574,6 +583,10 @@ restart: ncsi_probe_packages(); break; #endif
case RS:
if (IS_ENABLED(CONFIG_IPV6_ROUTER_DISCOVERY))
ip6_send_rs();
break; default: break; }
@@ -671,7 +684,13 @@ restart: x = time_handler; time_handler = (thand_f *)0; (*x)();
}
} else if (IS_ENABLED(CONFIG_IPV6_ROUTER_DISCOVERY))
if (time_handler && protocol == RS)
if
(!ip6_is_unspecified_addr(&net_gateway6) &&
net_prefix_length != 0) {
net_set_state(NETLOOP_SUCCESS
);
net_set_timeout_handler(0,
NULL);
} if (net_state == NETLOOP_FAIL) ret = net_start_again();
diff --git a/net/net6.c b/net/net6.c index 75577bc..2dd64c0 100644 --- a/net/net6.c +++ b/net/net6.c @@ -413,6 +413,7 @@ int net_ip6_handler(struct ethernet_hdr *et, struct ip6_hdr *ip6, int len) break; case IPV6_NDISC_NEIGHBOUR_SOLICITATION: case IPV6_NDISC_NEIGHBOUR_ADVERTISEMENT:
case IPV6_NDISC_ROUTER_ADVERTISEMENT: ndisc_receive(et, ip6, len); break; default:
-- 1.8.3.1
Tested on Si-five Hi-five Unmatched board (RISC-V)
Good. Thanks!
Tested-by: Viacheslav Mitrofanov v.v.mitrofanov@yadro.comReviewed-by: Viacheslav Mitrofanov v.v.mitrofanov@yadro.com

On Fri, 2023-04-21 at 17:08 -0700, emohandesi@linux.microsoft.com wrote:
From: Ehsan Mohandesi emohandesi@linux.microsoft.com
In IPv6, the default gateway and prefix length are determined by receiving a router advertisement as defined in - https://www.rfc-editor.org/rfc/rfc4861.
Add support for sending router solicitation (RS) and processing router advertisements (RA).
If the RA has prefix info option and following conditions are met, then gatewayip6 and net_prefix_length of ip6addr env variables are initialized. These are later consumed by IPv6 code for non-local destination IP.
- "Router Lifetime" != 0
- Prefix is NOT link-local prefix (0xfe80::/10)
- L flag is 1
- "Valid Lifetime" != 0
Timing Parameters:
- MAX_RTR_SOLICITATION_DELAY (0-1s)
- RTR_SOLICITATION_INTERVAL (4s) (min retransmit delay)
- MAX_RTR_SOLICITATIONS (3 RS transmissions)
The functionality is enabled by CONFIG_IPV6_ROUTER_DISCOVERY and invoked automatically from net_init_loop().
Signed-off-by: Ehsan Mohandesi emohandesi@linux.microsoft.com
cmd/Kconfig | 6 ++ include/ndisc.h | 35 ++++++++ include/net.h | 2 +- include/net6.h | 40 ++++++++++ net/ndisc.c | 243 +++++++++++++++++++++++++++++++++++++++++++++++++++++--- net/net.c | 23 +++++- net/net6.c | 1 + 7 files changed, 338 insertions(+), 12 deletions(-)
diff --git a/cmd/Kconfig b/cmd/Kconfig index e45b884..6919d31 100644 --- a/cmd/Kconfig +++ b/cmd/Kconfig @@ -1916,6 +1916,12 @@ config CMD_NCSI Normally this happens automatically before other network operations.
+config IPV6_ROUTER_DISCOVERY
bool "Do IPv6 router discovery"
depends on IPV6
help
Will automatically perform router solicitation on first
IPv6
network operation
endif
config CMD_ETHSW diff --git a/include/ndisc.h b/include/ndisc.h index f6f8eb6..12fa9e7 100644 --- a/include/ndisc.h +++ b/include/ndisc.h @@ -19,6 +19,20 @@ struct nd_msg { __u8 opt[0]; };
+/* struct rs_msg - ICMPv6 Router Solicitation message format */ +struct rs_msg {
struct icmp6hdr icmph;
__u8 opt[0];
+};
+/* struct ra_msg - ICMPv6 Router Advertisement message format */ +struct ra_msg {
struct icmp6hdr icmph;
__u32 reachable_time;
__u32 retransmission_timer;
__u8 opt[0];
+};
/* struct echo_msg - ICMPv6 echo request/reply message format */ struct echo_msg { struct icmp6hdr icmph; @@ -57,6 +71,11 @@ extern int net_nd_try; */ void ndisc_init(void);
+/*
- ip6_send_rs() - Send IPv6 Router Solicitation Message
- */
+void ip6_send_rs(void);
/**
- ndisc_receive() - Handle ND packet
@@ -78,6 +97,8 @@ void ndisc_request(void);
- Return: 0 if no timeout, -1 otherwise
*/ int ndisc_timeout_check(void); +bool validate_ra(struct ip6_hdr *ip6); +int process_ra(struct ip6_hdr *ip6, int len); #else static inline void ndisc_init(void) { @@ -97,6 +118,20 @@ static inline int ndisc_timeout_check(void) { return 0; }
+void ip6_send_rs(void) +{ +}
+static inline bool validate_ra(struct ip6_hdr *ip6) +{
return true;
+}
+static inline int process_ra(struct ip6_hdr *ip6, int len) +{
return 0;
+} #endif
#endif /* __NDISC_H__ */ diff --git a/include/net.h b/include/net.h index 399af5e..25c43b3 100644 --- a/include/net.h +++ b/include/net.h @@ -505,7 +505,7 @@ extern int net_restart_wrap; /* Tried all network devices */
enum proto_t { BOOTP, RARP, ARP, TFTPGET, DHCP, PING, PING6, DNS, NFS, CDP, NETCONS,
SNTP, TFTPSRV, TFTPPUT, LINKLOCAL, FASTBOOT, WOL, UDP, NCSI,
WGET
SNTP, TFTPSRV, TFTPPUT, LINKLOCAL, FASTBOOT, WOL, UDP, NCSI,
WGET, RS };
extern char net_boot_file_name[1024];/* Boot File name */ diff --git a/include/net6.h b/include/net6.h index 2d7c5a0..beafc05 100644 --- a/include/net6.h +++ b/include/net6.h @@ -81,8 +81,17 @@ struct udp_hdr { 0x00, 0x00, 0x00, 0x00, \ 0x00, 0x00, 0x00, 0x00, \ 0x00, 0x00, 0x00, 0x00 } } } +/*
- All-routers multicast address is the link-local scope address to
reach all
- routers.
- */
+#define ALL_ROUTERS_MULT_ADDR { { { 0xFF, 0x02, 0x00, 0x00, \
0x00, 0x00, 0x00, 0x00, \
0x00, 0x00, 0x00, 0x00, \
0x00, 0x00, 0x00, 0x02 } } }
#define IPV6_LINK_LOCAL_PREFIX 0xfe80 +#define IPV6_LINK_LOCAL_MASK 0xffb0 /* The first 10-bit of address mask. */
/* hop limit for neighbour discovery packets */ #define IPV6_NDISC_HOPLIMIT 255 @@ -166,6 +175,37 @@ struct icmp6hdr { #define icmp6_rt_lifetime icmp6_dataun.u_nd_ra.rt_lifetime } __packed;
+/*
- struct icmp6_ra_prefix_info - Prefix Information option of the
ICMPv6 message
- The Prefix Information option provides hosts with on-link
prefixes and
- prefixes for Address Autoconfiguration. Refer to RFC 4861 for
more info.
- */
+struct icmp6_ra_prefix_info {
u8 type; /* Type is 3 for Prefix Information.
*/
u8 len; /* Len is 4 for Prefix Information.
*/
/* The number of leading bits in the Prefix that are valid.
*/
u8 prefix_len;
u8 reserved1:6, /* MUST be ignored by the receiver.
*/
aac:1, /* autonomous address-configuration
flag */
/* Indicates that this prefix can be used for on-link
determination. */
on_link:1;
/*
* The length of time in seconds that the prefix is valid for
the
* purpose of on-link determination.
*/
__be32 valid_lifetime;
/* The length of time addresses remain preferred. */
__be32 preferred_lifetime;
__be32 reserved2; /* MUST be ignored by the receiver.
*/
/*
* Prefix is an IP address or a prefix of an IP address. The
Prefix
* Length field contains the number of valid leading bits in
the prefix.
* The bits in the prefix after the prefix length are
reserved and MUST
* be initialized to zero by the sender and ignored by the
receiver.
*/
struct in6_addr prefix;
+};
extern struct in6_addr const net_null_addr_ip6; /* NULL IPv6 address */ extern struct in6_addr net_gateway6; /* Our gateways IPv6 address */ extern struct in6_addr net_ip6; /* Our IPv6 addr (0 = unknown) */ diff --git a/net/ndisc.c b/net/ndisc.c index 367dae7..0b27779 100644 --- a/net/ndisc.c +++ b/net/ndisc.c @@ -13,6 +13,8 @@ #include <net.h> #include <net6.h> #include <ndisc.h> +#include <stdlib.h> +#include <linux/delay.h>
/* IPv6 destination address of packet waiting for ND */ struct in6_addr net_nd_sol_packet_ip6 = ZERO_IPV6_ADDR; @@ -29,31 +31,37 @@ int net_nd_tx_packet_size; ulong net_nd_timer_start; /* the number of requests we have sent so far */ int net_nd_try; +struct in6_addr all_routers = ALL_ROUTERS_MULT_ADDR;
+#define MAX_RTR_SOLICITATIONS 3 +/* The maximum time to delay sending the first router solicitation message. */ +#define MAX_SOLICITATION_DELAY 1 // 1 second +/* The time to wait before sending the next router solicitation message. */ +#define RTR_SOLICITATION_INTERVAL 4000 // 4 seconds
#define IP6_NDISC_OPT_SPACE(len) (((len) + 2 + 7) & ~7)
/**
- ndisc_insert_option() - Insert an option into a neighbor
discovery packet
- @ndisc: pointer to ND packet
- @opt: pointer to the option element of the neighbor
discovery packet
- @type: option type to insert
- @data: option data to insert
- @len: data length
- Return: the number of bytes inserted (which may be >= len)
*/ -static int -ndisc_insert_option(struct nd_msg *ndisc, int type, u8 *data, int len) +static int ndisc_insert_option(__u8 *opt, int type, u8 *data, int len) { int space = IP6_NDISC_OPT_SPACE(len);
ndisc->opt[0] = type;
ndisc->opt[1] = space >> 3;
memcpy(&ndisc->opt[2], data, len);
opt[0] = type;
opt[1] = space >> 3;
memcpy(&opt[2], data, len); len += 2; /* fill the remainder with 0 */ if (space - len > 0)
memset(&ndisc->opt[len], '\0', space - len);
memset(&opt[len], '\0', space - len); return space;
} @@ -123,7 +131,7 @@ static void ip6_send_ns(struct in6_addr *neigh_addr)
/* Set the target address and llsaddr option */ net_copy_ip6(&msg->target, neigh_addr);
ndisc_insert_option(msg, ND_OPT_SOURCE_LL_ADDR, net_ethaddr,
ndisc_insert_option(msg->opt, ND_OPT_SOURCE_LL_ADDR,
net_ethaddr, INETHADDRSZ);
/* checksum */
@@ -137,6 +145,76 @@ static void ip6_send_ns(struct in6_addr *neigh_addr) net_send_packet(net_tx_packet, (pkt - net_tx_packet)); }
+/*
- ip6_send_rs() - Send IPv6 Router Solicitation Message.
- A router solicitation is sent to discover a router. RS message
creation is
- based on RFC 4861 section 4.1. Router Solicitation Message
Format.
- */
+void ip6_send_rs(void) +{
unsigned char enetaddr[6];
struct rs_msg *msg;
__u16 icmp_len;
uchar *pkt;
unsigned short csum;
unsigned int pcsum;
static unsigned int retry_count;
if (!ip6_is_unspecified_addr(&net_gateway6) &&
net_prefix_length != 0) {
net_set_state(NETLOOP_SUCCESS);
return;
} else if (retry_count >= MAX_RTR_SOLICITATIONS) {
net_set_state(NETLOOP_FAIL);
net_set_timeout_handler(0, NULL);
retry_count = 0;
return;
}
printf("ROUTER SOLICITATION %d\n", retry_count + 1);
ip6_make_mult_ethdstaddr(enetaddr, &all_routers);
/*
* ICMP length is the size of ICMP header (8) + one option
(8) = 16.
* The option is 2 bytes of type and length + 6 bytes for
MAC.
*/
icmp_len = sizeof(struct icmp6hdr) +
IP6_NDISC_OPT_SPACE(INETHADDRSZ);
pkt = (uchar *)net_tx_packet;
pkt += net_set_ether(pkt, enetaddr, PROT_IP6);
pkt += ip6_add_hdr(pkt, &net_link_local_ip6, &all_routers,
PROT_ICMPV6,
IPV6_NDISC_HOPLIMIT, icmp_len);
/* ICMPv6 - RS */
msg = (struct rs_msg *)pkt;
msg->icmph.icmp6_type = IPV6_NDISC_ROUTER_SOLICITATION;
msg->icmph.icmp6_code = 0;
memset(&msg->icmph.icmp6_cksum, 0, sizeof(__be16));
memset(&msg->icmph.icmp6_unused, 0, sizeof(__be32));
/* Set the llsaddr option */
ndisc_insert_option(msg->opt, ND_OPT_SOURCE_LL_ADDR,
net_ethaddr,
INETHADDRSZ);
/* checksum */
pcsum = csum_partial((__u8 *)msg, icmp_len, 0);
csum = csum_ipv6_magic(&net_link_local_ip6, &all_routers,
icmp_len, PROT_ICMPV6, pcsum);
msg->icmph.icmp6_cksum = csum;
pkt += icmp_len;
/* Wait up to 1 second if it is the first try to get the RA
*/
if (retry_count == 0)
udelay(((unsigned int)rand() % 1000000) *
MAX_SOLICITATION_DELAY);
/* send it! */
net_send_packet(net_tx_packet, (pkt - net_tx_packet));
retry_count++;
net_set_timeout_handler(RTR_SOLICITATION_INTERVAL,
ip6_send_rs); +}
static void ip6_send_na(uchar *eth_dst_addr, struct in6_addr *neigh_addr, struct in6_addr *target) @@ -167,7 +245,7 @@ ip6_send_na(uchar *eth_dst_addr, struct in6_addr *neigh_addr, msg->icmph.icmp6_dataun.u_nd_advt.override = 1; /* Set the target address and lltargetaddr option */ net_copy_ip6(&msg->target, target);
ndisc_insert_option(msg, ND_OPT_TARGET_LL_ADDR, net_ethaddr,
ndisc_insert_option(msg->opt, ND_OPT_TARGET_LL_ADDR,
net_ethaddr, INETHADDRSZ);
/* checksum */
@@ -223,6 +301,10 @@ int ndisc_timeout_check(void) return 1; }
+/*
- ndisc_init() - Make initial steps for ND state machine.
- Usually move variables into initial state.
- */
void ndisc_init(void) { net_nd_packet_mac = NULL; @@ -234,12 +316,125 @@ void ndisc_init(void) net_nd_tx_packet -= (ulong)net_nd_tx_packet % PKTALIGN; }
+/*
- validate_ra() - Validate the router advertisement message.
- @ip6: Pointer to the router advertisement packet
- Check if the router advertisement message is valid. Conditions
are
- according to RFC 4861 section 6.1.2. Validation of Router
Advertisement
- Messages.
- Return: true if the message is valid and false if it is invalid.
- */
+bool validate_ra(struct ip6_hdr *ip6) +{
struct icmp6hdr *icmp = (struct icmp6hdr *)(ip6 + 1);
/* ICMP length (derived from the IP length) should be 16 or
more octets. */
if (ip6->payload_len < 16)
return false;
/* Source IP Address should be a valid link-local address. */
if ((ntohs(ip6->saddr.s6_addr16[0]) & IPV6_LINK_LOCAL_MASK)
!=
IPV6_LINK_LOCAL_PREFIX)
return false;
/*
* The IP Hop Limit field should have a value of 255, i.e.,
the packet
* could not possibly have been forwarded by a router.
*/
if (ip6->hop_limit != 255)
return false;
/* ICMP checksum has already been checked in net_ip6_handler.
*/
if (icmp->icmp6_code != 0)
return false;
return true;
+}
+/*
- process_ra() - Process the router advertisement packet.
- @ip6: Pointer to the router advertisement packet
- @len: Length of the router advertisement packet
- Process the received router advertisement message.
- Although RFC 4861 requires retaining at least two router
addresses, we only
- keep one because of the U-Boot limitations and its goal of
lightweight code.
- Return: 0 - RA is a default router and contains valid prefix
information.
- Non-zero - RA options are invalid or do not indicate it is a
default router
- or do not contain valid prefix information.
- */
+int process_ra(struct ip6_hdr *ip6, int len) +{
/* Pointer to the ICMP section of the packet */
struct icmp6hdr *icmp = (struct icmp6hdr *)(ip6 + 1);
struct ra_msg *msg = (struct ra_msg *)icmp;
int remaining_option_len = len - IP6_HDR_SIZE - sizeof(struct
ra_msg);
unsigned short int option_len; /* Length of each option */
/* Pointer to the ICMPv6 message options */
unsigned char *option = NULL;
/* 8-bit identifier of the type of ICMPv6 option */
unsigned char type = 0;
struct icmp6_ra_prefix_info *prefix = NULL;
/* Ignore the packet if router lifetime is 0. */
if (!icmp->icmp6_rt_lifetime)
return -EOPNOTSUPP;
/* Processing the options */
option = msg->opt;
while (remaining_option_len > 0) {
/* The 2nd byte of the option is its length. */
option_len = option[1];
/* All included options should have a positive
length. */
if (option_len == 0)
return -EINVAL;
type = option[0];
/* All option types except Prefix Information are
ignored. */
switch (type) {
case ND_OPT_SOURCE_LL_ADDR:
case ND_OPT_TARGET_LL_ADDR:
case ND_OPT_REDIRECT_HDR:
case ND_OPT_MTU:
break;
case ND_OPT_PREFIX_INFO:
prefix = (struct icmp6_ra_prefix_info
*)option;
/* The link-local prefix 0xfe80::/10 is
ignored. */
if ((ntohs(prefix->prefix.s6_addr16[0]) &
IPV6_LINK_LOCAL_MASK) ==
IPV6_LINK_LOCAL_PREFIX)
break;
if (prefix->on_link && ntohl(prefix-
valid_lifetime)) {
net_prefix_length = prefix-
prefix_len;
net_gateway6 = ip6->saddr;
return 0;
}
break;
default:
debug("Unknown IPv6 Neighbor Discovery Option
0x%x\n",
type);
}
option_len <<= 3; /* Option length is a multiple of
- */
remaining_option_len -= option_len;
option += option_len;
}
return -EADDRNOTAVAIL;
+}
int ndisc_receive(struct ethernet_hdr *et, struct ip6_hdr *ip6, int len) { struct icmp6hdr *icmp = (struct icmp6hdr *)(((uchar *)ip6) + IP6_HDR_SIZE); struct nd_msg *ndisc = (struct nd_msg *)icmp; uchar neigh_eth_addr[6];
int err = 0; // The error code returned calling functions. switch (icmp->icmp6_type) { case IPV6_NDISC_NEIGHBOUR_SOLICITATION:
@@ -280,6 +475,36 @@ int ndisc_receive(struct ethernet_hdr *et, struct ip6_hdr *ip6, int len) net_nd_packet_mac = NULL; } break;
case IPV6_NDISC_ROUTER_SOLICITATION:
break;
case IPV6_NDISC_ROUTER_ADVERTISEMENT:
debug("Received router advertisement for %pI6c from
%pI6c\n",
&ip6->daddr, &ip6->saddr);
/*
* If gateway and prefix are set, the RA packet is
ignored. The
* reason is that the U-Boot code is supposed to be
as compact
* as possible and does not need to take care of
multiple
* routers. In addition to that, U-Boot does not want
to handle
* scenarios like a router setting its lifetime to
zero to
* indicate it is not routing anymore. U-Boot program
has a
* short life when the system boots up and does not
need such
* sophistication.
*/
if (!ip6_is_unspecified_addr(&net_gateway6) &&
net_prefix_length != 0) {
break;
}
if (!validate_ra(ip6)) {
debug("Invalid router advertisement
message.\n");
break;
}
err = process_ra(ip6, len);
if (err)
debug("Ignored router advertisement. Error:
%d\n", err);
else
printf("Set gatewayip6: %pI6c, prefix_length:
%d\n",
&net_gateway6, net_prefix_length);
break; default: debug("Unexpected ICMPv6 type 0x%x\n", icmp-
icmp6_type);
return -1;
diff --git a/net/net.c b/net/net.c index c9a749f..9d15329 100644 --- a/net/net.c +++ b/net/net.c @@ -24,7 +24,7 @@
- name of bootfile
Next step: ARP
- LINK_LOCAL:
- LINKLOCAL:
Prerequisites: - own ethernet address
We want: - own IP address
@@ -122,6 +122,7 @@ #endif #include <net/tcp.h> #include <net/wget.h> +#include "net_rand.h"
/** BOOTP EXTENTIONS **/
@@ -346,6 +347,8 @@ void net_auto_load(void)
static int net_init_loop(void) {
static bool first_call = true;
if (eth_get_dev()) { memcpy(net_ethaddr, eth_get_ethaddr(), 6);
@@ -365,6 +368,12 @@ static int net_init_loop(void) */ return -ENONET;
if (IS_ENABLED(CONFIG_IPV6_ROUTER_DISCOVERY))
if (first_call && use_ip6) {
first_call = false;
srand_mac(); /* This is for rand used in
ip6_send_rs. */
net_loop(RS);
} return 0;
}
@@ -574,6 +583,10 @@ restart: ncsi_probe_packages(); break; #endif
case RS:
if (IS_ENABLED(CONFIG_IPV6_ROUTER_DISCOVERY))
ip6_send_rs();
break; default: break; }
@@ -671,7 +684,13 @@ restart: x = time_handler; time_handler = (thand_f *)0; (*x)();
}
} else if (IS_ENABLED(CONFIG_IPV6_ROUTER_DISCOVERY))
if (time_handler && protocol == RS)
if
(!ip6_is_unspecified_addr(&net_gateway6) &&
net_prefix_length != 0) {
net_set_state(NETLOOP_SUCCESS
);
net_set_timeout_handler(0,
NULL);
} if (net_state == NETLOOP_FAIL) ret = net_start_again();
diff --git a/net/net6.c b/net/net6.c index 75577bc..2dd64c0 100644 --- a/net/net6.c +++ b/net/net6.c @@ -413,6 +413,7 @@ int net_ip6_handler(struct ethernet_hdr *et, struct ip6_hdr *ip6, int len) break; case IPV6_NDISC_NEIGHBOUR_SOLICITATION: case IPV6_NDISC_NEIGHBOUR_ADVERTISEMENT:
case IPV6_NDISC_ROUTER_ADVERTISEMENT: ndisc_receive(et, ip6, len); break; default:
-- 1.8.3.1
Tested on Si-five Hi-five Unmatched board (RISC-V)
Good. Thanks!
Tested-by: Viacheslav Mitrofanov v.v.mitrofanov@yadro.com Reviewed-by: Viacheslav Mitrofanov v.v.mitrofanov@yadro.com

On Sat, 22 Apr 2023 at 03:08, emohandesi@linux.microsoft.com wrote:
From: Ehsan Mohandesi emohandesi@linux.microsoft.com
Hello, Ehsan! Good patch, but one little change is needed.
+struct icmp6_ra_prefix_info {
u8 type; /* Type is 3 for Prefix Information. */
u8 len; /* Len is 4 for Prefix Information. */
/* The number of leading bits in the Prefix that are valid. */
u8 prefix_len;
u8 reserved1:6, /* MUST be ignored by the receiver. */
aac:1, /* autonomous address-configuration flag */
/* Indicates that this prefix can be used for on-link determination. */
on_link:1;
/*
* The length of time in seconds that the prefix is valid for the
* purpose of on-link determination.
*/
__be32 valid_lifetime;
/* The length of time addresses remain preferred. */
__be32 preferred_lifetime;
__be32 reserved2; /* MUST be ignored by the receiver. */
/*
* Prefix is an IP address or a prefix of an IP address. The Prefix
* Length field contains the number of valid leading bits in the prefix.
* The bits in the prefix after the prefix length are reserved and MUST
* be initialized to zero by the sender and ignored by the receiver.
*/
struct in6_addr prefix;
+};
Here it should end with: } __packed; Because this structure may be placed at a badly aligned offset within a packet. For example, at offset 0x46 within Ethernet packet.
Other than that: Tested-by: Sergei Antonov saproj@gmail.com Reviewed-by: Sergei Antonov saproj@gmail.com

On Sat, 22 Apr 2023 at 03:08, emohandesi@linux.microsoft.com wrote:
if (prefix->on_link && ntohl(prefix->valid_lifetime)) {
net_prefix_length = prefix->prefix_len;
net_gateway6 = ip6->saddr;
return 0;
Is it OK that prefix->prefix_len is used, but prefix->prefix is not? Just curious.

On 5/4/2023 9:52 AM, Sergei Antonov wrote:
On Sat, 22 Apr 2023 at 03:08,emohandesi@linux.microsoft.com wrote:
if (prefix->on_link && ntohl(prefix->valid_lifetime)) {
net_prefix_length = prefix->prefix_len;
net_gateway6 = ip6->saddr;
return 0;
Is it OK that prefix->prefix_len is used, but prefix->prefix is not? Just curious.
prefix->prefix is used just before this if statement to make sure it is not a link-local address as shown below.
if((ntohs(prefix->prefix.s6_addr16[0]) & IPV6_LINK_LOCAL_MASK) == IPV6_LINK_LOCAL_PREFIX) break; if(prefix->on_link && ntohl(prefix->valid_lifetime)) { net_prefix_length = prefix->prefix_len; net_gateway6 = ip6->saddr; return0; }

On Fri, Apr 21, 2023 at 05:08:21PM -0700, emohandesi@linux.microsoft.com wrote:
From: Ehsan Mohandesi emohandesi@linux.microsoft.com
In IPv6, the default gateway and prefix length are determined by receiving a router advertisement as defined in - https://www.rfc-editor.org/rfc/rfc4861.
Add support for sending router solicitation (RS) and processing router advertisements (RA).
If the RA has prefix info option and following conditions are met, then gatewayip6 and net_prefix_length of ip6addr env variables are initialized. These are later consumed by IPv6 code for non-local destination IP.
- "Router Lifetime" != 0
- Prefix is NOT link-local prefix (0xfe80::/10)
- L flag is 1
- "Valid Lifetime" != 0
Timing Parameters:
- MAX_RTR_SOLICITATION_DELAY (0-1s)
- RTR_SOLICITATION_INTERVAL (4s) (min retransmit delay)
- MAX_RTR_SOLICITATIONS (3 RS transmissions)
The functionality is enabled by CONFIG_IPV6_ROUTER_DISCOVERY and invoked automatically from net_init_loop().
Signed-off-by: Ehsan Mohandesi emohandesi@linux.microsoft.com Tested-by: Viacheslav Mitrofanov v.v.mitrofanov@yadro.comReviewed-by: Tested-by: Viacheslav Mitrofanov v.v.mitrofanov@yadro.com Reviewed-by: Viacheslav Mitrofanov v.v.mitrofanov@yadro.com Tested-by: Sergei Antonov saproj@gmail.com Reviewed-by: Sergei Antonov saproj@gmail.com
Applied to u-boot/master, thanks!

On Sat, 6 May 2023 at 17:53, Tom Rini trini@konsulko.com wrote:
On Fri, Apr 21, 2023 at 05:08:21PM -0700, emohandesi@linux.microsoft.com wrote:
From: Ehsan Mohandesi emohandesi@linux.microsoft.com
In IPv6, the default gateway and prefix length are determined by receiving a router advertisement as defined in - https://www.rfc-editor.org/rfc/rfc4861.
Add support for sending router solicitation (RS) and processing router advertisements (RA).
If the RA has prefix info option and following conditions are met, then gatewayip6 and net_prefix_length of ip6addr env variables are initialized. These are later consumed by IPv6 code for non-local destination IP.
- "Router Lifetime" != 0
- Prefix is NOT link-local prefix (0xfe80::/10)
- L flag is 1
- "Valid Lifetime" != 0
Timing Parameters:
- MAX_RTR_SOLICITATION_DELAY (0-1s)
- RTR_SOLICITATION_INTERVAL (4s) (min retransmit delay)
- MAX_RTR_SOLICITATIONS (3 RS transmissions)
The functionality is enabled by CONFIG_IPV6_ROUTER_DISCOVERY and invoked automatically from net_init_loop().
Signed-off-by: Ehsan Mohandesi emohandesi@linux.microsoft.com Tested-by: Viacheslav Mitrofanov v.v.mitrofanov@yadro.comReviewed-by: Tested-by: Viacheslav Mitrofanov v.v.mitrofanov@yadro.com Reviewed-by: Viacheslav Mitrofanov v.v.mitrofanov@yadro.com Tested-by: Sergei Antonov saproj@gmail.com Reviewed-by: Sergei Antonov saproj@gmail.com
Applied to u-boot/master, thanks!
Hey! It was added without "__packed", see https://lists.denx.de/pipermail/u-boot/2023-May/517370.html

On Wed, 2023-05-10 at 13:05 +0300, Sergei Antonov wrote:
Hey! It was added without "__packed", see https://lists.denx.de/pipermail/u-boot/2023-May/517370.html
Hello, Sergei.
I see Ehsan hadn't sent v5 before v4 was applied. I would make a patch with __packed attribute and post it. Thanks

From: Ehsan Mohandesi emohandesi@linux.microsoft.com
Test the IPv6 network discovery feature if indicated by boardenv file.
Signed-off-by: Ehsan Mohandesi emohandesi@linux.microsoft.com --- configs/sandbox64_defconfig | 2 ++ configs/sandbox_defconfig | 2 ++ configs/sandbox_flattree_defconfig | 2 ++ test/py/tests/test_net.py | 31 ++++++++++++++++++++++++++++++- 4 files changed, 36 insertions(+), 1 deletion(-)
diff --git a/configs/sandbox64_defconfig b/configs/sandbox64_defconfig index af2c56a..be36ede 100644 --- a/configs/sandbox64_defconfig +++ b/configs/sandbox64_defconfig @@ -260,3 +260,5 @@ CONFIG_FWU_MULTI_BANK_UPDATE=y CONFIG_UNIT_TEST=y CONFIG_UT_TIME=y CONFIG_UT_DM=y +CONFIG_IPV6=y +CONFIG_IPV6_ROUTER_DISCOVERY=y diff --git a/configs/sandbox_defconfig b/configs/sandbox_defconfig index ca95b2c..0673c69 100644 --- a/configs/sandbox_defconfig +++ b/configs/sandbox_defconfig @@ -341,3 +341,5 @@ CONFIG_UNIT_TEST=y CONFIG_UT_TIME=y CONFIG_UT_DM=y CONFIG_CMD_2048=y +CONFIG_IPV6=y +CONFIG_IPV6_ROUTER_DISCOVERY=y diff --git a/configs/sandbox_flattree_defconfig b/configs/sandbox_flattree_defconfig index e9fcc5b..d6c8dd2 100644 --- a/configs/sandbox_flattree_defconfig +++ b/configs/sandbox_flattree_defconfig @@ -229,3 +229,5 @@ CONFIG_EFI_CAPSULE_FIRMWARE_FIT=y CONFIG_UNIT_TEST=y CONFIG_UT_TIME=y CONFIG_UT_DM=y +CONFIG_IPV6=y +CONFIG_IPV6_ROUTER_DISCOVERY=y diff --git a/test/py/tests/test_net.py b/test/py/tests/test_net.py index 9ca6743..f85071d 100644 --- a/test/py/tests/test_net.py +++ b/test/py/tests/test_net.py @@ -9,7 +9,7 @@ import u_boot_utils
""" Note: This test relies on boardenv_* containing configuration values to define -which the network environment available for testing. Without this, this test +which network environment is available for testing. Without this, this test will be automatically skipped.
For example: @@ -55,6 +55,11 @@ env__net_nfs_readable_file = { 'size': 5058624, 'crc32': 'c2244b26', } + +# True if a router advertisement service is connected to the network, and should +# be tested. If router advertisement testing is not possible or desired, this +variable may be omitted or set to False. +env__router_on_net = True """
net_set_up = False @@ -126,6 +131,30 @@ def test_net_ping(u_boot_console): output = u_boot_console.run_command('ping $serverip') assert 'is alive' in output
+@pytest.mark.buildconfigspec('IPV6_ROUTER_DISCOVERY') +def test_net_network_discovery(u_boot_console): + """Test the network discovery feature of IPv6. + + An IPv6 network command (ping6 in this case) is run to make U-Boot send a + router solicitation packet, receive a router advertisement message, and + parse it. + A router advertisement service needs to be running for this test to succeed. + U-Boot receives the RA, processes it, and if successful, assigns the gateway + IP and prefix length. + The configuration is provided by the boardenv_* file; see the comment at + the beginning of this file. + """ + + router_on_net = u_boot_console.config.env.get('env__router_on_net', False) + if not router_on_net: + pytest.skip('No router on network') + + fake_host_ip = 'fe80::215:5dff:fef6:2ec6' + output = u_boot_console.run_command('ping6 ' + fake_host_ip) + assert 'ROUTER SOLICITATION 1' in output + assert 'Set gatewayip6:' in output + assert '0000:0000:0000:0000:0000:0000:0000:0000' not in output + @pytest.mark.buildconfigspec('cmd_net') def test_net_tftpboot(u_boot_console): """Test the tftpboot command.

On Fri, 2023-04-21 at 17:08 -0700, emohandesi@linux.microsoft.com wrote:
«Внимание! Данное письмо от внешнего адресата!»
From: Ehsan Mohandesi emohandesi@linux.microsoft.com
Test the IPv6 network discovery feature if indicated by boardenv file.
Signed-off-by: Ehsan Mohandesi emohandesi@linux.microsoft.com
configs/sandbox64_defconfig | 2 ++ configs/sandbox_defconfig | 2 ++ configs/sandbox_flattree_defconfig | 2 ++ test/py/tests/test_net.py | 31 ++++++++++++++++++++++++++++++- 4 files changed, 36 insertions(+), 1 deletion(-)
diff --git a/configs/sandbox64_defconfig b/configs/sandbox64_defconfig index af2c56a..be36ede 100644 --- a/configs/sandbox64_defconfig +++ b/configs/sandbox64_defconfig @@ -260,3 +260,5 @@ CONFIG_FWU_MULTI_BANK_UPDATE=y CONFIG_UNIT_TEST=y CONFIG_UT_TIME=y CONFIG_UT_DM=y +CONFIG_IPV6=y +CONFIG_IPV6_ROUTER_DISCOVERY=y diff --git a/configs/sandbox_defconfig b/configs/sandbox_defconfig index ca95b2c..0673c69 100644 --- a/configs/sandbox_defconfig +++ b/configs/sandbox_defconfig @@ -341,3 +341,5 @@ CONFIG_UNIT_TEST=y CONFIG_UT_TIME=y CONFIG_UT_DM=y CONFIG_CMD_2048=y +CONFIG_IPV6=y +CONFIG_IPV6_ROUTER_DISCOVERY=y diff --git a/configs/sandbox_flattree_defconfig b/configs/sandbox_flattree_defconfig index e9fcc5b..d6c8dd2 100644 --- a/configs/sandbox_flattree_defconfig +++ b/configs/sandbox_flattree_defconfig @@ -229,3 +229,5 @@ CONFIG_EFI_CAPSULE_FIRMWARE_FIT=y CONFIG_UNIT_TEST=y CONFIG_UT_TIME=y CONFIG_UT_DM=y +CONFIG_IPV6=y +CONFIG_IPV6_ROUTER_DISCOVERY=y diff --git a/test/py/tests/test_net.py b/test/py/tests/test_net.py index 9ca6743..f85071d 100644 --- a/test/py/tests/test_net.py +++ b/test/py/tests/test_net.py @@ -9,7 +9,7 @@ import u_boot_utils
""" Note: This test relies on boardenv_* containing configuration values to define -which the network environment available for testing. Without this, this test +which network environment is available for testing. Without this, this test will be automatically skipped.
For example: @@ -55,6 +55,11 @@ env__net_nfs_readable_file = { 'size': 5058624, 'crc32': 'c2244b26', }
+# True if a router advertisement service is connected to the network, and should +# be tested. If router advertisement testing is not possible or desired, this +variable may be omitted or set to False. +env__router_on_net = True """
net_set_up = False @@ -126,6 +131,30 @@ def test_net_ping(u_boot_console): output = u_boot_console.run_command('ping $serverip') assert 'is alive' in output
+@pytest.mark.buildconfigspec('IPV6_ROUTER_DISCOVERY') +def test_net_network_discovery(u_boot_console):
- """Test the network discovery feature of IPv6.
- An IPv6 network command (ping6 in this case) is run to make U-
Boot send a
- router solicitation packet, receive a router advertisement
message, and
- parse it.
- A router advertisement service needs to be running for this test
to succeed.
- U-Boot receives the RA, processes it, and if successful, assigns
the gateway
- IP and prefix length.
- The configuration is provided by the boardenv_* file; see the
comment at
- the beginning of this file.
- """
- router_on_net =
u_boot_console.config.env.get('env__router_on_net', False)
- if not router_on_net:
pytest.skip('No router on network')
- fake_host_ip = 'fe80::215:5dff:fef6:2ec6'
- output = u_boot_console.run_command('ping6 ' + fake_host_ip)
- assert 'ROUTER SOLICITATION 1' in output
- assert 'Set gatewayip6:' in output
- assert '0000:0000:0000:0000:0000:0000:0000:0000' not in output
@pytest.mark.buildconfigspec('cmd_net') def test_net_tftpboot(u_boot_console): """Test the tftpboot command. -- 1.8.3.1
Reviewed-by: Viacheslav Mitrofanov v.v.mitrofanov@yadro.com

On Fri, Apr 21, 2023 at 05:08:22PM -0700, emohandesi@linux.microsoft.com wrote:
From: Ehsan Mohandesi emohandesi@linux.microsoft.com
Test the IPv6 network discovery feature if indicated by boardenv file.
Signed-off-by: Ehsan Mohandesi emohandesi@linux.microsoft.com Reviewed-by: Viacheslav Mitrofanov v.v.mitrofanov@yadro.com
Applied to u-boot/master, thanks!

From: Ehsan Mohandesi emohandesi@linux.microsoft.com
Test router advertisement validation and processing functions.
Signed-off-by: Ehsan Mohandesi emohandesi@linux.microsoft.com --- test/dm/eth.c | 88 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+)
diff --git a/test/dm/eth.c b/test/dm/eth.c index ebf01d8..d05d2a9 100644 --- a/test/dm/eth.c +++ b/test/dm/eth.c @@ -20,6 +20,7 @@ #include <dm/uclass-internal.h> #include <test/test.h> #include <test/ut.h> +#include <ndisc.h>
#define DM_TEST_ETH_NUM 4
@@ -607,3 +608,90 @@ static int dm_test_eth_async_ping_reply(struct unit_test_state *uts) }
DM_TEST(dm_test_eth_async_ping_reply, UT_TESTF_SCAN_FDT); + +#if IS_ENABLED(CONFIG_IPV6_ROUTER_DISCOVERY) + +static u8 ip6_ra_buf[] = {0x60, 0xf, 0xc5, 0x4a, 0x0, 0x38, 0x3a, 0xff, 0xfe, + 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x6, 0x85, 0xe6, + 0x29, 0x77, 0xcb, 0xc8, 0x53, 0xff, 0x2, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x1, 0x86, 0x0, 0xdc, 0x90, 0x40, 0x80, 0x15, 0x18, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3, 0x4, + 0x40, 0xc0, 0x0, 0x0, 0x37, 0xdc, 0x0, 0x0, 0x37, + 0x78, 0x0, 0x0, 0x0, 0x0, 0x20, 0x1, 0xca, 0xfe, 0xca, + 0xfe, 0xca, 0xfe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x1, 0x1, 0x0, 0x15, 0x5d, 0xe2, 0x8a, 0x2}; + +static int dm_test_validate_ra(struct unit_test_state *uts) +{ + struct ip6_hdr *ip6 = (struct ip6_hdr *)ip6_ra_buf; + struct icmp6hdr *icmp = (struct icmp6hdr *)(ip6 + 1); + __be16 temp = 0; + + ut_assert(validate_ra(ip6) == true); + + temp = ip6->payload_len; + ip6->payload_len = 15; + ut_assert(validate_ra(ip6) == false); + ip6->payload_len = temp; + + temp = ip6->saddr.s6_addr16[0]; + ip6->saddr.s6_addr16[0] = 0x2001; + ut_assert(validate_ra(ip6) == false); + ip6->saddr.s6_addr16[0] = temp; + + temp = ip6->hop_limit; + ip6->hop_limit = 15; + ut_assert(validate_ra(ip6) == false); + ip6->hop_limit = temp; + + temp = icmp->icmp6_code; + icmp->icmp6_code = 15; + ut_assert(validate_ra(ip6) == false); + icmp->icmp6_code = temp; + + return 0; +} + +DM_TEST(dm_test_validate_ra, 0); + +static int dm_test_process_ra(struct unit_test_state *uts) +{ + int len = sizeof(ip6_ra_buf); + struct ip6_hdr *ip6 = (struct ip6_hdr *)ip6_ra_buf; + struct icmp6hdr *icmp = (struct icmp6hdr *)(ip6 + 1); + struct ra_msg *msg = (struct ra_msg *)icmp; + unsigned char *option = msg->opt; + struct icmp6_ra_prefix_info *prefix = + (struct icmp6_ra_prefix_info *)option; + __be16 temp = 0; + unsigned char option_len = option[1]; + + ut_assert(process_ra(ip6, len) == 0); + + temp = icmp->icmp6_rt_lifetime; + icmp->icmp6_rt_lifetime = 0; + ut_assert(process_ra(ip6, len) != 0); + icmp->icmp6_rt_lifetime = temp; + + ut_assert(process_ra(ip6, 0) != 0); + + option[1] = 0; + ut_assert(process_ra(ip6, len) != 0); + option[1] = option_len; + + prefix->on_link = false; + ut_assert(process_ra(ip6, len) != 0); + prefix->on_link = true; + + temp = prefix->prefix.s6_addr16[0]; + prefix->prefix.s6_addr16[0] = 0x80fe; + ut_assert(process_ra(ip6, len) != 0); + prefix->prefix.s6_addr16[0] = temp; + + return 0; +} + +DM_TEST(dm_test_process_ra, 0); + +#endif

On Fri, 2023-04-21 at 17:08 -0700, emohandesi@linux.microsoft.com wrote:
«Внимание! Данное письмо от внешнего адресата!»
From: Ehsan Mohandesi emohandesi@linux.microsoft.com
Test router advertisement validation and processing functions.
Signed-off-by: Ehsan Mohandesi emohandesi@linux.microsoft.com
test/dm/eth.c | 88 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+)
diff --git a/test/dm/eth.c b/test/dm/eth.c index ebf01d8..d05d2a9 100644 --- a/test/dm/eth.c +++ b/test/dm/eth.c @@ -20,6 +20,7 @@ #include <dm/uclass-internal.h> #include <test/test.h> #include <test/ut.h> +#include <ndisc.h>
#define DM_TEST_ETH_NUM 4
@@ -607,3 +608,90 @@ static int dm_test_eth_async_ping_reply(struct unit_test_state *uts) }
DM_TEST(dm_test_eth_async_ping_reply, UT_TESTF_SCAN_FDT);
+#if IS_ENABLED(CONFIG_IPV6_ROUTER_DISCOVERY)
+static u8 ip6_ra_buf[] = {0x60, 0xf, 0xc5, 0x4a, 0x0, 0x38, 0x3a, 0xff, 0xfe,
0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x6,
0x85, 0xe6,
0x29, 0x77, 0xcb, 0xc8, 0x53, 0xff, 0x2,
0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0,
0x1, 0x86, 0x0, 0xdc, 0x90, 0x40, 0x80,
0x15, 0x18,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x3, 0x4,
0x40, 0xc0, 0x0, 0x0, 0x37, 0xdc, 0x0, 0x0,
0x37,
0x78, 0x0, 0x0, 0x0, 0x0, 0x20, 0x1, 0xca,
0xfe, 0xca,
0xfe, 0xca, 0xfe, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0,
0x0, 0x1, 0x1, 0x0, 0x15, 0x5d, 0xe2, 0x8a,
0x2};
+static int dm_test_validate_ra(struct unit_test_state *uts) +{
struct ip6_hdr *ip6 = (struct ip6_hdr *)ip6_ra_buf;
struct icmp6hdr *icmp = (struct icmp6hdr *)(ip6 + 1);
__be16 temp = 0;
ut_assert(validate_ra(ip6) == true);
temp = ip6->payload_len;
ip6->payload_len = 15;
ut_assert(validate_ra(ip6) == false);
ip6->payload_len = temp;
temp = ip6->saddr.s6_addr16[0];
ip6->saddr.s6_addr16[0] = 0x2001;
ut_assert(validate_ra(ip6) == false);
ip6->saddr.s6_addr16[0] = temp;
temp = ip6->hop_limit;
ip6->hop_limit = 15;
ut_assert(validate_ra(ip6) == false);
ip6->hop_limit = temp;
temp = icmp->icmp6_code;
icmp->icmp6_code = 15;
ut_assert(validate_ra(ip6) == false);
icmp->icmp6_code = temp;
return 0;
+}
+DM_TEST(dm_test_validate_ra, 0);
+static int dm_test_process_ra(struct unit_test_state *uts) +{
int len = sizeof(ip6_ra_buf);
struct ip6_hdr *ip6 = (struct ip6_hdr *)ip6_ra_buf;
struct icmp6hdr *icmp = (struct icmp6hdr *)(ip6 + 1);
struct ra_msg *msg = (struct ra_msg *)icmp;
unsigned char *option = msg->opt;
struct icmp6_ra_prefix_info *prefix =
(struct icmp6_ra_prefix_info
*)option;
__be16 temp = 0;
unsigned char option_len = option[1];
ut_assert(process_ra(ip6, len) == 0);
temp = icmp->icmp6_rt_lifetime;
icmp->icmp6_rt_lifetime = 0;
ut_assert(process_ra(ip6, len) != 0);
icmp->icmp6_rt_lifetime = temp;
ut_assert(process_ra(ip6, 0) != 0);
option[1] = 0;
ut_assert(process_ra(ip6, len) != 0);
option[1] = option_len;
prefix->on_link = false;
ut_assert(process_ra(ip6, len) != 0);
prefix->on_link = true;
temp = prefix->prefix.s6_addr16[0];
prefix->prefix.s6_addr16[0] = 0x80fe;
ut_assert(process_ra(ip6, len) != 0);
prefix->prefix.s6_addr16[0] = temp;
return 0;
+}
+DM_TEST(dm_test_process_ra, 0);
+#endif
1.8.3.1
Reviewed-by: Viacheslav Mitrofanov v.v.mitrofanov@yadro.com

On Fri, Apr 21, 2023 at 05:08:23PM -0700, emohandesi@linux.microsoft.com wrote:
From: Ehsan Mohandesi emohandesi@linux.microsoft.com
Test router advertisement validation and processing functions.
Signed-off-by: Ehsan Mohandesi emohandesi@linux.microsoft.com Reviewed-by: Viacheslav Mitrofanov v.v.mitrofanov@yadro.com
Applied to u-boot/master, thanks!
participants (8)
-
Ehsan Mohandesi
-
Ehsan Mohandesi
-
emohandesi@linux.microsoft.com
-
Ramon Fried
-
Sergei Antonov
-
Simon Glass
-
Tom Rini
-
Vyacheslav V. Mitrofanov