[ipv6] Implement EUI-64 generation as a function
[people/meteger/gpxe.git] / src / net / ipv6.c
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>
17
18 struct net_protocol ipv6_protocol;
19
20 char * inet6_ntoa ( struct in6_addr in6 );
21
22 /* Unspecified IP6 address */
23 static struct in6_addr ip6_none = {
24         .in6_u.u6_addr32 = { 0,0,0,0 }
25 };
26
27 /** An IPv6 routing table entry */
28 struct ipv6_miniroute {
29         /* List of miniroutes */
30         struct list_head list;
31
32         /* Network device */
33         struct net_device *netdev;
34
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;
43 };
44
45 /** List of IPv6 miniroutes */
46 static LIST_HEAD ( miniroutes );
47
48 /**
49  * Generate an EUI-64 from a given link-local address.
50  *
51  * @v out               pointer to buffer to receive the EUI-64
52  * @v ll                pointer to buffer containing the link-local address
53  */
54 void ipv6_generate_eui64 ( uint8_t *out, uint8_t *ll ) {
55         assert ( out != 0 );
56         assert ( ll != 0 );
57         
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;
63         
64         /* Designate that this is in fact an EUI-64. */
65         out[0] |= 0x2;
66 }
67
68 /**
69  * Add IPv6 minirouting table entry
70  *
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
76  */
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;
82         
83         DBG( "ipv6 add: %s/%d ", inet6_ntoa ( address ), prefix_len );
84         DBG( "gw %s\n", inet6_ntoa( gateway ) );
85
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;
94
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 );
100                 }
101         }
102
103         return miniroute;
104 }
105
106 /**
107  * Delete IPv6 minirouting table entry
108  *
109  * @v miniroute         Routing table entry
110  */
111 static void del_ipv6_miniroute ( struct ipv6_miniroute *miniroute ) {
112         
113         DBG ( "ipv6 del: %s/%d\n", inet6_ntoa(miniroute->address),
114                                    miniroute->prefix_len );
115         
116         netdev_put ( miniroute->netdev );
117         list_del ( &miniroute->list );
118         free ( miniroute );
119 }
120
121 /**
122  * Add IPv6 interface
123  *
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)
128  */
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;
133
134         /* Clear any existing address for this net device */
135         /* del_ipv6_address ( netdev ); */
136
137         /* Add new miniroute */
138         miniroute = add_ipv6_miniroute ( netdev, prefix, prefix_len, address,
139                                          gateway );
140         if ( ! miniroute )
141                 return -ENOMEM;
142
143         return 0;
144 }
145
146 /**
147  * Remove IPv6 interface
148  *
149  * @v netdev    Network device
150  */
151 void del_ipv6_address ( struct net_device *netdev ) {
152         struct ipv6_miniroute *miniroute;
153
154         list_for_each_entry ( miniroute, &miniroutes, list ) {
155                 if ( miniroute->netdev == netdev ) {
156                         del_ipv6_miniroute ( miniroute );
157                         break;
158                 }
159         }
160 }
161
162 /**
163  * Calculate TCPIP checksum
164  *
165  * @v iobuf     I/O buffer
166  * @v tcpip     TCP/IP protocol
167  *
168  * This function constructs the pseudo header and completes the checksum in the
169  * upper layer header.
170  */
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;
174
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;
181
182         /* Update checksum value */
183         return tcpip_continue_chksum ( csum, &pshdr, sizeof ( pshdr ) );
184 }
185
186 /**
187  * Dump IP6 header for debugging
188  *
189  * ip6hdr       IPv6 header
190  */
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 ) );
197 }
198
199 /**
200  * Transmit IP6 packet
201  *
202  * iobuf                I/O buffer
203  * tcpip        TCP/IP protocol
204  * st_dest      Destination socket address
205  *
206  * This function prepends the IPv6 headers to the payload an transmits it.
207  */
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;
220         
221         /* Check for multicast transmission. */
222         multicast = dest->sin6_addr.in6_u.u6_addr8[0] == 0xFF;
223
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
231         
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;
238                 }
239                 
240                 /* Link-local route? */
241                 linklocal = (miniroute->address.in6_u.u6_addr16[0] & htons(0xFE80)) == htons(0xFE80);
242
243                 /* Handle link-local for multicast. */
244                 if ( multicast )
245                 {
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;
255                                 }
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;
263                                 }
264                         }
265                 }
266                 
267                 /* Check for a prefix match on the route. */
268                 if ( ! memcmp ( &next_hop, &miniroute->prefix, miniroute->prefix_len / 8 ) ) {
269                         rc = 0;
270                         
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" );
275                         
276                                 /* Compare the remaining bits. */
277                                 offset = miniroute->prefix_len / 8;
278                                 bits = miniroute->prefix_len % 8;
279                                 
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;
285                                 }
286                         }
287                 } else {
288                         rc = 1;
289                 }
290                 
291                 /* Matched? */
292                 if( rc == 0 ) {
293                         netdev = miniroute->netdev;
294                         ip6hdr->src = miniroute->address;
295                         break;
296                 }
297                 
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;
303                 }
304         }
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;
312         }
313         
314         /* Add the next hop to the packet. */
315         ip6hdr->dest = dest->sin6_addr;
316
317         /* Complete the transport layer checksum */
318         if ( trans_csum )
319                 *trans_csum = ipv6_tx_csum ( iobuf, *trans_csum );
320
321         /* Print IPv6 header */
322         /* ipv6_dump ( ip6hdr ); */
323
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;
338                 }
339         }
340
341         /* Transmit packet */
342         return net_tx ( iobuf, netdev, &ipv6_protocol, ll_dest );
343
344   err:
345         free_iob ( iobuf );
346         return rc;
347 }
348
349 /**
350  * Process next IP6 header
351  *
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.
358  *
359  * Refer http://www.iana.org/assignments/ipv6-parameters for the numbers
360  */
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;
378         }
379         /* Next header is not a IPv6 extension header */
380         return tcpip_rx ( iobuf, nxt_hdr, src, dest, phcsm );
381 }
382
383 /**
384  * Process incoming IP6 packets
385  *
386  * @v iobuf             I/O buffer
387  * @v netdev            Network device
388  * @v ll_source         Link-layer source address
389  *
390  * This function processes a IPv6 packet
391  */
392 static int ipv6_rx ( struct io_buffer *iobuf,
393                      __unused struct net_device *netdev,
394                      __unused const void *ll_source ) {
395
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;
402
403         /* Sanity check */
404         if ( iob_len ( iobuf ) < sizeof ( *ip6hdr ) ) {
405                 DBG ( "Packet too short (%zd bytes)\n", iob_len ( iobuf ) );
406                 goto drop;
407         }
408
409         /* Print IP6 header for debugging */
410         /* ipv6_dump ( ip6hdr ); */
411
412         /* Check header version */
413         if ( ( ntohl( ip6hdr->ver_traffic_class_flow_label ) & 0xf0000000 ) != 0x60000000 ) {
414                 DBG ( "Invalid protocol version\n" );
415                 goto drop;
416         }
417
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;
423         }
424
425         /* Ignore the traffic class and flow control values */
426
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;
434
435         /* Calculate the psuedo-header checksum before the IP6 header is
436          * stripped away. */
437         phcsm = ipv6_tx_csum ( iobuf, 0 );
438
439         /* Strip header */
440         iob_unput ( iobuf, iob_len ( iobuf ) - ntohs ( ip6hdr->payload_len ) -
441                                                         sizeof ( *ip6hdr ) );
442         iob_pull ( iobuf, sizeof ( *ip6hdr ) );
443
444         /* Send it to the transport layer */
445         return ipv6_process_nxt_hdr ( iobuf, ip6hdr->nxt_hdr, &src.st, &dest.st,
446                                       netdev, phcsm );
447
448   drop:
449         DBG ( "IP6 packet dropped\n" );
450         free_iob ( iobuf );
451         return -1;
452 }
453
454 /**
455  * Convert an IPv6 address to a string.
456  *
457  * @v in6   Address to convert to string.
458  *
459  * Converts an IPv6 address to a string, and applies zero-compression as needed
460  * to condense the address for easier reading/typing.
461  */
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;
466         
467         /* ::0 */
468         if ( IP6_EQUAL ( in6, ip6_none ) ) {
469                 tmp = sprintf ( buf, "::0" );
470                 buf[tmp] = 0;
471                 return buf;
472         }
473
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;
481                         
482                         tmp = 0;
483                 }
484         }
485         
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;
491         }
492
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, ":" );
500
501                         /* Handle end-of-string. */
502                         if(i > 7)
503                                 break;
504                 }
505
506                 /* Insert this component of the address. */
507                 tmp += sprintf(buf + tmp, "%x", ntohs(bytes[i]));
508
509                 /* Add the next colon, if needed. */
510                 if ( i < 7 )
511                         tmp += sprintf( buf + tmp, ":" );
512         }
513
514         buf[tmp] = 0;
515
516         return buf;
517 }
518
519 /**
520  * Convert a string to an IPv6 address.
521  *
522  * @v in6   String to convert to an address.
523  */
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;
530         
531         /* Verify a valid address. */
532         if ( ! len ) {
533                 return 0;
534         }
535         
536         for ( ; i < len; i++ ) {
537                 c = cp[i];
538                 
539                 ok = c == ':';
540                 ok = ok || ( ( c >= '0' ) && ( c <= '9' ) );
541                 ok = ok || ( ( c >= 'a' ) && ( c <= 'f' ) );
542                 ok = ok || ( ( c >= 'A' ) && ( c <= 'F' ) );
543                 
544                 if ( ! ok ) {
545                         return 0;
546                 }
547         }
548         
549         strcpy ( convbuf, cp );
550         
551         DBG ( "ipv6 converting %s to an in6_addr\n", cp );
552         
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;
559                 
560                 /* Convert to integer. */
561                 inp->s6_addr16[i++] = htons( strtoul ( tmp, 0, 16 ) );
562                 
563                 *next++ = 0;
564                 tmp = next;
565         }
566         
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 ) );
571         }
572         else
573         {
574                 /* Handle zero-compression now (go backwards). */
575                 i = 7;
576                 if ( i && ( *tmp == ':' ) ) {
577                         next = strrchr ( next, ':' );
578                         do
579                         {
580                                 tmp = next + 1;
581                                 *next-- = 0;
582         
583                                 /* Convert to integer. */
584                                 inp->s6_addr16[i--] = htons( strtoul ( tmp, 0, 16 ) );
585                         } while ( ( next = strrchr ( next, ':' ) ) );
586                 }
587         }
588         
589         return 1;
590 }
591
592 static const char * ipv6_ntoa ( const void *net_addr ) {
593         return inet6_ntoa ( * ( ( struct in6_addr * ) net_addr ) );
594 }
595
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;
599
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;
605                 }
606         }
607         return -ENOENT;
608 }
609
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,
617 };
618
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,
624 };
625
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,
630 };
631