1// SPDX-License-Identifier: GPL-2.0+ 2/* 3 * Copyright (C) 2013 Allied Telesis Labs NZ 4 * Chris Packham, <judge.packham@gmail.com> 5 * 6 * Copyright (C) 2022 YADRO 7 * Viacheslav Mitrofanov <v.v.mitrofanov@yadro.com> 8 */ 9 10/* Neighbour Discovery for IPv6 */ 11 12#include <common.h> 13#include <net.h> 14#include <net6.h> 15#include <ndisc.h> 16#include <stdlib.h> 17#include <linux/delay.h> 18 19/* IPv6 destination address of packet waiting for ND */ 20struct in6_addr net_nd_sol_packet_ip6 = ZERO_IPV6_ADDR; 21/* IPv6 address we are expecting ND advert from */ 22static struct in6_addr net_nd_rep_packet_ip6 = ZERO_IPV6_ADDR; 23/* MAC destination address of packet waiting for ND */ 24uchar *net_nd_packet_mac; 25/* pointer to packet waiting to be transmitted after ND is resolved */ 26uchar *net_nd_tx_packet; 27static uchar net_nd_packet_buf[PKTSIZE_ALIGN + PKTALIGN]; 28/* size of packet waiting to be transmitted */ 29int net_nd_tx_packet_size; 30/* the timer for ND resolution */ 31ulong net_nd_timer_start; 32/* the number of requests we have sent so far */ 33int net_nd_try; 34struct in6_addr all_routers = ALL_ROUTERS_MULT_ADDR; 35 36#define MAX_RTR_SOLICITATIONS 3 37/* The maximum time to delay sending the first router solicitation message. */ 38#define MAX_SOLICITATION_DELAY 1 // 1 second 39/* The time to wait before sending the next router solicitation message. */ 40#define RTR_SOLICITATION_INTERVAL 4000 // 4 seconds 41 42#define IP6_NDISC_OPT_SPACE(len) (((len) + 2 + 7) & ~7) 43 44/** 45 * ndisc_insert_option() - Insert an option into a neighbor discovery packet 46 * 47 * @opt: pointer to the option element of the neighbor discovery packet 48 * @type: option type to insert 49 * @data: option data to insert 50 * @len: data length 51 * Return: the number of bytes inserted (which may be >= len) 52 */ 53static int ndisc_insert_option(__u8 *opt, int type, u8 *data, int len) 54{ 55 int space = IP6_NDISC_OPT_SPACE(len); 56 57 opt[0] = type; 58 opt[1] = space >> 3; 59 memcpy(&opt[2], data, len); 60 len += 2; 61 62 /* fill the remainder with 0 */ 63 if (space - len > 0) 64 memset(&opt[len], '\0', space - len); 65 66 return space; 67} 68 69/** 70 * ndisc_extract_enetaddr() - Extract the Ethernet address from a ND packet 71 * 72 * Note that the link layer address could be anything but the only networking 73 * media that u-boot supports is Ethernet so we assume we're extracting a 6 74 * byte Ethernet MAC address. 75 * 76 * @ndisc: pointer to ND packet 77 * @enetaddr: extracted MAC addr 78 */ 79static void ndisc_extract_enetaddr(struct nd_msg *ndisc, uchar enetaddr[6]) 80{ 81 memcpy(enetaddr, &ndisc->opt[2], 6); 82} 83 84/** 85 * ndisc_has_option() - Check if the ND packet has the specified option set 86 * 87 * @ip6: pointer to IPv6 header 88 * @type: option type to check 89 * Return: 1 if ND has that option, 0 therwise 90 */ 91static int ndisc_has_option(struct ip6_hdr *ip6, __u8 type) 92{ 93 struct nd_msg *ndisc = (struct nd_msg *)(((uchar *)ip6) + IP6_HDR_SIZE); 94 95 if (ip6->payload_len <= sizeof(struct icmp6hdr)) 96 return 0; 97 98 return ndisc->opt[0] == type; 99} 100 101static void ip6_send_ns(struct in6_addr *neigh_addr) 102{ 103 struct in6_addr dst_adr; 104 unsigned char enetaddr[6]; 105 struct nd_msg *msg; 106 __u16 len; 107 uchar *pkt; 108 unsigned short csum; 109 unsigned int pcsum; 110 111 debug("sending neighbor solicitation for %pI6c our address %pI6c\n", 112 neigh_addr, &net_link_local_ip6); 113 114 /* calculate src, dest IPv6 addr and dest Eth addr */ 115 ip6_make_snma(&dst_adr, neigh_addr); 116 ip6_make_mult_ethdstaddr(enetaddr, &dst_adr); 117 len = sizeof(struct icmp6hdr) + IN6ADDRSZ + 118 IP6_NDISC_OPT_SPACE(INETHADDRSZ); 119 120 pkt = (uchar *)net_tx_packet; 121 pkt += net_set_ether(pkt, enetaddr, PROT_IP6); 122 pkt += ip6_add_hdr(pkt, &net_link_local_ip6, &dst_adr, PROT_ICMPV6, 123 IPV6_NDISC_HOPLIMIT, len); 124 125 /* ICMPv6 - NS */ 126 msg = (struct nd_msg *)pkt; 127 msg->icmph.icmp6_type = IPV6_NDISC_NEIGHBOUR_SOLICITATION; 128 msg->icmph.icmp6_code = 0; 129 memset(&msg->icmph.icmp6_cksum, 0, sizeof(__be16)); 130 memset(&msg->icmph.icmp6_unused, 0, sizeof(__be32)); 131 132 /* Set the target address and llsaddr option */ 133 net_copy_ip6(&msg->target, neigh_addr); 134 ndisc_insert_option(msg->opt, ND_OPT_SOURCE_LL_ADDR, net_ethaddr, 135 INETHADDRSZ); 136 137 /* checksum */ 138 pcsum = csum_partial((__u8 *)msg, len, 0); 139 csum = csum_ipv6_magic(&net_link_local_ip6, &dst_adr, 140 len, PROT_ICMPV6, pcsum); 141 msg->icmph.icmp6_cksum = csum; 142 pkt += len; 143 144 /* send it! */ 145 net_send_packet(net_tx_packet, (pkt - net_tx_packet)); 146} 147 148/* 149 * ip6_send_rs() - Send IPv6 Router Solicitation Message. 150 * 151 * A router solicitation is sent to discover a router. RS message creation is 152 * based on RFC 4861 section 4.1. Router Solicitation Message Format. 153 */ 154void ip6_send_rs(void) 155{ 156 unsigned char enetaddr[6]; 157 struct rs_msg *msg; 158 __u16 icmp_len; 159 uchar *pkt; 160 unsigned short csum; 161 unsigned int pcsum; 162 static unsigned int retry_count; 163 164 if (!ip6_is_unspecified_addr(&net_gateway6) && 165 net_prefix_length != 0) { 166 net_set_state(NETLOOP_SUCCESS); 167 return; 168 } else if (retry_count >= MAX_RTR_SOLICITATIONS) { 169 net_set_state(NETLOOP_FAIL); 170 net_set_timeout_handler(0, NULL); 171 retry_count = 0; 172 return; 173 } 174 175 printf("ROUTER SOLICITATION %d\n", retry_count + 1); 176 177 ip6_make_mult_ethdstaddr(enetaddr, &all_routers); 178 /* 179 * ICMP length is the size of ICMP header (8) + one option (8) = 16. 180 * The option is 2 bytes of type and length + 6 bytes for MAC. 181 */ 182 icmp_len = sizeof(struct icmp6hdr) + IP6_NDISC_OPT_SPACE(INETHADDRSZ); 183 184 pkt = (uchar *)net_tx_packet; 185 pkt += net_set_ether(pkt, enetaddr, PROT_IP6); 186 pkt += ip6_add_hdr(pkt, &net_link_local_ip6, &all_routers, PROT_ICMPV6, 187 IPV6_NDISC_HOPLIMIT, icmp_len); 188 189 /* ICMPv6 - RS */ 190 msg = (struct rs_msg *)pkt; 191 msg->icmph.icmp6_type = IPV6_NDISC_ROUTER_SOLICITATION; 192 msg->icmph.icmp6_code = 0; 193 memset(&msg->icmph.icmp6_cksum, 0, sizeof(__be16)); 194 memset(&msg->icmph.icmp6_unused, 0, sizeof(__be32)); 195 196 /* Set the llsaddr option */ 197 ndisc_insert_option(msg->opt, ND_OPT_SOURCE_LL_ADDR, net_ethaddr, 198 INETHADDRSZ); 199 200 /* checksum */ 201 pcsum = csum_partial((__u8 *)msg, icmp_len, 0); 202 csum = csum_ipv6_magic(&net_link_local_ip6, &all_routers, 203 icmp_len, PROT_ICMPV6, pcsum); 204 msg->icmph.icmp6_cksum = csum; 205 pkt += icmp_len; 206 207 /* Wait up to 1 second if it is the first try to get the RA */ 208 if (retry_count == 0) 209 udelay(((unsigned int)rand() % 1000000) * MAX_SOLICITATION_DELAY); 210 211 /* send it! */ 212 net_send_packet(net_tx_packet, (pkt - net_tx_packet)); 213 214 retry_count++; 215 net_set_timeout_handler(RTR_SOLICITATION_INTERVAL, ip6_send_rs); 216} 217 218static void 219ip6_send_na(uchar *eth_dst_addr, struct in6_addr *neigh_addr, 220 struct in6_addr *target) 221{ 222 struct nd_msg *msg; 223 __u16 len; 224 uchar *pkt; 225 unsigned short csum; 226 227 debug("sending neighbor advertisement for %pI6c to %pI6c (%pM)\n", 228 target, neigh_addr, eth_dst_addr); 229 230 len = sizeof(struct icmp6hdr) + IN6ADDRSZ + 231 IP6_NDISC_OPT_SPACE(INETHADDRSZ); 232 233 pkt = (uchar *)net_tx_packet; 234 pkt += net_set_ether(pkt, eth_dst_addr, PROT_IP6); 235 pkt += ip6_add_hdr(pkt, &net_link_local_ip6, neigh_addr, 236 PROT_ICMPV6, IPV6_NDISC_HOPLIMIT, len); 237 238 /* ICMPv6 - NA */ 239 msg = (struct nd_msg *)pkt; 240 msg->icmph.icmp6_type = IPV6_NDISC_NEIGHBOUR_ADVERTISEMENT; 241 msg->icmph.icmp6_code = 0; 242 memset(&msg->icmph.icmp6_cksum, 0, sizeof(__be16)); 243 memset(&msg->icmph.icmp6_unused, 0, sizeof(__be32)); 244 msg->icmph.icmp6_dataun.u_nd_advt.solicited = 1; 245 msg->icmph.icmp6_dataun.u_nd_advt.override = 1; 246 /* Set the target address and lltargetaddr option */ 247 net_copy_ip6(&msg->target, target); 248 ndisc_insert_option(msg->opt, ND_OPT_TARGET_LL_ADDR, net_ethaddr, 249 INETHADDRSZ); 250 251 /* checksum */ 252 csum = csum_ipv6_magic(&net_link_local_ip6, 253 neigh_addr, len, PROT_ICMPV6, 254 csum_partial((__u8 *)msg, len, 0)); 255 msg->icmph.icmp6_cksum = csum; 256 pkt += len; 257 258 /* send it! */ 259 net_send_packet(net_tx_packet, (pkt - net_tx_packet)); 260} 261 262void ndisc_request(void) 263{ 264 if (!ip6_addr_in_subnet(&net_ip6, &net_nd_sol_packet_ip6, 265 net_prefix_length)) { 266 if (ip6_is_unspecified_addr(&net_gateway6)) { 267 puts("## Warning: gatewayip6 is needed but not set\n"); 268 net_nd_rep_packet_ip6 = net_nd_sol_packet_ip6; 269 } else { 270 net_nd_rep_packet_ip6 = net_gateway6; 271 } 272 } else { 273 net_nd_rep_packet_ip6 = net_nd_sol_packet_ip6; 274 } 275 276 ip6_send_ns(&net_nd_rep_packet_ip6); 277} 278 279int ndisc_timeout_check(void) 280{ 281 ulong t; 282 283 if (ip6_is_unspecified_addr(&net_nd_sol_packet_ip6)) 284 return 0; 285 286 t = get_timer(0); 287 288 /* check for NDISC timeout */ 289 if ((t - net_nd_timer_start) > NDISC_TIMEOUT) { 290 net_nd_try++; 291 if (net_nd_try >= NDISC_TIMEOUT_COUNT) { 292 puts("\nNeighbour discovery retry count exceeded; " 293 "starting again\n"); 294 net_nd_try = 0; 295 net_set_state(NETLOOP_FAIL); 296 } else { 297 net_nd_timer_start = t; 298 ndisc_request(); 299 } 300 } 301 return 1; 302} 303 304/* 305 * ndisc_init() - Make initial steps for ND state machine. 306 * Usually move variables into initial state. 307 */ 308void ndisc_init(void) 309{ 310 net_nd_packet_mac = NULL; 311 net_nd_tx_packet = NULL; 312 net_nd_sol_packet_ip6 = net_null_addr_ip6; 313 net_nd_rep_packet_ip6 = net_null_addr_ip6; 314 net_nd_tx_packet_size = 0; 315 net_nd_tx_packet = &net_nd_packet_buf[0] + (PKTALIGN - 1); 316 net_nd_tx_packet -= (ulong)net_nd_tx_packet % PKTALIGN; 317} 318 319/* 320 * validate_ra() - Validate the router advertisement message. 321 * 322 * @ip6: Pointer to the router advertisement packet 323 * 324 * Check if the router advertisement message is valid. Conditions are 325 * according to RFC 4861 section 6.1.2. Validation of Router Advertisement 326 * Messages. 327 * 328 * Return: true if the message is valid and false if it is invalid. 329 */ 330bool validate_ra(struct ip6_hdr *ip6) 331{ 332 struct icmp6hdr *icmp = (struct icmp6hdr *)(ip6 + 1); 333 334 /* ICMP length (derived from the IP length) should be 16 or more octets. */ 335 if (ip6->payload_len < 16) 336 return false; 337 338 /* Source IP Address should be a valid link-local address. */ 339 if ((ntohs(ip6->saddr.s6_addr16[0]) & IPV6_LINK_LOCAL_MASK) != 340 IPV6_LINK_LOCAL_PREFIX) 341 return false; 342 343 /* 344 * The IP Hop Limit field should have a value of 255, i.e., the packet 345 * could not possibly have been forwarded by a router. 346 */ 347 if (ip6->hop_limit != 255) 348 return false; 349 350 /* ICMP checksum has already been checked in net_ip6_handler. */ 351 352 if (icmp->icmp6_code != 0) 353 return false; 354 355 return true; 356} 357 358/* 359 * process_ra() - Process the router advertisement packet. 360 * 361 * @ip6: Pointer to the router advertisement packet 362 * @len: Length of the router advertisement packet 363 * 364 * Process the received router advertisement message. 365 * Although RFC 4861 requires retaining at least two router addresses, we only 366 * keep one because of the U-Boot limitations and its goal of lightweight code. 367 * 368 * Return: 0 - RA is a default router and contains valid prefix information. 369 * Non-zero - RA options are invalid or do not indicate it is a default router 370 * or do not contain valid prefix information. 371 */ 372int process_ra(struct ip6_hdr *ip6, int len) 373{ 374 /* Pointer to the ICMP section of the packet */ 375 struct icmp6hdr *icmp = (struct icmp6hdr *)(ip6 + 1); 376 struct ra_msg *msg = (struct ra_msg *)icmp; 377 int remaining_option_len = len - IP6_HDR_SIZE - sizeof(struct ra_msg); 378 unsigned short int option_len; /* Length of each option */ 379 /* Pointer to the ICMPv6 message options */ 380 unsigned char *option = NULL; 381 /* 8-bit identifier of the type of ICMPv6 option */ 382 unsigned char type = 0; 383 struct icmp6_ra_prefix_info *prefix = NULL; 384 385 if (len > ETH_MAX_MTU) 386 return -EMSGSIZE; 387 /* Ignore the packet if router lifetime is 0. */ 388 if (!icmp->icmp6_rt_lifetime) 389 return -EOPNOTSUPP; 390 391 /* Processing the options */ 392 option = msg->opt; 393 while (remaining_option_len > 0) { 394 /* The 2nd byte of the option is its length. */ 395 option_len = option[1]; 396 /* All included options should have a positive length. */ 397 if (option_len == 0) 398 return -EINVAL; 399 400 type = option[0]; 401 /* All option types except Prefix Information are ignored. */ 402 switch (type) { 403 case ND_OPT_SOURCE_LL_ADDR: 404 case ND_OPT_TARGET_LL_ADDR: 405 case ND_OPT_REDIRECT_HDR: 406 case ND_OPT_MTU: 407 break; 408 case ND_OPT_PREFIX_INFO: 409 prefix = (struct icmp6_ra_prefix_info *)option; 410 /* The link-local prefix 0xfe80::/10 is ignored. */ 411 if ((ntohs(prefix->prefix.s6_addr16[0]) & 412 IPV6_LINK_LOCAL_MASK) == IPV6_LINK_LOCAL_PREFIX) 413 break; 414 if (prefix->on_link && ntohl(prefix->valid_lifetime)) { 415 net_prefix_length = prefix->prefix_len; 416 net_gateway6 = ip6->saddr; 417 return 0; 418 } 419 break; 420 default: 421 debug("Unknown IPv6 Neighbor Discovery Option 0x%x\n", 422 type); 423 } 424 425 option_len <<= 3; /* Option length is a multiple of 8. */ 426 remaining_option_len -= option_len; 427 option += option_len; 428 } 429 430 return -EADDRNOTAVAIL; 431} 432 433int ndisc_receive(struct ethernet_hdr *et, struct ip6_hdr *ip6, int len) 434{ 435 struct icmp6hdr *icmp = 436 (struct icmp6hdr *)(((uchar *)ip6) + IP6_HDR_SIZE); 437 struct nd_msg *ndisc = (struct nd_msg *)icmp; 438 uchar neigh_eth_addr[6]; 439 int err = 0; // The error code returned calling functions. 440 441 switch (icmp->icmp6_type) { 442 case IPV6_NDISC_NEIGHBOUR_SOLICITATION: 443 debug("received neighbor solicitation for %pI6c from %pI6c\n", 444 &ndisc->target, &ip6->saddr); 445 if (ip6_is_our_addr(&ndisc->target) && 446 ndisc_has_option(ip6, ND_OPT_SOURCE_LL_ADDR)) { 447 ndisc_extract_enetaddr(ndisc, neigh_eth_addr); 448 ip6_send_na(neigh_eth_addr, &ip6->saddr, 449 &ndisc->target); 450 } 451 break; 452 453 case IPV6_NDISC_NEIGHBOUR_ADVERTISEMENT: 454 /* are we waiting for a reply ? */ 455 if (ip6_is_unspecified_addr(&net_nd_sol_packet_ip6)) 456 break; 457 458 if ((memcmp(&ndisc->target, &net_nd_rep_packet_ip6, 459 sizeof(struct in6_addr)) == 0) && 460 ndisc_has_option(ip6, ND_OPT_TARGET_LL_ADDR)) { 461 ndisc_extract_enetaddr(ndisc, neigh_eth_addr); 462 463 /* save address for later use */ 464 if (!net_nd_packet_mac) 465 net_nd_packet_mac = neigh_eth_addr; 466 467 /* modify header, and transmit it */ 468 memcpy(((struct ethernet_hdr *)net_nd_tx_packet)->et_dest, 469 neigh_eth_addr, 6); 470 471 net_send_packet(net_nd_tx_packet, 472 net_nd_tx_packet_size); 473 474 /* no ND request pending now */ 475 net_nd_sol_packet_ip6 = net_null_addr_ip6; 476 net_nd_tx_packet_size = 0; 477 net_nd_packet_mac = NULL; 478 } 479 break; 480 case IPV6_NDISC_ROUTER_SOLICITATION: 481 break; 482 case IPV6_NDISC_ROUTER_ADVERTISEMENT: 483 debug("Received router advertisement for %pI6c from %pI6c\n", 484 &ip6->daddr, &ip6->saddr); 485 /* 486 * If gateway and prefix are set, the RA packet is ignored. The 487 * reason is that the U-Boot code is supposed to be as compact 488 * as possible and does not need to take care of multiple 489 * routers. In addition to that, U-Boot does not want to handle 490 * scenarios like a router setting its lifetime to zero to 491 * indicate it is not routing anymore. U-Boot program has a 492 * short life when the system boots up and does not need such 493 * sophistication. 494 */ 495 if (!ip6_is_unspecified_addr(&net_gateway6) && 496 net_prefix_length != 0) { 497 break; 498 } 499 if (!validate_ra(ip6)) { 500 debug("Invalid router advertisement message.\n"); 501 break; 502 } 503 err = process_ra(ip6, len); 504 if (err) 505 debug("Ignored router advertisement. Error: %d\n", err); 506 else 507 printf("Set gatewayip6: %pI6c, prefix_length: %d\n", 508 &net_gateway6, net_prefix_length); 509 break; 510 default: 511 debug("Unexpected ICMPv6 type 0x%x\n", icmp->icmp6_type); 512 return -1; 513 } 514 515 return 0; 516} 517