[ipv6] Enable ICMPv6 protocols to check addresses against network devices
[gpxe.git] / src / net / ipv6.c
index 8d884db..c0ae68b 100644 (file)
@@ -49,7 +49,7 @@ static LIST_HEAD ( miniroutes );
  * Add IPv6 minirouting table entry
  *
  * @v netdev           Network device
- * @v prefix           Destination prefix
+ * @v prefix           Destination prefix (in bits)
  * @v address          Address of the interface
  * @v gateway          Gateway address (or ::0 for no gateway)
  * @ret miniroute      Routing table entry, or NULL
@@ -60,8 +60,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 * 8);
-       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 ) {
@@ -90,8 +90,8 @@ add_ipv6_miniroute ( struct net_device *netdev, struct in6_addr prefix,
  */
 static void del_ipv6_miniroute ( struct ipv6_miniroute *miniroute ) {
        
-       DBG("ipv6 del: %s/%d\n", inet6_ntoa(miniroute->address),
-                                miniroute->prefix_len * 8);
+       DBG ( "ipv6 del: %s/%d\n", inet6_ntoa(miniroute->address),
+                                  miniroute->prefix_len );
        
        netdev_put ( miniroute->netdev );
        list_del ( &miniroute->list );
@@ -112,7 +112,7 @@ int add_ipv6_address ( struct net_device *netdev, struct in6_addr prefix,
        struct ipv6_miniroute *miniroute;
 
        /* Clear any existing address for this net device */
-       del_ipv6_address ( netdev );
+       /* del_ipv6_address ( netdev ); */
 
        /* Add new miniroute */
        miniroute = add_ipv6_miniroute ( netdev, prefix, prefix_len, address,
@@ -194,9 +194,12 @@ static int ipv6_tx ( struct io_buffer *iobuf,
        struct sockaddr_in6 *dest = ( struct sockaddr_in6* ) st_dest;
        struct in6_addr next_hop;
        struct ipv6_miniroute *miniroute;
-       uint8_t ll_dest_buf[MAX_LL_ADDR_LEN];
+       uint8_t ll_dest_buf[MAX_LL_ADDR_LEN], ip1, ip2;
        const uint8_t *ll_dest = ll_dest_buf;
-       int rc;
+       int rc, multicast, linklocal, bits, offset;
+       
+       /* Check for multicast transmission. */
+       multicast = dest->sin6_addr.in6_u.u6_addr8[0] == 0xFF;
 
        /* Construct the IPv6 packet */
        struct ip6_header *ip6hdr = iob_push ( iobuf, sizeof ( *ip6hdr ) );
@@ -205,19 +208,73 @@ static int ipv6_tx ( struct io_buffer *iobuf,
        ip6hdr->payload_len = htons ( iob_len ( iobuf ) - sizeof ( *ip6hdr ) );
        ip6hdr->nxt_hdr = tcpip->tcpip_proto;
        ip6hdr->hop_limit = IP6_HOP_LIMIT; // 255
-
-       /* Determine the next hop address and interface
-        *
-        * TODO: Implement the routing table.
-        */
+       
+       /* Determine the next hop address and interface. */
        next_hop = dest->sin6_addr;
        list_for_each_entry ( miniroute, &miniroutes, list ) {
-               if ( ( memcmp ( &ip6hdr->dest, &miniroute->prefix,
-                                       miniroute->prefix_len ) == 0 ) ||
-                    ( IP6_EQUAL ( miniroute->gateway, ip6_none ) ) ) {
+               /* Link-local route? */
+               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 ( 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;
+                               }
+                       }
+               }
+               
+               /* 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;
+               }
+               
+               /* 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;
@@ -229,16 +286,19 @@ static int ipv6_tx ( struct io_buffer *iobuf,
                rc = -ENETUNREACH;
                goto err;
        }
+       
+       /* Add the next hop to the packet. */
+       ip6hdr->dest = next_hop;
 
        /* Complete the transport layer checksum */
        if ( trans_csum )
                *trans_csum = ipv6_tx_csum ( iobuf, *trans_csum );
 
        /* Print IPv6 header */
-       ipv6_dump ( ip6hdr );
+       /* ipv6_dump ( ip6hdr ); */
 
        /* Resolve link layer address */
-       if ( next_hop.in6_u.u6_addr8[0] == 0xff ) {
+       if ( next_hop.in6_u.u6_addr8[0] == 0xFF ) {
                ll_dest_buf[0] = 0x33;
                ll_dest_buf[1] = 0x33;
                ll_dest_buf[2] = next_hop.in6_u.u6_addr8[12];
@@ -269,13 +329,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:
@@ -286,7 +347,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;
@@ -322,7 +383,7 @@ static int ipv6_rx ( struct io_buffer *iobuf,
        }
 
        /* Print IP6 header for debugging */
-       ipv6_dump ( ip6hdr );
+       /* ipv6_dump ( ip6hdr ); */
 
        /* Check header version */
        if ( ( ntohl( ip6hdr->ver_traffic_class_flow_label ) & 0xf0000000 ) != 0x60000000 ) {
@@ -358,7 +419,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" );
@@ -378,6 +439,13 @@ char * inet6_ntoa ( struct in6_addr in6 ) {
        static char buf[40];
        uint16_t *bytes = ( uint16_t* ) &in6;
        size_t i = 0, longest = 0, tmp = 0, long_idx = ~0;
+       
+       /* ::0 */
+       if ( IP6_EQUAL ( in6, ip6_none ) ) {
+               tmp = sprintf ( buf, "::0" );
+               buf[tmp] = 0;
+               return buf;
+       }
 
        /* Determine the longest string of zeroes for zero-compression. */
        for ( ; i < 8; i++ ) {
@@ -424,10 +492,68 @@ char * inet6_ntoa ( struct in6_addr in6 ) {
        return buf;
 }
 
+/**
+ * Convert a string to an IPv6 address.
+ *
+ * @v in6   String to convert to an address.
+ */
+int inet6_aton ( const char *cp, struct in6_addr *inp ) {
+       char convbuf[40];
+       char *tmp = convbuf, *next = convbuf;
+       size_t i = 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. */
+       while ( ( next = strchr ( next, ':' ) ) ) {
+               /* Cater for zero-compression. */
+               if ( *tmp == ':' )
+                       break;
+               
+               /* Convert to integer. */
+               inp->s6_addr16[i++] = htons( strtoul ( tmp, 0, 16 ) );
+               
+               *next++ = 0;
+               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, ':' ) ) );
+       }
+       
+       return 1;
+}
+
 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",
@@ -443,3 +569,10 @@ 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,
+};
+