[ipv6] Implement EUI-64 generation as a function
[gpxe.git] / src / net / ipv6.c
blob9d63ce836e0fed8026e89cc10e969288c7eafcf0
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 * Generate an EUI-64 from a given link-local address.
51 * @v out pointer to buffer to receive the EUI-64
52 * @v ll pointer to buffer containing the link-local address
54 void ipv6_generate_eui64 ( uint8_t *out, uint8_t *ll ) {
55 assert ( out != 0 );
56 assert ( ll != 0 );
58 /* Create an EUI-64 identifier. */
59 memcpy( out, ll, 3 );
60 memcpy( out + 5, ll + 3, 3 );
61 out[3] = 0xFF;
62 out[4] = 0xFE;
64 /* Designate that this is in fact an EUI-64. */
65 out[0] |= 0x2;
68 /**
69 * Add IPv6 minirouting table entry
71 * @v netdev Network device
72 * @v prefix Destination prefix (in bits)
73 * @v address Address of the interface
74 * @v gateway Gateway address (or ::0 for no gateway)
75 * @ret miniroute Routing table entry, or NULL
77 static struct ipv6_miniroute * __malloc
78 add_ipv6_miniroute ( struct net_device *netdev, struct in6_addr prefix,
79 int prefix_len, struct in6_addr address,
80 struct in6_addr gateway ) {
81 struct ipv6_miniroute *miniroute;
83 DBG( "ipv6 add: %s/%d ", inet6_ntoa ( address ), prefix_len );
84 DBG( "gw %s\n", inet6_ntoa( gateway ) );
86 miniroute = malloc ( sizeof ( *miniroute ) );
87 if ( miniroute ) {
88 /* Record routing information */
89 miniroute->netdev = netdev_get ( netdev );
90 miniroute->prefix = prefix;
91 miniroute->prefix_len = prefix_len;
92 miniroute->address = address;
93 miniroute->gateway = gateway;
95 /* Add miniroute to list of miniroutes */
96 if ( !IP6_EQUAL ( gateway, ip6_none ) ) {
97 list_add_tail ( &miniroute->list, &miniroutes );
98 } else {
99 list_add ( &miniroute->list, &miniroutes );
103 return miniroute;
107 * Delete IPv6 minirouting table entry
109 * @v miniroute Routing table entry
111 static void del_ipv6_miniroute ( struct ipv6_miniroute *miniroute ) {
113 DBG ( "ipv6 del: %s/%d\n", inet6_ntoa(miniroute->address),
114 miniroute->prefix_len );
116 netdev_put ( miniroute->netdev );
117 list_del ( &miniroute->list );
118 free ( miniroute );
122 * Add IPv6 interface
124 * @v netdev Network device
125 * @v prefix Destination prefix
126 * @v address Address of the interface
127 * @v gateway Gateway address (or ::0 for no gateway)
129 int add_ipv6_address ( struct net_device *netdev, struct in6_addr prefix,
130 int prefix_len, struct in6_addr address,
131 struct in6_addr gateway ) {
132 struct ipv6_miniroute *miniroute;
134 /* Clear any existing address for this net device */
135 /* del_ipv6_address ( netdev ); */
137 /* Add new miniroute */
138 miniroute = add_ipv6_miniroute ( netdev, prefix, prefix_len, address,
139 gateway );
140 if ( ! miniroute )
141 return -ENOMEM;
143 return 0;
147 * Remove IPv6 interface
149 * @v netdev Network device
151 void del_ipv6_address ( struct net_device *netdev ) {
152 struct ipv6_miniroute *miniroute;
154 list_for_each_entry ( miniroute, &miniroutes, list ) {
155 if ( miniroute->netdev == netdev ) {
156 del_ipv6_miniroute ( miniroute );
157 break;
163 * Calculate TCPIP checksum
165 * @v iobuf I/O buffer
166 * @v tcpip TCP/IP protocol
168 * This function constructs the pseudo header and completes the checksum in the
169 * upper layer header.
171 static uint16_t ipv6_tx_csum ( struct io_buffer *iobuf, uint16_t csum ) {
172 struct ip6_header *ip6hdr = iobuf->data;
173 struct ipv6_pseudo_header pshdr;
175 /* Calculate pseudo header */
176 memset ( &pshdr, 0, sizeof ( pshdr ) );
177 pshdr.src = ip6hdr->src;
178 pshdr.dest = ip6hdr->dest;
179 pshdr.len = htons ( iob_len ( iobuf ) - sizeof ( *ip6hdr ) );
180 pshdr.nxt_hdr = ip6hdr->nxt_hdr;
182 /* Update checksum value */
183 return tcpip_continue_chksum ( csum, &pshdr, sizeof ( pshdr ) );
187 * Dump IP6 header for debugging
189 * ip6hdr IPv6 header
191 void ipv6_dump ( struct ip6_header *ip6hdr ) {
192 /* Because inet6_ntoa returns a static char[16], each call needs to be
193 * separate. */
194 DBG ( "IP6 %p src %s ", ip6hdr, inet6_ntoa( ip6hdr->src ) );
195 DBG ( "dest %s nxt_hdr %d len %d\n", inet6_ntoa ( ip6hdr->dest ),
196 ip6hdr->nxt_hdr, ntohs ( ip6hdr->payload_len ) );
200 * Transmit IP6 packet
202 * iobuf I/O buffer
203 * tcpip TCP/IP protocol
204 * st_dest Destination socket address
206 * This function prepends the IPv6 headers to the payload an transmits it.
208 static int ipv6_tx ( struct io_buffer *iobuf,
209 struct tcpip_protocol *tcpip,
210 struct sockaddr_tcpip *st_src __unused,
211 struct sockaddr_tcpip *st_dest,
212 struct net_device *netdev,
213 uint16_t *trans_csum ) {
214 struct sockaddr_in6 *dest = ( struct sockaddr_in6* ) st_dest;
215 struct in6_addr next_hop, gateway = ip6_none;
216 struct ipv6_miniroute *miniroute;
217 uint8_t ll_dest_buf[MAX_LL_ADDR_LEN], ip1, ip2;
218 const uint8_t *ll_dest = ll_dest_buf;
219 int rc, multicast, linklocal, bits, offset;
221 /* Check for multicast transmission. */
222 multicast = dest->sin6_addr.in6_u.u6_addr8[0] == 0xFF;
224 /* Construct the IPv6 packet */
225 struct ip6_header *ip6hdr = iob_push ( iobuf, sizeof ( *ip6hdr ) );
226 memset ( ip6hdr, 0, sizeof ( *ip6hdr) );
227 ip6hdr->ver_traffic_class_flow_label = htonl ( 0x60000000 );//IP6_VERSION;
228 ip6hdr->payload_len = htons ( iob_len ( iobuf ) - sizeof ( *ip6hdr ) );
229 ip6hdr->nxt_hdr = tcpip->tcpip_proto;
230 ip6hdr->hop_limit = IP6_HOP_LIMIT; // 255
232 /* Determine the next hop address and interface. */
233 next_hop = dest->sin6_addr;
234 list_for_each_entry ( miniroute, &miniroutes, list ) {
235 /* Check for specific netdev */
236 if ( netdev && ( miniroute->netdev != netdev ) ) {
237 continue;
240 /* Link-local route? */
241 linklocal = (miniroute->address.in6_u.u6_addr16[0] & htons(0xFE80)) == htons(0xFE80);
243 /* Handle link-local for multicast. */
244 if ( multicast )
246 /* Link-local scope? */
247 if ( next_hop.in6_u.u6_addr8[0] & 0x2 ) {
248 if ( linklocal ) {
249 netdev = miniroute->netdev;
250 ip6hdr->src = miniroute->address;
251 break;
252 } else {
253 /* Should be link-local address. */
254 continue;
256 } else {
257 /* Can we route on this interface?
258 (assume non-link-local means routable) */
259 if ( ! linklocal ) {
260 netdev = miniroute->netdev;
261 ip6hdr->src = miniroute->address;
262 break;
267 /* Check for a prefix match on the route. */
268 if ( ! memcmp ( &next_hop, &miniroute->prefix, miniroute->prefix_len / 8 ) ) {
269 rc = 0;
271 /* Handle extra bits in the prefix. */
272 if ( ( miniroute->prefix_len % 8 ) ||
273 ( miniroute->prefix_len < 8 ) ) {
274 DBG ( "ipv6: prefix is not aligned to a byte.\n" );
276 /* Compare the remaining bits. */
277 offset = miniroute->prefix_len / 8;
278 bits = miniroute->prefix_len % 8;
280 ip1 = next_hop.in6_u.u6_addr8[offset];
281 ip2 = miniroute->prefix.in6_u.u6_addr8[offset];
282 if ( ! ( ( ip1 & (0xFF >> (8 - bits)) ) &
283 ( ip2 ) ) ) {
284 rc = 1;
287 } else {
288 rc = 1;
291 /* Matched? */
292 if( rc == 0 ) {
293 netdev = miniroute->netdev;
294 ip6hdr->src = miniroute->address;
295 break;
298 if ( ( ! ( IP6_EQUAL ( miniroute->gateway, ip6_none ) ) ) &&
299 ( IP6_EQUAL ( gateway, ip6_none ) ) ) {
300 netdev = miniroute->netdev;
301 ip6hdr->src = miniroute->address;
302 gateway = miniroute->gateway;
305 /* No network interface identified */
306 if ( ( ! netdev ) ) {
307 DBG ( "No route to host %s\n", inet6_ntoa ( ip6hdr->dest ) );
308 rc = -ENETUNREACH;
309 goto err;
310 } else if ( ! IP6_EQUAL ( gateway, ip6_none ) ) {
311 next_hop = gateway;
314 /* Add the next hop to the packet. */
315 ip6hdr->dest = dest->sin6_addr;
317 /* Complete the transport layer checksum */
318 if ( trans_csum )
319 *trans_csum = ipv6_tx_csum ( iobuf, *trans_csum );
321 /* Print IPv6 header */
322 /* ipv6_dump ( ip6hdr ); */
324 /* Resolve link layer address */
325 if ( next_hop.in6_u.u6_addr8[0] == 0xFF ) {
326 ll_dest_buf[0] = 0x33;
327 ll_dest_buf[1] = 0x33;
328 ll_dest_buf[2] = next_hop.in6_u.u6_addr8[12];
329 ll_dest_buf[3] = next_hop.in6_u.u6_addr8[13];
330 ll_dest_buf[4] = next_hop.in6_u.u6_addr8[14];
331 ll_dest_buf[5] = next_hop.in6_u.u6_addr8[15];
332 } else {
333 /* Unicast address needs to be resolved by NDP */
334 if ( ( rc = ndp_resolve ( netdev, &next_hop, &ip6hdr->src,
335 ll_dest_buf ) ) != 0 ) {
336 DBG ( "No entry for %s\n", inet6_ntoa ( next_hop ) );
337 goto err;
341 /* Transmit packet */
342 return net_tx ( iobuf, netdev, &ipv6_protocol, ll_dest );
344 err:
345 free_iob ( iobuf );
346 return rc;
350 * Process next IP6 header
352 * @v iobuf I/O buffer
353 * @v nxt_hdr Next header number
354 * @v src Source socket address
355 * @v dest Destination socket address
356 * @v netdev Net device the packet arrived on
357 * @v phcsm Partial checksum over the IPv6 psuedo-header.
359 * Refer http://www.iana.org/assignments/ipv6-parameters for the numbers
361 static int ipv6_process_nxt_hdr ( struct io_buffer *iobuf, uint8_t nxt_hdr,
362 struct sockaddr_tcpip *src, struct sockaddr_tcpip *dest,
363 struct net_device *netdev, uint16_t phcsm ) {
364 switch ( nxt_hdr ) {
365 case IP6_HOPBYHOP:
366 case IP6_ROUTING:
367 case IP6_FRAGMENT:
368 case IP6_AUTHENTICATION:
369 case IP6_DEST_OPTS:
370 case IP6_ESP:
371 DBG ( "Function not implemented for header %d\n", nxt_hdr );
372 return -ENOSYS;
373 case IP6_ICMP6:
374 return icmp6_rx ( iobuf, src, dest, netdev, phcsm );
375 case IP6_NO_HEADER:
376 DBG ( "No next header\n" );
377 return 0;
379 /* Next header is not a IPv6 extension header */
380 return tcpip_rx ( iobuf, nxt_hdr, src, dest, phcsm );
384 * Process incoming IP6 packets
386 * @v iobuf I/O buffer
387 * @v netdev Network device
388 * @v ll_source Link-layer source address
390 * This function processes a IPv6 packet
392 static int ipv6_rx ( struct io_buffer *iobuf,
393 __unused struct net_device *netdev,
394 __unused const void *ll_source ) {
396 struct ip6_header *ip6hdr = iobuf->data;
397 union {
398 struct sockaddr_in6 sin6;
399 struct sockaddr_tcpip st;
400 } src, dest;
401 uint16_t phcsm = 0;
403 /* Sanity check */
404 if ( iob_len ( iobuf ) < sizeof ( *ip6hdr ) ) {
405 DBG ( "Packet too short (%zd bytes)\n", iob_len ( iobuf ) );
406 goto drop;
409 /* Print IP6 header for debugging */
410 /* ipv6_dump ( ip6hdr ); */
412 /* Check header version */
413 if ( ( ntohl( ip6hdr->ver_traffic_class_flow_label ) & 0xf0000000 ) != 0x60000000 ) {
414 DBG ( "Invalid protocol version\n" );
415 goto drop;
418 /* Check the payload length */
419 if ( ntohs ( ip6hdr->payload_len ) > iob_len ( iobuf ) ) {
420 DBG ( "Inconsistent packet length (%d bytes)\n",
421 ip6hdr->payload_len );
422 goto drop;
425 /* Ignore the traffic class and flow control values */
427 /* Construct socket address */
428 memset ( &src, 0, sizeof ( src ) );
429 src.sin6.sin_family = AF_INET6;
430 src.sin6.sin6_addr = ip6hdr->src;
431 memset ( &dest, 0, sizeof ( dest ) );
432 dest.sin6.sin_family = AF_INET6;
433 dest.sin6.sin6_addr = ip6hdr->dest;
435 /* Calculate the psuedo-header checksum before the IP6 header is
436 * stripped away. */
437 phcsm = ipv6_tx_csum ( iobuf, 0 );
439 /* Strip header */
440 iob_unput ( iobuf, iob_len ( iobuf ) - ntohs ( ip6hdr->payload_len ) -
441 sizeof ( *ip6hdr ) );
442 iob_pull ( iobuf, sizeof ( *ip6hdr ) );
444 /* Send it to the transport layer */
445 return ipv6_process_nxt_hdr ( iobuf, ip6hdr->nxt_hdr, &src.st, &dest.st,
446 netdev, phcsm );
448 drop:
449 DBG ( "IP6 packet dropped\n" );
450 free_iob ( iobuf );
451 return -1;
455 * Convert an IPv6 address to a string.
457 * @v in6 Address to convert to string.
459 * Converts an IPv6 address to a string, and applies zero-compression as needed
460 * to condense the address for easier reading/typing.
462 char * inet6_ntoa ( struct in6_addr in6 ) {
463 static char buf[40];
464 uint16_t *bytes = ( uint16_t* ) &in6;
465 size_t i = 0, longest = 0, tmp = 0, long_idx = ~0;
467 /* ::0 */
468 if ( IP6_EQUAL ( in6, ip6_none ) ) {
469 tmp = sprintf ( buf, "::0" );
470 buf[tmp] = 0;
471 return buf;
474 /* Determine the longest string of zeroes for zero-compression. */
475 for ( ; i < 8; i++ ) {
476 if ( !bytes[i] )
477 tmp++;
478 else if(tmp > longest) {
479 longest = tmp;
480 long_idx = i - longest;
482 tmp = 0;
486 /* Check for last word being zero. This will cause long_idx to be zero,
487 * which confuses the actual buffer fill code. */
488 if(tmp && (tmp > longest)) {
489 longest = tmp;
490 long_idx = 8 - longest;
493 /* Inject into the buffer. */
494 tmp = 0;
495 for ( i = 0; i < 8; i++ ) {
496 /* Should we skip over a string of zeroes? */
497 if ( i == long_idx ) {
498 i += longest;
499 tmp += sprintf( buf + tmp, ":" );
501 /* Handle end-of-string. */
502 if(i > 7)
503 break;
506 /* Insert this component of the address. */
507 tmp += sprintf(buf + tmp, "%x", ntohs(bytes[i]));
509 /* Add the next colon, if needed. */
510 if ( i < 7 )
511 tmp += sprintf( buf + tmp, ":" );
514 buf[tmp] = 0;
516 return buf;
520 * Convert a string to an IPv6 address.
522 * @v in6 String to convert to an address.
524 int inet6_aton ( const char *cp, struct in6_addr *inp ) {
525 char convbuf[40];
526 char *tmp = convbuf, *next = convbuf;
527 size_t i = 0, len = strlen ( cp );
528 int ok;
529 char c;
531 /* Verify a valid address. */
532 if ( ! len ) {
533 return 0;
536 for ( ; i < len; i++ ) {
537 c = cp[i];
539 ok = c == ':';
540 ok = ok || ( ( c >= '0' ) && ( c <= '9' ) );
541 ok = ok || ( ( c >= 'a' ) && ( c <= 'f' ) );
542 ok = ok || ( ( c >= 'A' ) && ( c <= 'F' ) );
544 if ( ! ok ) {
545 return 0;
549 strcpy ( convbuf, cp );
551 DBG ( "ipv6 converting %s to an in6_addr\n", cp );
553 /* Handle the first part of the address (or all of it if no zero-compression. */
554 i = 0;
555 while ( ( next = strchr ( next, ':' ) ) ) {
556 /* Cater for zero-compression. */
557 if ( *tmp == ':' )
558 break;
560 /* Convert to integer. */
561 inp->s6_addr16[i++] = htons( strtoul ( tmp, 0, 16 ) );
563 *next++ = 0;
564 tmp = next;
567 /* Handle the case where no zero-compression is needed, but every word
568 * was filled in the address. */
569 if ( ( i == 7 ) && ( *tmp != ':' ) ) {
570 inp->s6_addr16[i++] = htons( strtoul ( tmp, 0, 16 ) );
572 else
574 /* Handle zero-compression now (go backwards). */
575 i = 7;
576 if ( i && ( *tmp == ':' ) ) {
577 next = strrchr ( next, ':' );
580 tmp = next + 1;
581 *next-- = 0;
583 /* Convert to integer. */
584 inp->s6_addr16[i--] = htons( strtoul ( tmp, 0, 16 ) );
585 } while ( ( next = strrchr ( next, ':' ) ) );
589 return 1;
592 static const char * ipv6_ntoa ( const void *net_addr ) {
593 return inet6_ntoa ( * ( ( struct in6_addr * ) net_addr ) );
596 static int ipv6_check ( struct net_device *netdev, const void *net_addr ) {
597 const struct in6_addr *address = net_addr;
598 struct ipv6_miniroute *miniroute;
600 list_for_each_entry ( miniroute, &miniroutes, list ) {
601 if ( ( miniroute->netdev == netdev ) &&
602 ( ! memcmp ( &miniroute->address, address, 16 ) ) ) {
603 /* Found matching address */
604 return 0;
607 return -ENOENT;
610 /** IPv6 protocol */
611 struct net_protocol ipv6_protocol __net_protocol = {
612 .name = "IPV6",
613 .net_proto = htons ( ETH_P_IPV6 ),
614 .net_addr_len = sizeof ( struct in6_addr ),
615 .rx = ipv6_rx,
616 .ntoa = ipv6_ntoa,
619 /** IPv6 TCPIP net protocol */
620 struct tcpip_net_protocol ipv6_tcpip_protocol __tcpip_net_protocol = {
621 .name = "IPv6",
622 .sa_family = AF_INET6,
623 .tx = ipv6_tx,
626 /** IPv6 ICMPv6 protocol, for NDP */
627 struct icmp6_net_protocol ipv6_icmp6_protocol __icmp6_net_protocol = {
628 .net_protocol = &ipv6_protocol,
629 .check = ipv6_check,