summaryrefslogtreecommitdiff
path: root/net/ndisc.c
diff options
context:
space:
mode:
Diffstat (limited to 'net/ndisc.c')
-rw-r--r--net/ndisc.c515
1 files changed, 515 insertions, 0 deletions
diff --git a/net/ndisc.c b/net/ndisc.c
new file mode 100644
index 00000000000..d417c5987ac
--- /dev/null
+++ b/net/ndisc.c
@@ -0,0 +1,515 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2013 Allied Telesis Labs NZ
+ * Chris Packham, <judge.packham@gmail.com>
+ *
+ * Copyright (C) 2022 YADRO
+ * Viacheslav Mitrofanov <v.v.mitrofanov@yadro.com>
+ */
+
+/* Neighbour Discovery for IPv6 */
+
+#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;
+/* IPv6 address we are expecting ND advert from */
+static struct in6_addr net_nd_rep_packet_ip6 = ZERO_IPV6_ADDR;
+/* MAC destination address of packet waiting for ND */
+uchar *net_nd_packet_mac;
+/* pointer to packet waiting to be transmitted after ND is resolved */
+uchar *net_nd_tx_packet;
+static uchar net_nd_packet_buf[PKTSIZE_ALIGN + PKTALIGN];
+/* size of packet waiting to be transmitted */
+int net_nd_tx_packet_size;
+/* the timer for ND resolution */
+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
+ *
+ * @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(__u8 *opt, int type, u8 *data, int len)
+{
+ int space = IP6_NDISC_OPT_SPACE(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(&opt[len], '\0', space - len);
+
+ return space;
+}
+
+/**
+ * ndisc_extract_enetaddr() - Extract the Ethernet address from a ND packet
+ *
+ * Note that the link layer address could be anything but the only networking
+ * media that u-boot supports is Ethernet so we assume we're extracting a 6
+ * byte Ethernet MAC address.
+ *
+ * @ndisc: pointer to ND packet
+ * @enetaddr: extracted MAC addr
+ */
+static void ndisc_extract_enetaddr(struct nd_msg *ndisc, uchar enetaddr[6])
+{
+ memcpy(enetaddr, &ndisc->opt[2], 6);
+}
+
+/**
+ * ndisc_has_option() - Check if the ND packet has the specified option set
+ *
+ * @ip6: pointer to IPv6 header
+ * @type: option type to check
+ * Return: 1 if ND has that option, 0 therwise
+ */
+static int ndisc_has_option(struct ip6_hdr *ip6, __u8 type)
+{
+ struct nd_msg *ndisc = (struct nd_msg *)(((uchar *)ip6) + IP6_HDR_SIZE);
+
+ if (ip6->payload_len <= sizeof(struct icmp6hdr))
+ return 0;
+
+ return ndisc->opt[0] == type;
+}
+
+static void ip6_send_ns(struct in6_addr *neigh_addr)
+{
+ struct in6_addr dst_adr;
+ unsigned char enetaddr[6];
+ struct nd_msg *msg;
+ __u16 len;
+ uchar *pkt;
+ unsigned short csum;
+ unsigned int pcsum;
+
+ debug("sending neighbor solicitation for %pI6c our address %pI6c\n",
+ neigh_addr, &net_link_local_ip6);
+
+ /* calculate src, dest IPv6 addr and dest Eth addr */
+ ip6_make_snma(&dst_adr, neigh_addr);
+ ip6_make_mult_ethdstaddr(enetaddr, &dst_adr);
+ len = sizeof(struct icmp6hdr) + IN6ADDRSZ +
+ 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, &dst_adr, PROT_ICMPV6,
+ IPV6_NDISC_HOPLIMIT, len);
+
+ /* ICMPv6 - NS */
+ msg = (struct nd_msg *)pkt;
+ msg->icmph.icmp6_type = IPV6_NDISC_NEIGHBOUR_SOLICITATION;
+ msg->icmph.icmp6_code = 0;
+ memset(&msg->icmph.icmp6_cksum, 0, sizeof(__be16));
+ memset(&msg->icmph.icmp6_unused, 0, sizeof(__be32));
+
+ /* Set the target address and llsaddr option */
+ net_copy_ip6(&msg->target, neigh_addr);
+ ndisc_insert_option(msg->opt, ND_OPT_SOURCE_LL_ADDR, net_ethaddr,
+ INETHADDRSZ);
+
+ /* checksum */
+ pcsum = csum_partial((__u8 *)msg, len, 0);
+ csum = csum_ipv6_magic(&net_link_local_ip6, &dst_adr,
+ len, PROT_ICMPV6, pcsum);
+ msg->icmph.icmp6_cksum = csum;
+ pkt += len;
+
+ /* send it! */
+ 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)
+{
+ struct nd_msg *msg;
+ __u16 len;
+ uchar *pkt;
+ unsigned short csum;
+
+ debug("sending neighbor advertisement for %pI6c to %pI6c (%pM)\n",
+ target, neigh_addr, eth_dst_addr);
+
+ len = sizeof(struct icmp6hdr) + IN6ADDRSZ +
+ IP6_NDISC_OPT_SPACE(INETHADDRSZ);
+
+ pkt = (uchar *)net_tx_packet;
+ pkt += net_set_ether(pkt, eth_dst_addr, PROT_IP6);
+ pkt += ip6_add_hdr(pkt, &net_link_local_ip6, neigh_addr,
+ PROT_ICMPV6, IPV6_NDISC_HOPLIMIT, len);
+
+ /* ICMPv6 - NA */
+ msg = (struct nd_msg *)pkt;
+ msg->icmph.icmp6_type = IPV6_NDISC_NEIGHBOUR_ADVERTISEMENT;
+ msg->icmph.icmp6_code = 0;
+ memset(&msg->icmph.icmp6_cksum, 0, sizeof(__be16));
+ memset(&msg->icmph.icmp6_unused, 0, sizeof(__be32));
+ msg->icmph.icmp6_dataun.u_nd_advt.solicited = 1;
+ 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->opt, ND_OPT_TARGET_LL_ADDR, net_ethaddr,
+ INETHADDRSZ);
+
+ /* checksum */
+ csum = csum_ipv6_magic(&net_link_local_ip6,
+ neigh_addr, len, PROT_ICMPV6,
+ csum_partial((__u8 *)msg, len, 0));
+ msg->icmph.icmp6_cksum = csum;
+ pkt += len;
+
+ /* send it! */
+ net_send_packet(net_tx_packet, (pkt - net_tx_packet));
+}
+
+void ndisc_request(void)
+{
+ if (!ip6_addr_in_subnet(&net_ip6, &net_nd_sol_packet_ip6,
+ net_prefix_length)) {
+ if (ip6_is_unspecified_addr(&net_gateway6)) {
+ puts("## Warning: gatewayip6 is needed but not set\n");
+ net_nd_rep_packet_ip6 = net_nd_sol_packet_ip6;
+ } else {
+ net_nd_rep_packet_ip6 = net_gateway6;
+ }
+ } else {
+ net_nd_rep_packet_ip6 = net_nd_sol_packet_ip6;
+ }
+
+ ip6_send_ns(&net_nd_rep_packet_ip6);
+}
+
+int ndisc_timeout_check(void)
+{
+ ulong t;
+
+ if (ip6_is_unspecified_addr(&net_nd_sol_packet_ip6))
+ return 0;
+
+ t = get_timer(0);
+
+ /* check for NDISC timeout */
+ if ((t - net_nd_timer_start) > NDISC_TIMEOUT) {
+ net_nd_try++;
+ if (net_nd_try >= NDISC_TIMEOUT_COUNT) {
+ puts("\nNeighbour discovery retry count exceeded; "
+ "starting again\n");
+ net_nd_try = 0;
+ net_set_state(NETLOOP_FAIL);
+ } else {
+ net_nd_timer_start = t;
+ ndisc_request();
+ }
+ }
+ 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;
+ net_nd_tx_packet = NULL;
+ net_nd_sol_packet_ip6 = net_null_addr_ip6;
+ net_nd_rep_packet_ip6 = net_null_addr_ip6;
+ net_nd_tx_packet_size = 0;
+ net_nd_tx_packet = &net_nd_packet_buf[0] + (PKTALIGN - 1);
+ 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;
+
+ if (len > ETH_MAX_MTU)
+ return -EMSGSIZE;
+ /* 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:
+ debug("received neighbor solicitation for %pI6c from %pI6c\n",
+ &ndisc->target, &ip6->saddr);
+ if (ip6_is_our_addr(&ndisc->target) &&
+ ndisc_has_option(ip6, ND_OPT_SOURCE_LL_ADDR)) {
+ ndisc_extract_enetaddr(ndisc, neigh_eth_addr);
+ ip6_send_na(neigh_eth_addr, &ip6->saddr,
+ &ndisc->target);
+ }
+ break;
+
+ case IPV6_NDISC_NEIGHBOUR_ADVERTISEMENT:
+ /* are we waiting for a reply ? */
+ if (ip6_is_unspecified_addr(&net_nd_sol_packet_ip6))
+ break;
+
+ if ((memcmp(&ndisc->target, &net_nd_rep_packet_ip6,
+ sizeof(struct in6_addr)) == 0) &&
+ ndisc_has_option(ip6, ND_OPT_TARGET_LL_ADDR)) {
+ ndisc_extract_enetaddr(ndisc, neigh_eth_addr);
+
+ /* save address for later use */
+ if (!net_nd_packet_mac)
+ net_nd_packet_mac = neigh_eth_addr;
+
+ /* modify header, and transmit it */
+ memcpy(((struct ethernet_hdr *)net_nd_tx_packet)->et_dest,
+ neigh_eth_addr, 6);
+
+ net_send_packet(net_nd_tx_packet,
+ net_nd_tx_packet_size);
+
+ /* no ND request pending now */
+ net_nd_sol_packet_ip6 = net_null_addr_ip6;
+ net_nd_tx_packet_size = 0;
+ 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;
+ }
+
+ return 0;
+}