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 */
20 struct in6_addr net_nd_sol_packet_ip6 = ZERO_IPV6_ADDR;
21 /* IPv6 address we are expecting ND advert from */
22 static struct in6_addr net_nd_rep_packet_ip6 = ZERO_IPV6_ADDR;
23 /* MAC destination address of packet waiting for ND */
24 uchar *net_nd_packet_mac;
25 /* pointer to packet waiting to be transmitted after ND is resolved */
26 uchar *net_nd_tx_packet;
27 static uchar net_nd_packet_buf[PKTSIZE_ALIGN + PKTALIGN];
28 /* size of packet waiting to be transmitted */
29 int net_nd_tx_packet_size;
30 /* the timer for ND resolution */
31 ulong net_nd_timer_start;
32 /* the number of requests we have sent so far */
33 int net_nd_try;
34 struct 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  */
ndisc_insert_option(__u8 * opt,int type,u8 * data,int len)53 static 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  */
ndisc_extract_enetaddr(struct nd_msg * ndisc,uchar enetaddr[6])79 static 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  */
ndisc_has_option(struct ip6_hdr * ip6,__u8 type)91 static 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 
ip6_send_ns(struct in6_addr * neigh_addr)101 static 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  */
ip6_send_rs(void)154 void 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 
218 static void
ip6_send_na(uchar * eth_dst_addr,struct in6_addr * neigh_addr,struct in6_addr * target)219 ip6_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 
ndisc_request(void)262 void 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 
ndisc_timeout_check(void)279 int 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  */
ndisc_init(void)308 void 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  */
validate_ra(struct ip6_hdr * ip6)330 bool 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  */
process_ra(struct ip6_hdr * ip6,int len)372 int 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 	/* Ignore the packet if router lifetime is 0. */
386 	if (!icmp->icmp6_rt_lifetime)
387 		return -EOPNOTSUPP;
388 
389 	/* Processing the options */
390 	option = msg->opt;
391 	while (remaining_option_len > 0) {
392 		/* The 2nd byte of the option is its length. */
393 		option_len = option[1];
394 		/* All included options should have a positive length. */
395 		if (option_len == 0)
396 			return -EINVAL;
397 
398 		type = option[0];
399 		/* All option types except Prefix Information are ignored. */
400 		switch (type) {
401 		case ND_OPT_SOURCE_LL_ADDR:
402 		case ND_OPT_TARGET_LL_ADDR:
403 		case ND_OPT_REDIRECT_HDR:
404 		case ND_OPT_MTU:
405 			break;
406 		case ND_OPT_PREFIX_INFO:
407 			prefix = (struct icmp6_ra_prefix_info *)option;
408 			/* The link-local prefix 0xfe80::/10 is ignored. */
409 			if ((ntohs(prefix->prefix.s6_addr16[0]) &
410 			     IPV6_LINK_LOCAL_MASK) == IPV6_LINK_LOCAL_PREFIX)
411 				break;
412 			if (prefix->on_link && ntohl(prefix->valid_lifetime)) {
413 				net_prefix_length = prefix->prefix_len;
414 				net_gateway6 = ip6->saddr;
415 				return 0;
416 			}
417 			break;
418 		default:
419 			debug("Unknown IPv6 Neighbor Discovery Option 0x%x\n",
420 			      type);
421 		}
422 
423 		option_len <<= 3; /* Option length is a multiple of 8. */
424 		remaining_option_len -= option_len;
425 		option += option_len;
426 	}
427 
428 	return -EADDRNOTAVAIL;
429 }
430 
ndisc_receive(struct ethernet_hdr * et,struct ip6_hdr * ip6,int len)431 int ndisc_receive(struct ethernet_hdr *et, struct ip6_hdr *ip6, int len)
432 {
433 	struct icmp6hdr *icmp =
434 	    (struct icmp6hdr *)(((uchar *)ip6) + IP6_HDR_SIZE);
435 	struct nd_msg *ndisc = (struct nd_msg *)icmp;
436 	uchar neigh_eth_addr[6];
437 	int err = 0;	// The error code returned calling functions.
438 
439 	switch (icmp->icmp6_type) {
440 	case IPV6_NDISC_NEIGHBOUR_SOLICITATION:
441 		debug("received neighbor solicitation for %pI6c from %pI6c\n",
442 		      &ndisc->target, &ip6->saddr);
443 		if (ip6_is_our_addr(&ndisc->target) &&
444 		    ndisc_has_option(ip6, ND_OPT_SOURCE_LL_ADDR)) {
445 			ndisc_extract_enetaddr(ndisc, neigh_eth_addr);
446 			ip6_send_na(neigh_eth_addr, &ip6->saddr,
447 				    &ndisc->target);
448 		}
449 		break;
450 
451 	case IPV6_NDISC_NEIGHBOUR_ADVERTISEMENT:
452 		/* are we waiting for a reply ? */
453 		if (ip6_is_unspecified_addr(&net_nd_sol_packet_ip6))
454 			break;
455 
456 		if ((memcmp(&ndisc->target, &net_nd_rep_packet_ip6,
457 			    sizeof(struct in6_addr)) == 0) &&
458 		    ndisc_has_option(ip6, ND_OPT_TARGET_LL_ADDR)) {
459 			ndisc_extract_enetaddr(ndisc, neigh_eth_addr);
460 
461 			/* save address for later use */
462 			if (!net_nd_packet_mac)
463 				net_nd_packet_mac = neigh_eth_addr;
464 
465 			/* modify header, and transmit it */
466 			memcpy(((struct ethernet_hdr *)net_nd_tx_packet)->et_dest,
467 			       neigh_eth_addr, 6);
468 
469 			net_send_packet(net_nd_tx_packet,
470 					net_nd_tx_packet_size);
471 
472 			/* no ND request pending now */
473 			net_nd_sol_packet_ip6 = net_null_addr_ip6;
474 			net_nd_tx_packet_size = 0;
475 			net_nd_packet_mac = NULL;
476 		}
477 		break;
478 	case IPV6_NDISC_ROUTER_SOLICITATION:
479 		break;
480 	case IPV6_NDISC_ROUTER_ADVERTISEMENT:
481 		debug("Received router advertisement for %pI6c from %pI6c\n",
482 		      &ip6->daddr, &ip6->saddr);
483 		/*
484 		 * If gateway and prefix are set, the RA packet is ignored. The
485 		 * reason is that the U-Boot code is supposed to be as compact
486 		 * as possible and does not need to take care of multiple
487 		 * routers. In addition to that, U-Boot does not want to handle
488 		 * scenarios like a router setting its lifetime to zero to
489 		 * indicate it is not routing anymore. U-Boot program has a
490 		 * short life when the system boots up and does not need such
491 		 * sophistication.
492 		 */
493 		if (!ip6_is_unspecified_addr(&net_gateway6) &&
494 		    net_prefix_length != 0) {
495 			break;
496 		}
497 		if (!validate_ra(ip6)) {
498 			debug("Invalid router advertisement message.\n");
499 			break;
500 		}
501 		err = process_ra(ip6, len);
502 		if (err)
503 			debug("Ignored router advertisement. Error: %d\n", err);
504 		else
505 			printf("Set gatewayip6: %pI6c, prefix_length: %d\n",
506 			       &net_gateway6, net_prefix_length);
507 		break;
508 	default:
509 		debug("Unexpected ICMPv6 type 0x%x\n", icmp->icmp6_type);
510 		return -1;
511 	}
512 
513 	return 0;
514 }
515