[ipv6] Fix multicast routing in some conditions
[people/meteger/gpxe.git] / src / net / ipv6.c
index 5c6b942..d928934 100644 (file)
 #include <gpxe/iobuf.h>
 #include <gpxe/netdevice.h>
 #include <gpxe/if_ether.h>
+#include <gpxe/dhcp6.h>
 
 struct net_protocol ipv6_protocol;
 
+#define is_linklocal( a ) ( ( (a).in6_u.u6_addr16[0] & htons ( 0xFE80 ) ) == htons ( 0xFE80 ) )
+
 char * inet6_ntoa ( struct in6_addr in6 );
 
 /* Unspecified IP6 address */
@@ -45,6 +48,66 @@ struct ipv6_miniroute {
 /** List of IPv6 miniroutes */
 static LIST_HEAD ( miniroutes );
 
+/**
+ * Generate an EUI-64 from a given link-local address.
+ *
+ * @v out              pointer to buffer to receive the EUI-64
+ * @v ll               pointer to buffer containing the link-local address
+ */
+void ipv6_generate_eui64 ( uint8_t *out, uint8_t *ll ) {
+       assert ( out != 0 );
+       assert ( ll != 0 );
+       
+       /* Create an EUI-64 identifier. */
+       memcpy( out, ll, 3 );
+       memcpy( out + 5, ll + 3, 3 );
+       out[3] = 0xFF;
+       out[4] = 0xFE;
+       
+       /* Designate that this is in fact an EUI-64. */
+       out[0] |= 0x2;
+}
+
+/**
+ * Verifies that a prefix matches another one.
+ *
+ * @v p1               first prefix
+ * @v p2               second prefix
+ * @v len              prefix length in bits to compare
+ * @ret int            0 if a match, nonzero otherwise
+ */
+int ipv6_match_prefix ( struct in6_addr *p1, struct in6_addr *p2, size_t len ) {
+       uint8_t ip1, ip2;
+       size_t offset, bits;
+       int rc = 0;
+
+       /* Check for a prefix match on the route. */
+       if ( ! memcmp ( p1, p2, len / 8 ) ) {
+               rc = 0;
+
+               /* Handle extra bits in the prefix. */
+               if ( ( len % 8 ) ||
+                    ( len < 8 ) ) {
+                       DBG ( "ipv6: prefix is not aligned to a byte.\n" );
+
+                       /* Compare the remaining bits. */
+                       offset = len / 8;
+                       bits = len % 8;
+
+                       ip1 = p1->in6_u.u6_addr8[offset];
+                       ip2 = p2->in6_u.u6_addr8[offset];
+                       if ( ! ( ( ip1 & (0xFF >> (8 - bits)) ) &
+                            ( ip2 ) ) ) {
+                               rc = 1;
+                       }
+               }
+       } else {
+               rc = 1;
+       }
+
+       return rc;
+}
+
 /**
  * Add IPv6 minirouting table entry
  *
@@ -60,8 +123,8 @@ add_ipv6_miniroute ( struct net_device *netdev, struct in6_addr prefix,
                     struct in6_addr gateway ) {
        struct ipv6_miniroute *miniroute;
        
-       DBG("ipv6 add: %s/%d ", inet6_ntoa(address), prefix_len);
-       DBG("gw %s\n", inet6_ntoa(gateway));
+       DBG( "ipv6 add: %s/%d ", inet6_ntoa ( address ), prefix_len );
+       DBG( "gw %s\n", inet6_ntoa( gateway ) );
 
        miniroute = malloc ( sizeof ( *miniroute ) );
        if ( miniroute ) {
@@ -156,7 +219,7 @@ static uint16_t ipv6_tx_csum ( struct io_buffer *iobuf, uint16_t csum ) {
        memset ( &pshdr, 0, sizeof ( pshdr ) );
        pshdr.src = ip6hdr->src;
        pshdr.dest = ip6hdr->dest;
-       pshdr.len = htons ( iob_len ( iobuf ) - sizeof ( *ip6hdr ) );
+       pshdr.len = ip6hdr->payload_len;
        pshdr.nxt_hdr = ip6hdr->nxt_hdr;
 
        /* Update checksum value */
@@ -185,18 +248,18 @@ void ipv6_dump ( struct ip6_header *ip6hdr ) {
  *
  * This function prepends the IPv6 headers to the payload an transmits it.
  */
-static int ipv6_tx ( struct io_buffer *iobuf,
-                    struct tcpip_protocol *tcpip,
-                    struct sockaddr_tcpip *st_src __unused,
-                    struct sockaddr_tcpip *st_dest,
-                    struct net_device *netdev,
-                    uint16_t *trans_csum ) {
+int ipv6_tx ( struct io_buffer *iobuf,
+             struct tcpip_protocol *tcpip,
+             struct sockaddr_tcpip *st_src __unused,
+             struct sockaddr_tcpip *st_dest,
+             struct net_device *netdev,
+             uint16_t *trans_csum ) {
        struct sockaddr_in6 *dest = ( struct sockaddr_in6* ) st_dest;
-       struct in6_addr next_hop;
+       struct in6_addr next_hop, gateway = ip6_none;
        struct ipv6_miniroute *miniroute;
-       uint8_t ll_dest_buf[MAX_LL_ADDR_LEN], ip1, ip2;
+       uint8_t ll_dest_buf[MAX_LL_ADDR_LEN];
        const uint8_t *ll_dest = ll_dest_buf;
-       int rc, multicast, linklocal, bits, offset;
+       int rc, multicast, linklocal;
        
        /* Check for multicast transmission. */
        multicast = dest->sin6_addr.in6_u.u6_addr8[0] == 0xFF;
@@ -212,83 +275,65 @@ static int ipv6_tx ( struct io_buffer *iobuf,
        /* Determine the next hop address and interface. */
        next_hop = dest->sin6_addr;
        list_for_each_entry ( miniroute, &miniroutes, list ) {
+               /* Check for specific netdev */
+               if ( netdev && ( miniroute->netdev != netdev ) ) {
+                       continue;
+               }
+               
                /* Link-local route? */
-               linklocal = (miniroute->address.in6_u.u6_addr16[0] & htons(0xFE80)) == htons(0xFE80);
+               linklocal = is_linklocal ( miniroute->address ); // (.in6_u.u6_addr16[0] & htons(0xFE80)) == htons(0xFE80);
 
                /* Handle link-local for multicast. */
                if ( multicast )
                {
                        /* Link-local scope? */
-                       if ( next_hop.in6_u.u6_addr8[0] & 0x2 ) {
+                       if ( is_linklocal ( next_hop ) ) { // .in6_u.u6_addr8[0] & 0x2 ) {
                                if ( linklocal ) {
                                        netdev = miniroute->netdev;
                                        ip6hdr->src = miniroute->address;
-                                       
-                                       DBG ( "ipv6: link-local multicast, sending as %s\n", inet6_ntoa ( ip6hdr->src ) );
                                        break;
                                } else {
                                        /* Should be link-local address. */
                                        continue;
                                }
                        } else {
-                               DBG ( "ipv6: non-link-local multicast\n" );
-                               
-                               /* Can we route on this interface?
-                                  (assume non-link-local means routable) */
-                               if ( ! linklocal ) {
-                                       netdev = miniroute->netdev;
-                                       ip6hdr->src = miniroute->address;
-                                       break;
-                               }
+                               /* Assume we can TX on this interface, even if
+                                * it is link-local. For multicast this should
+                                * not be too much of a problem. */
+                               netdev = miniroute->netdev;
+                               ip6hdr->src = miniroute->address;
+                               break;
                        }
                }
                
                /* Check for a prefix match on the route. */
-               if ( ! memcmp ( &next_hop, &miniroute->prefix, miniroute->prefix_len / 8 ) ) {
-                       rc = 0;
-                       
-                       /* Handle extra bits in the prefix. */
-                       if ( ( miniroute->prefix_len % 2 ) ||
-                            ( miniroute->prefix_len < 8 ) ) {
-                               DBG ( "ipv6: prefix is not aligned to a byte.\n" );
-                       
-                               /* Compare the remaining bits. */
-                               offset = miniroute->prefix_len / 8;
-                               bits = miniroute->prefix_len % 8;
-                               
-                               ip1 = next_hop.in6_u.u6_addr8[offset];
-                               ip2 = miniroute->prefix.in6_u.u6_addr8[offset];
-                               if ( ! ( ( ip1 & (0xFF >> (8 - bits)) ) &
-                                    ( ip2 ) ) ) {
-                                       rc = 1;
-                               }
-                       }
-               } else {
-                       rc = 1;
-               }
+               rc = ipv6_match_prefix ( &next_hop, &miniroute->prefix, miniroute->prefix_len );
                
                /* Matched? */
                if( rc == 0 ) {
-                       DBG ( "ipv6: route found for %s.\n", inet6_ntoa ( next_hop ) );
-                       
                        netdev = miniroute->netdev;
                        ip6hdr->src = miniroute->address;
-                       if ( ! ( IS_UNSPECIFIED ( miniroute->gateway ) ) ) {
-                               DBG ( "    (via %s)\n", inet6_ntoa ( miniroute->gateway ) );
-                               next_hop = miniroute->gateway;
-                       }
                        break;
                }
+               
+               if ( ( ! ( IP6_EQUAL ( miniroute->gateway, ip6_none ) ) ) &&
+                    ( IP6_EQUAL ( gateway, ip6_none ) ) ) {
+                       netdev = miniroute->netdev;
+                       ip6hdr->src = miniroute->address;
+                       gateway = miniroute->gateway;
+               }
        }
        /* No network interface identified */
-       if ( !netdev ) {
-               DBG ( "No route to host %s\n", inet6_ntoa ( ip6hdr->dest ) );
+       if ( ( ! netdev ) ) {
+               DBG ( "No route to host %s\n", inet6_ntoa ( dest->sin6_addr ) );
                rc = -ENETUNREACH;
                goto err;
+       } else if ( ! IP6_EQUAL ( gateway, ip6_none ) ) {
+               next_hop = gateway;
        }
        
        /* Add the next hop to the packet. */
-       ip6hdr->dest = next_hop;
+       ip6hdr->dest = dest->sin6_addr;
 
        /* Complete the transport layer checksum */
        if ( trans_csum )
@@ -329,13 +374,14 @@ static int ipv6_tx ( struct io_buffer *iobuf,
  * @v nxt_hdr  Next header number
  * @v src      Source socket address
  * @v dest     Destination socket address
+ * @v netdev   Net device the packet arrived on
  * @v phcsm Partial checksum over the IPv6 psuedo-header.
  *
  * Refer http://www.iana.org/assignments/ipv6-parameters for the numbers
  */
 static int ipv6_process_nxt_hdr ( struct io_buffer *iobuf, uint8_t nxt_hdr,
                struct sockaddr_tcpip *src, struct sockaddr_tcpip *dest,
-               uint16_t phcsm ) {
+               struct net_device *netdev, uint16_t phcsm ) {
        switch ( nxt_hdr ) {
        case IP6_HOPBYHOP:
        case IP6_ROUTING:
@@ -346,7 +392,7 @@ static int ipv6_process_nxt_hdr ( struct io_buffer *iobuf, uint8_t nxt_hdr,
                DBG ( "Function not implemented for header %d\n", nxt_hdr );
                return -ENOSYS;
        case IP6_ICMP6:
-               break;
+               return icmp6_rx ( iobuf, src, dest, netdev, phcsm );
        case IP6_NO_HEADER:
                DBG ( "No next header\n" );
                return 0;
@@ -367,7 +413,6 @@ static int ipv6_process_nxt_hdr ( struct io_buffer *iobuf, uint8_t nxt_hdr,
 static int ipv6_rx ( struct io_buffer *iobuf,
                     __unused struct net_device *netdev,
                     __unused const void *ll_source ) {
-
        struct ip6_header *ip6hdr = iobuf->data;
        union {
                struct sockaddr_in6 sin6;
@@ -418,7 +463,7 @@ static int ipv6_rx ( struct io_buffer *iobuf,
 
        /* Send it to the transport layer */
        return ipv6_process_nxt_hdr ( iobuf, ip6hdr->nxt_hdr, &src.st, &dest.st,
-                                     phcsm );
+                                     netdev, phcsm );
 
   drop:
        DBG ( "IP6 packet dropped\n" );
@@ -500,12 +545,30 @@ int inet6_aton ( const char *cp, struct in6_addr *inp ) {
        char convbuf[40];
        char *tmp = convbuf, *next = convbuf;
        size_t i = 0;
+       int ok;
+       char c;
+       
+       /* Verify a valid address. */
+       while ( ( c = cp[i++] ) ) {
+               ok = c == ':';
+               ok = ok || ( ( c >= '0' ) && ( c <= '9' ) );
+               ok = ok || ( ( c >= 'a' ) && ( c <= 'f' ) );
+               ok = ok || ( ( c >= 'A' ) && ( c <= 'F' ) );
+               
+               if ( ! ok ) {
+                       return 0;
+               }
+       }
+       if ( ! i ) {
+               return 0;
+       }
        
        strcpy ( convbuf, cp );
        
        DBG ( "ipv6 converting %s to an in6_addr\n", cp );
        
        /* Handle the first part of the address (or all of it if no zero-compression. */
+       i = 0;
        while ( ( next = strchr ( next, ':' ) ) ) {
                /* Cater for zero-compression. */
                if ( *tmp == ':' )
@@ -518,18 +581,26 @@ int inet6_aton ( const char *cp, struct in6_addr *inp ) {
                tmp = next;
        }
        
-       /* Handle zero-compression now (go backwards). */
-       i = 7;
-       if ( *tmp == ':' ) {
-               next = strrchr ( next, ':' );
-               do
-               {
-                       tmp = next + 1;
-                       *next-- = 0;
-               
-                       /* Convert to integer. */
-                       inp->s6_addr16[i--] = htons( strtoul ( tmp, 0, 16 ) );
-               } while ( ( next = strrchr ( next, ':' ) ) );
+       /* Handle the case where no zero-compression is needed, but every word
+        * was filled in the address. */
+       if ( ( i == 7 ) && ( *tmp != ':' ) ) {
+               inp->s6_addr16[i++] = htons( strtoul ( tmp, 0, 16 ) );
+       }
+       else
+       {
+               /* Handle zero-compression now (go backwards). */
+               i = 7;
+               if ( i && ( *tmp == ':' ) ) {
+                       next = strrchr ( next, ':' );
+                       do
+                       {
+                               tmp = next + 1;
+                               *next-- = 0;
+       
+                               /* Convert to integer. */
+                               inp->s6_addr16[i--] = htons( strtoul ( tmp, 0, 16 ) );
+                       } while ( ( next = strrchr ( next, ':' ) ) );
+               }
        }
        
        return 1;
@@ -539,6 +610,20 @@ static const char * ipv6_ntoa ( const void *net_addr ) {
        return inet6_ntoa ( * ( ( struct in6_addr * ) net_addr ) );
 }
 
+static int ipv6_check ( struct net_device *netdev, const void *net_addr ) {
+       const struct in6_addr *address = net_addr;
+       struct ipv6_miniroute *miniroute;
+
+       list_for_each_entry ( miniroute, &miniroutes, list ) {
+               if ( ( miniroute->netdev == netdev ) &&
+                    ( ! memcmp ( &miniroute->address, address, 16 ) ) ) {
+                       /* Found matching address */
+                       return 0;
+               }
+       }
+       return -ENOENT;
+}
+
 /** IPv6 protocol */
 struct net_protocol ipv6_protocol __net_protocol = {
        .name = "IPV6",
@@ -554,3 +639,101 @@ struct tcpip_net_protocol ipv6_tcpip_protocol __tcpip_net_protocol = {
        .sa_family = AF_INET6,
        .tx = ipv6_tx,
 };
+
+/** IPv6 ICMPv6 protocol, for NDP */
+struct icmp6_net_protocol ipv6_icmp6_protocol __icmp6_net_protocol = {
+       .net_protocol = &ipv6_protocol,
+       .check = ipv6_check,
+};
+
+
+
+/******************************************************************************
+ *
+ * Settings
+ *
+ ******************************************************************************
+ */
+
+/** IPv6 address setting */
+struct setting ip6_setting __setting = {
+       .name = "ip6",
+       .description = "IPv6 address",
+       .tag = DHCP6_OPT_IAADDR,
+       .type = &setting_type_ipv6,
+};
+
+/** IPv6 prefix setting */
+struct setting prefix_setting __setting = {
+       .name = "prefix",
+       .description = "IPv6 address prefix length",
+       .tag = 0,
+       .type = &setting_type_int32,
+};
+
+/** Default IPv6 gateway setting */
+struct setting gateway6_setting __setting = {
+       .name = "gateway6",
+       .description = "IPv6 Default gateway",
+       .tag = 0,
+       .type = &setting_type_ipv4,
+};
+
+/**
+ * Create IPv6 routes based on configured settings.
+ *
+ * @ret rc             Return status code
+ */
+static int ipv6_create_routes ( void ) {
+       struct ipv6_miniroute *miniroute;
+       struct ipv6_miniroute *tmp;
+       struct net_device *netdev;
+       struct settings *settings;
+       struct in6_addr address;
+       struct in6_addr gateway;
+       long prefix = 0;
+       int rc = 0;
+       
+       /* Create a route for each configured network device */
+       for_each_netdev ( netdev ) {
+               settings = netdev_settings ( netdev );
+       
+               /* Read the settings first. We may need to clear routes. */
+               fetch_ipv6_setting ( settings, &ip6_setting, &address );
+               fetch_ipv6_setting ( settings, &gateway6_setting, &gateway );
+               fetch_int_setting ( settings, &prefix_setting, &prefix );
+       
+               /* Sanity check! */
+               if ( ( prefix <= 0 ) || ( prefix > 128 ) ) {
+                       DBG ( "ipv6: attempt to apply settings without a valid prefix, ignoring\n" );
+                       continue; /* Simply ignore this setting. */
+               }
+       
+               /* Remove any existing routes for this address. */
+               list_for_each_entry_safe ( miniroute, tmp, &miniroutes, list ) {
+                       if ( ! ipv6_match_prefix ( &address,
+                                                &miniroute->prefix,
+                                                prefix ) ) {
+                               DBG ( "ipv6: existing route for a configured setting, deleting\n" );
+                               del_ipv6_miniroute ( miniroute );
+                       }
+               }
+               
+               /* Configure route */
+               rc = add_ipv6_address ( netdev, address, prefix,
+                                       address, gateway );
+               if ( ! rc )
+                       return rc;
+       }
+       
+       return 0;
+}
+
+/** IPv6 settings applicator */
+struct settings_applicator ipv6_settings_applicator __settings_applicator = {
+       .apply = ipv6_create_routes,
+};
+
+/* Drag in ICMP6 and DHCP6 */
+REQUIRE_OBJECT ( icmpv6 );
+REQUIRE_OBJECT ( dhcp6 );