[ipv6] Make IPv6 routing work with addresses not on local network
[gpxe.git] / src / net / ipv6.c
blob489db44f25cee760f53f239e98871b0e0d43f4b7
1 #include <errno.h>
2 #include <stdint.h>
3 #include <string.h>
4 #include <stdlib.h>
5 #include <stdio.h>
6 #include <byteswap.h>
7 #include <gpxe/in.h>
8 #include <gpxe/ip6.h>
9 #include <gpxe/ndp.h>
10 #include <gpxe/list.h>
11 #include <gpxe/icmp6.h>
12 #include <gpxe/tcpip.h>
13 #include <gpxe/socket.h>
14 #include <gpxe/iobuf.h>
15 #include <gpxe/netdevice.h>
16 #include <gpxe/if_ether.h>
18 struct net_protocol ipv6_protocol;
20 char * inet6_ntoa ( struct in6_addr in6 );
22 /* Unspecified IP6 address */
23 static struct in6_addr ip6_none = {
24 .in6_u.u6_addr32 = { 0,0,0,0 }
27 /** An IPv6 routing table entry */
28 struct ipv6_miniroute {
29 /* List of miniroutes */
30 struct list_head list;
32 /* Network device */
33 struct net_device *netdev;
35 /* Destination prefix */
36 struct in6_addr prefix;
37 /* Prefix length */
38 int prefix_len;
39 /* IPv6 address of interface */
40 struct in6_addr address;
41 /* Gateway address */
42 struct in6_addr gateway;
45 /** List of IPv6 miniroutes */
46 static LIST_HEAD ( miniroutes );
48 /**
49 * Add IPv6 minirouting table entry
51 * @v netdev Network device
52 * @v prefix Destination prefix (in bits)
53 * @v address Address of the interface
54 * @v gateway Gateway address (or ::0 for no gateway)
55 * @ret miniroute Routing table entry, or NULL
57 static struct ipv6_miniroute * __malloc
58 add_ipv6_miniroute ( struct net_device *netdev, struct in6_addr prefix,
59 int prefix_len, struct in6_addr address,
60 struct in6_addr gateway ) {
61 struct ipv6_miniroute *miniroute;
63 DBG( "ipv6 add: %s/%d ", inet6_ntoa ( address ), prefix_len );
64 DBG( "gw %s\n", inet6_ntoa( gateway ) );
66 miniroute = malloc ( sizeof ( *miniroute ) );
67 if ( miniroute ) {
68 /* Record routing information */
69 miniroute->netdev = netdev_get ( netdev );
70 miniroute->prefix = prefix;
71 miniroute->prefix_len = prefix_len;
72 miniroute->address = address;
73 miniroute->gateway = gateway;
75 /* Add miniroute to list of miniroutes */
76 if ( !IP6_EQUAL ( gateway, ip6_none ) ) {
77 list_add_tail ( &miniroute->list, &miniroutes );
78 } else {
79 list_add ( &miniroute->list, &miniroutes );
83 return miniroute;
86 /**
87 * Delete IPv6 minirouting table entry
89 * @v miniroute Routing table entry
91 static void del_ipv6_miniroute ( struct ipv6_miniroute *miniroute ) {
93 DBG ( "ipv6 del: %s/%d\n", inet6_ntoa(miniroute->address),
94 miniroute->prefix_len );
96 netdev_put ( miniroute->netdev );
97 list_del ( &miniroute->list );
98 free ( miniroute );
102 * Add IPv6 interface
104 * @v netdev Network device
105 * @v prefix Destination prefix
106 * @v address Address of the interface
107 * @v gateway Gateway address (or ::0 for no gateway)
109 int add_ipv6_address ( struct net_device *netdev, struct in6_addr prefix,
110 int prefix_len, struct in6_addr address,
111 struct in6_addr gateway ) {
112 struct ipv6_miniroute *miniroute;
114 /* Clear any existing address for this net device */
115 /* del_ipv6_address ( netdev ); */
117 /* Add new miniroute */
118 miniroute = add_ipv6_miniroute ( netdev, prefix, prefix_len, address,
119 gateway );
120 if ( ! miniroute )
121 return -ENOMEM;
123 return 0;
127 * Remove IPv6 interface
129 * @v netdev Network device
131 void del_ipv6_address ( struct net_device *netdev ) {
132 struct ipv6_miniroute *miniroute;
134 list_for_each_entry ( miniroute, &miniroutes, list ) {
135 if ( miniroute->netdev == netdev ) {
136 del_ipv6_miniroute ( miniroute );
137 break;
143 * Calculate TCPIP checksum
145 * @v iobuf I/O buffer
146 * @v tcpip TCP/IP protocol
148 * This function constructs the pseudo header and completes the checksum in the
149 * upper layer header.
151 static uint16_t ipv6_tx_csum ( struct io_buffer *iobuf, uint16_t csum ) {
152 struct ip6_header *ip6hdr = iobuf->data;
153 struct ipv6_pseudo_header pshdr;
155 /* Calculate pseudo header */
156 memset ( &pshdr, 0, sizeof ( pshdr ) );
157 pshdr.src = ip6hdr->src;
158 pshdr.dest = ip6hdr->dest;
159 pshdr.len = htons ( iob_len ( iobuf ) - sizeof ( *ip6hdr ) );
160 pshdr.nxt_hdr = ip6hdr->nxt_hdr;
162 /* Update checksum value */
163 return tcpip_continue_chksum ( csum, &pshdr, sizeof ( pshdr ) );
167 * Dump IP6 header for debugging
169 * ip6hdr IPv6 header
171 void ipv6_dump ( struct ip6_header *ip6hdr ) {
172 /* Because inet6_ntoa returns a static char[16], each call needs to be
173 * separate. */
174 DBG ( "IP6 %p src %s ", ip6hdr, inet6_ntoa( ip6hdr->src ) );
175 DBG ( "dest %s nxt_hdr %d len %d\n", inet6_ntoa ( ip6hdr->dest ),
176 ip6hdr->nxt_hdr, ntohs ( ip6hdr->payload_len ) );
180 * Transmit IP6 packet
182 * iobuf I/O buffer
183 * tcpip TCP/IP protocol
184 * st_dest Destination socket address
186 * This function prepends the IPv6 headers to the payload an transmits it.
188 static int ipv6_tx ( struct io_buffer *iobuf,
189 struct tcpip_protocol *tcpip,
190 struct sockaddr_tcpip *st_src __unused,
191 struct sockaddr_tcpip *st_dest,
192 struct net_device *netdev,
193 uint16_t *trans_csum ) {
194 struct sockaddr_in6 *dest = ( struct sockaddr_in6* ) st_dest;
195 struct in6_addr next_hop, gateway = ip6_none;
196 struct ipv6_miniroute *miniroute;
197 uint8_t ll_dest_buf[MAX_LL_ADDR_LEN], ip1, ip2;
198 const uint8_t *ll_dest = ll_dest_buf;
199 int rc, multicast, linklocal, bits, offset;
201 /* Check for multicast transmission. */
202 multicast = dest->sin6_addr.in6_u.u6_addr8[0] == 0xFF;
204 /* Construct the IPv6 packet */
205 struct ip6_header *ip6hdr = iob_push ( iobuf, sizeof ( *ip6hdr ) );
206 memset ( ip6hdr, 0, sizeof ( *ip6hdr) );
207 ip6hdr->ver_traffic_class_flow_label = htonl ( 0x60000000 );//IP6_VERSION;
208 ip6hdr->payload_len = htons ( iob_len ( iobuf ) - sizeof ( *ip6hdr ) );
209 ip6hdr->nxt_hdr = tcpip->tcpip_proto;
210 ip6hdr->hop_limit = IP6_HOP_LIMIT; // 255
212 /* Determine the next hop address and interface. */
213 next_hop = dest->sin6_addr;
214 list_for_each_entry ( miniroute, &miniroutes, list ) {
215 /* Link-local route? */
216 linklocal = (miniroute->address.in6_u.u6_addr16[0] & htons(0xFE80)) == htons(0xFE80);
218 /* Handle link-local for multicast. */
219 if ( multicast )
221 /* Link-local scope? */
222 if ( next_hop.in6_u.u6_addr8[0] & 0x2 ) {
223 if ( linklocal ) {
224 netdev = miniroute->netdev;
225 ip6hdr->src = miniroute->address;
226 break;
227 } else {
228 /* Should be link-local address. */
229 continue;
231 } else {
232 /* Can we route on this interface?
233 (assume non-link-local means routable) */
234 if ( ! linklocal ) {
235 netdev = miniroute->netdev;
236 ip6hdr->src = miniroute->address;
237 break;
242 /* Check for a prefix match on the route. */
243 if ( ! memcmp ( &next_hop, &miniroute->prefix, miniroute->prefix_len / 8 ) ) {
244 rc = 0;
246 /* Handle extra bits in the prefix. */
247 if ( ( miniroute->prefix_len % 8 ) ||
248 ( miniroute->prefix_len < 8 ) ) {
249 DBG ( "ipv6: prefix is not aligned to a byte.\n" );
251 /* Compare the remaining bits. */
252 offset = miniroute->prefix_len / 8;
253 bits = miniroute->prefix_len % 8;
255 ip1 = next_hop.in6_u.u6_addr8[offset];
256 ip2 = miniroute->prefix.in6_u.u6_addr8[offset];
257 if ( ! ( ( ip1 & (0xFF >> (8 - bits)) ) &
258 ( ip2 ) ) ) {
259 rc = 1;
262 } else {
263 rc = 1;
266 /* Matched? */
267 if( rc == 0 ) {
268 netdev = miniroute->netdev;
269 ip6hdr->src = miniroute->address;
270 break;
273 if ( ( ! ( IP6_EQUAL ( miniroute->gateway, ip6_none ) ) ) &&
274 ( IP6_EQUAL ( gateway, ip6_none ) ) ) {
275 netdev = miniroute->netdev;
276 ip6hdr->src = miniroute->address;
277 gateway = miniroute->gateway;
280 /* No network interface identified */
281 if ( ( ! netdev ) ) {
282 DBG ( "No route to host %s\n", inet6_ntoa ( ip6hdr->dest ) );
283 rc = -ENETUNREACH;
284 goto err;
285 } else if ( ! IP6_EQUAL ( gateway, ip6_none ) ) {
286 next_hop = gateway;
289 /* Add the next hop to the packet. */
290 ip6hdr->dest = dest->sin6_addr;
292 /* Complete the transport layer checksum */
293 if ( trans_csum )
294 *trans_csum = ipv6_tx_csum ( iobuf, *trans_csum );
296 /* Print IPv6 header */
297 /* ipv6_dump ( ip6hdr ); */
299 /* Resolve link layer address */
300 if ( next_hop.in6_u.u6_addr8[0] == 0xFF ) {
301 ll_dest_buf[0] = 0x33;
302 ll_dest_buf[1] = 0x33;
303 ll_dest_buf[2] = next_hop.in6_u.u6_addr8[12];
304 ll_dest_buf[3] = next_hop.in6_u.u6_addr8[13];
305 ll_dest_buf[4] = next_hop.in6_u.u6_addr8[14];
306 ll_dest_buf[5] = next_hop.in6_u.u6_addr8[15];
307 } else {
308 /* Unicast address needs to be resolved by NDP */
309 if ( ( rc = ndp_resolve ( netdev, &next_hop, &ip6hdr->src,
310 ll_dest_buf ) ) != 0 ) {
311 DBG ( "No entry for %s\n", inet6_ntoa ( next_hop ) );
312 goto err;
316 /* Transmit packet */
317 return net_tx ( iobuf, netdev, &ipv6_protocol, ll_dest );
319 err:
320 free_iob ( iobuf );
321 return rc;
325 * Process next IP6 header
327 * @v iobuf I/O buffer
328 * @v nxt_hdr Next header number
329 * @v src Source socket address
330 * @v dest Destination socket address
331 * @v netdev Net device the packet arrived on
332 * @v phcsm Partial checksum over the IPv6 psuedo-header.
334 * Refer http://www.iana.org/assignments/ipv6-parameters for the numbers
336 static int ipv6_process_nxt_hdr ( struct io_buffer *iobuf, uint8_t nxt_hdr,
337 struct sockaddr_tcpip *src, struct sockaddr_tcpip *dest,
338 struct net_device *netdev, uint16_t phcsm ) {
339 switch ( nxt_hdr ) {
340 case IP6_HOPBYHOP:
341 case IP6_ROUTING:
342 case IP6_FRAGMENT:
343 case IP6_AUTHENTICATION:
344 case IP6_DEST_OPTS:
345 case IP6_ESP:
346 DBG ( "Function not implemented for header %d\n", nxt_hdr );
347 return -ENOSYS;
348 case IP6_ICMP6:
349 return icmp6_rx ( iobuf, src, dest, netdev, phcsm );
350 case IP6_NO_HEADER:
351 DBG ( "No next header\n" );
352 return 0;
354 /* Next header is not a IPv6 extension header */
355 return tcpip_rx ( iobuf, nxt_hdr, src, dest, phcsm );
359 * Process incoming IP6 packets
361 * @v iobuf I/O buffer
362 * @v netdev Network device
363 * @v ll_source Link-layer source address
365 * This function processes a IPv6 packet
367 static int ipv6_rx ( struct io_buffer *iobuf,
368 __unused struct net_device *netdev,
369 __unused const void *ll_source ) {
371 struct ip6_header *ip6hdr = iobuf->data;
372 union {
373 struct sockaddr_in6 sin6;
374 struct sockaddr_tcpip st;
375 } src, dest;
376 uint16_t phcsm = 0;
378 /* Sanity check */
379 if ( iob_len ( iobuf ) < sizeof ( *ip6hdr ) ) {
380 DBG ( "Packet too short (%zd bytes)\n", iob_len ( iobuf ) );
381 goto drop;
384 /* Print IP6 header for debugging */
385 /* ipv6_dump ( ip6hdr ); */
387 /* Check header version */
388 if ( ( ntohl( ip6hdr->ver_traffic_class_flow_label ) & 0xf0000000 ) != 0x60000000 ) {
389 DBG ( "Invalid protocol version\n" );
390 goto drop;
393 /* Check the payload length */
394 if ( ntohs ( ip6hdr->payload_len ) > iob_len ( iobuf ) ) {
395 DBG ( "Inconsistent packet length (%d bytes)\n",
396 ip6hdr->payload_len );
397 goto drop;
400 /* Ignore the traffic class and flow control values */
402 /* Construct socket address */
403 memset ( &src, 0, sizeof ( src ) );
404 src.sin6.sin_family = AF_INET6;
405 src.sin6.sin6_addr = ip6hdr->src;
406 memset ( &dest, 0, sizeof ( dest ) );
407 dest.sin6.sin_family = AF_INET6;
408 dest.sin6.sin6_addr = ip6hdr->dest;
410 /* Calculate the psuedo-header checksum before the IP6 header is
411 * stripped away. */
412 phcsm = ipv6_tx_csum ( iobuf, 0 );
414 /* Strip header */
415 iob_unput ( iobuf, iob_len ( iobuf ) - ntohs ( ip6hdr->payload_len ) -
416 sizeof ( *ip6hdr ) );
417 iob_pull ( iobuf, sizeof ( *ip6hdr ) );
419 /* Send it to the transport layer */
420 return ipv6_process_nxt_hdr ( iobuf, ip6hdr->nxt_hdr, &src.st, &dest.st,
421 netdev, phcsm );
423 drop:
424 DBG ( "IP6 packet dropped\n" );
425 free_iob ( iobuf );
426 return -1;
430 * Convert an IPv6 address to a string.
432 * @v in6 Address to convert to string.
434 * Converts an IPv6 address to a string, and applies zero-compression as needed
435 * to condense the address for easier reading/typing.
437 char * inet6_ntoa ( struct in6_addr in6 ) {
438 static char buf[40];
439 uint16_t *bytes = ( uint16_t* ) &in6;
440 size_t i = 0, longest = 0, tmp = 0, long_idx = ~0;
442 /* ::0 */
443 if ( IP6_EQUAL ( in6, ip6_none ) ) {
444 tmp = sprintf ( buf, "::0" );
445 buf[tmp] = 0;
446 return buf;
449 /* Determine the longest string of zeroes for zero-compression. */
450 for ( ; i < 8; i++ ) {
451 if ( !bytes[i] )
452 tmp++;
453 else if(tmp > longest) {
454 longest = tmp;
455 long_idx = i - longest;
457 tmp = 0;
461 /* Check for last word being zero. This will cause long_idx to be zero,
462 * which confuses the actual buffer fill code. */
463 if(tmp && (tmp > longest)) {
464 longest = tmp;
465 long_idx = 8 - longest;
468 /* Inject into the buffer. */
469 tmp = 0;
470 for ( i = 0; i < 8; i++ ) {
471 /* Should we skip over a string of zeroes? */
472 if ( i == long_idx ) {
473 i += longest;
474 tmp += sprintf( buf + tmp, ":" );
476 /* Handle end-of-string. */
477 if(i > 7)
478 break;
481 /* Insert this component of the address. */
482 tmp += sprintf(buf + tmp, "%x", ntohs(bytes[i]));
484 /* Add the next colon, if needed. */
485 if ( i < 7 )
486 tmp += sprintf( buf + tmp, ":" );
489 buf[tmp] = 0;
491 return buf;
495 * Convert a string to an IPv6 address.
497 * @v in6 String to convert to an address.
499 int inet6_aton ( const char *cp, struct in6_addr *inp ) {
500 char convbuf[40];
501 char *tmp = convbuf, *next = convbuf;
502 size_t i = 0;
504 strcpy ( convbuf, cp );
506 DBG ( "ipv6 converting %s to an in6_addr\n", cp );
508 /* Handle the first part of the address (or all of it if no zero-compression. */
509 while ( ( next = strchr ( next, ':' ) ) ) {
510 /* Cater for zero-compression. */
511 if ( *tmp == ':' )
512 break;
514 /* Convert to integer. */
515 inp->s6_addr16[i++] = htons( strtoul ( tmp, 0, 16 ) );
517 *next++ = 0;
518 tmp = next;
521 /* Handle the case where no zero-compression is needed, but every word
522 * was filled in the address. */
523 if ( ( i == 7 ) && ( *tmp != ':' ) ) {
524 inp->s6_addr16[i++] = htons( strtoul ( tmp, 0, 16 ) );
526 else
528 /* Handle zero-compression now (go backwards). */
529 i = 7;
530 if ( i && ( *tmp == ':' ) ) {
531 next = strrchr ( next, ':' );
534 tmp = next + 1;
535 *next-- = 0;
537 /* Convert to integer. */
538 inp->s6_addr16[i--] = htons( strtoul ( tmp, 0, 16 ) );
539 } while ( ( next = strrchr ( next, ':' ) ) );
543 return 1;
546 static const char * ipv6_ntoa ( const void *net_addr ) {
547 return inet6_ntoa ( * ( ( struct in6_addr * ) net_addr ) );
550 static int ipv6_check ( struct net_device *netdev, const void *net_addr ) {
551 const struct in6_addr *address = net_addr;
552 struct ipv6_miniroute *miniroute;
554 list_for_each_entry ( miniroute, &miniroutes, list ) {
555 if ( ( miniroute->netdev == netdev ) &&
556 ( ! memcmp ( &miniroute->address, address, 16 ) ) ) {
557 /* Found matching address */
558 return 0;
561 return -ENOENT;
564 /** IPv6 protocol */
565 struct net_protocol ipv6_protocol __net_protocol = {
566 .name = "IPV6",
567 .net_proto = htons ( ETH_P_IPV6 ),
568 .net_addr_len = sizeof ( struct in6_addr ),
569 .rx = ipv6_rx,
570 .ntoa = ipv6_ntoa,
573 /** IPv6 TCPIP net protocol */
574 struct tcpip_net_protocol ipv6_tcpip_protocol __tcpip_net_protocol = {
575 .name = "IPv6",
576 .sa_family = AF_INET6,
577 .tx = ipv6_tx,
580 /** IPv6 ICMPv6 protocol, for NDP */
581 struct icmp6_net_protocol ipv6_icmp6_protocol __icmp6_net_protocol = {
582 .net_protocol = &ipv6_protocol,
583 .check = ipv6_check,