[ipv6] Implement inet6_aton, numeric IPv6 address resolution
[gpxe.git] / src / net / ipv6.c
index 458a937..abf5353 100644 (file)
 #include <gpxe/icmp6.h>
 #include <gpxe/tcpip.h>
 #include <gpxe/socket.h>
-#include <gpxe/pkbuff.h>
+#include <gpxe/iobuf.h>
 #include <gpxe/netdevice.h>
 #include <gpxe/if_ether.h>
 
 struct net_protocol ipv6_protocol;
 
+char * inet6_ntoa ( struct in6_addr in6 );
+
 /* Unspecified IP6 address */
 static struct in6_addr ip6_none = {
-        .in6_u.u6_addr32[0] = 0,
-        .in6_u.u6_addr32[1] = 0,
-        .in6_u.u6_addr32[2] = 0,
-        .in6_u.u6_addr32[3] = 0,
+       .in6_u.u6_addr32 = { 0,0,0,0 }
 };
 
 /** An IPv6 routing table entry */
@@ -32,8 +31,6 @@ struct ipv6_miniroute {
 
        /* Network device */
        struct net_device *netdev;
-       /** Reference to network device */
-       struct reference netdev_ref;
 
        /* Destination prefix */
        struct in6_addr prefix;
@@ -48,43 +45,39 @@ struct ipv6_miniroute {
 /** List of IPv6 miniroutes */
 static LIST_HEAD ( miniroutes );
 
-static void ipv6_forget_netdev ( struct reference *ref );
-
 /**
  * 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
  */
-static struct ipv6_miniroute * add_ipv6_miniroute ( struct net_device *netdev,
-                                                   struct in6_addr prefix,
-                                                   int prefix_len,
-                                                   struct in6_addr address,
-                                                   struct in6_addr gateway ) {
+static struct ipv6_miniroute * __malloc
+add_ipv6_miniroute ( struct net_device *netdev, struct in6_addr prefix,
+                    int prefix_len, struct in6_addr address,
+                    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));
+
        miniroute = malloc ( sizeof ( *miniroute ) );
        if ( miniroute ) {
                /* Record routing information */
-               miniroute->netdev = netdev;
+               miniroute->netdev = netdev_get ( netdev );
                miniroute->prefix = prefix;
                miniroute->prefix_len = prefix_len;
                miniroute->address = address;
                miniroute->gateway = gateway;
-               
+
                /* Add miniroute to list of miniroutes */
                if ( !IP6_EQUAL ( gateway, ip6_none ) ) {
                        list_add_tail ( &miniroute->list, &miniroutes );
                } else {
                        list_add ( &miniroute->list, &miniroutes );
                }
-
-               /* Record reference to net_device */
-               miniroute->netdev_ref.forget = ipv6_forget_netdev;
-               ref_add ( &miniroute->netdev_ref, &netdev->references );
        }
 
        return miniroute;
@@ -96,23 +89,15 @@ static struct ipv6_miniroute * add_ipv6_miniroute ( struct net_device *netdev,
  * @v miniroute                Routing table entry
  */
 static void del_ipv6_miniroute ( struct ipv6_miniroute *miniroute ) {
-       ref_del ( &miniroute->netdev_ref );
+       
+       DBG("ipv6 del: %s/%d\n", inet6_ntoa(miniroute->address),
+                                miniroute->prefix_len);
+       
+       netdev_put ( miniroute->netdev );
        list_del ( &miniroute->list );
        free ( miniroute );
 }
 
-/**
- * Forget reference to net_device
- *
- * @v ref              Persistent reference
- */
-static void ipv6_forget_netdev ( struct reference *ref ) {
-       struct ipv6_miniroute *miniroute
-               = container_of ( ref, struct ipv6_miniroute, netdev_ref );
-
-       del_ipv6_miniroute ( miniroute );
-}
-
 /**
  * Add IPv6 interface
  *
@@ -157,21 +142,21 @@ void del_ipv6_address ( struct net_device *netdev ) {
 /**
  * Calculate TCPIP checksum
  *
- * @v pkb      Packet buffer
+ * @v iobuf    I/O buffer
  * @v tcpip    TCP/IP protocol
  *
  * This function constructs the pseudo header and completes the checksum in the
  * upper layer header.
  */
-static uint16_t ipv6_tx_csum ( struct pk_buff *pkb, uint16_t csum ) {
-       struct ip6_header *ip6hdr = pkb->data;
+static uint16_t ipv6_tx_csum ( struct io_buffer *iobuf, uint16_t csum ) {
+       struct ip6_header *ip6hdr = iobuf->data;
        struct ipv6_pseudo_header pshdr;
 
        /* Calculate pseudo header */
        memset ( &pshdr, 0, sizeof ( pshdr ) );
        pshdr.src = ip6hdr->src;
        pshdr.dest = ip6hdr->dest;
-       pshdr.len = htons ( pkb_len ( pkb ) - sizeof ( *ip6hdr ) );
+       pshdr.len = htons ( iob_len ( iobuf ) - sizeof ( *ip6hdr ) );
        pshdr.nxt_hdr = ip6hdr->nxt_hdr;
 
        /* Update checksum value */
@@ -184,22 +169,25 @@ static uint16_t ipv6_tx_csum ( struct pk_buff *pkb, uint16_t csum ) {
  * ip6hdr      IPv6 header
  */
 void ipv6_dump ( struct ip6_header *ip6hdr ) {
-       DBG ( "IP6 %p src %s dest %s nxt_hdr %d len %d\n", ip6hdr,
-             inet6_ntoa ( ip6hdr->src ), inet6_ntoa ( ip6hdr->dest ),
-             ip6hdr->nxt_hdr, ntohs ( ip6hdr->payload_len ) );
+       /* Because inet6_ntoa returns a static char[16], each call needs to be
+        * separate. */
+       DBG ( "IP6 %p src %s ", ip6hdr, inet6_ntoa( ip6hdr->src ) );
+       DBG ( "dest %s nxt_hdr %d len %d\n", inet6_ntoa ( ip6hdr->dest ),
+                 ip6hdr->nxt_hdr, ntohs ( ip6hdr->payload_len ) );
 }
 
 /**
  * Transmit IP6 packet
  *
- * pkb         Packet buffer
+ * iobuf               I/O buffer
  * tcpip       TCP/IP protocol
  * st_dest     Destination socket address
  *
  * This function prepends the IPv6 headers to the payload an transmits it.
  */
-static int ipv6_tx ( struct pk_buff *pkb,
+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 ) {
@@ -211,10 +199,10 @@ static int ipv6_tx ( struct pk_buff *pkb,
        int rc;
 
        /* Construct the IPv6 packet */
-       struct ip6_header *ip6hdr = pkb_push ( pkb, sizeof ( *ip6hdr ) );
+       struct ip6_header *ip6hdr = iob_push ( iobuf, sizeof ( *ip6hdr ) );
        memset ( ip6hdr, 0, sizeof ( *ip6hdr) );
        ip6hdr->ver_traffic_class_flow_label = htonl ( 0x60000000 );//IP6_VERSION;
-       ip6hdr->payload_len = htons ( pkb_len ( pkb ) - sizeof ( *ip6hdr ) );
+       ip6hdr->payload_len = htons ( iob_len ( iobuf ) - sizeof ( *ip6hdr ) );
        ip6hdr->nxt_hdr = tcpip->tcpip_proto;
        ip6hdr->hop_limit = IP6_HOP_LIMIT; // 255
 
@@ -224,7 +212,7 @@ static int ipv6_tx ( struct pk_buff *pkb,
         */
        next_hop = dest->sin6_addr;
        list_for_each_entry ( miniroute, &miniroutes, list ) {
-               if ( ( strncmp ( &ip6hdr->dest, &miniroute->prefix,
+               if ( ( memcmp ( &ip6hdr->dest, &miniroute->prefix,
                                        miniroute->prefix_len ) == 0 ) ||
                     ( IP6_EQUAL ( miniroute->gateway, ip6_none ) ) ) {
                        netdev = miniroute->netdev;
@@ -244,11 +232,11 @@ static int ipv6_tx ( struct pk_buff *pkb,
 
        /* Complete the transport layer checksum */
        if ( trans_csum )
-               *trans_csum = ipv6_tx_csum ( pkb, *trans_csum );
+               *trans_csum = ipv6_tx_csum ( iobuf, *trans_csum );
 
        /* Print IPv6 header */
        ipv6_dump ( ip6hdr );
-       
+
        /* Resolve link layer address */
        if ( next_hop.in6_u.u6_addr8[0] == 0xff ) {
                ll_dest_buf[0] = 0x33;
@@ -267,82 +255,83 @@ static int ipv6_tx ( struct pk_buff *pkb,
        }
 
        /* Transmit packet */
-       return net_tx ( pkb, netdev, &ipv6_protocol, ll_dest );
+       return net_tx ( iobuf, netdev, &ipv6_protocol, ll_dest );
 
   err:
-       free_pkb ( pkb );
+       free_iob ( iobuf );
        return rc;
 }
 
 /**
  * Process next IP6 header
  *
- * @v pkb      Packet buffer
+ * @v iobuf    I/O buffer
  * @v nxt_hdr  Next header number
  * @v src      Source socket address
  * @v dest     Destination socket address
+ * @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 pk_buff *pkb, uint8_t nxt_hdr,
-               struct sockaddr_tcpip *src, struct sockaddr_tcpip *dest ) {
+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 ) {
        switch ( nxt_hdr ) {
-       case IP6_HOPBYHOP: 
-       case IP6_ROUTING: 
-       case IP6_FRAGMENT: 
-       case IP6_AUTHENTICATION: 
-       case IP6_DEST_OPTS: 
-       case IP6_ESP: 
+       case IP6_HOPBYHOP:
+       case IP6_ROUTING:
+       case IP6_FRAGMENT:
+       case IP6_AUTHENTICATION:
+       case IP6_DEST_OPTS:
+       case IP6_ESP:
                DBG ( "Function not implemented for header %d\n", nxt_hdr );
                return -ENOSYS;
-       case IP6_ICMP6: 
+       case IP6_ICMP6:
                break;
-       case IP6_NO_HEADER: 
+       case IP6_NO_HEADER:
                DBG ( "No next header\n" );
                return 0;
        }
        /* Next header is not a IPv6 extension header */
-       return tcpip_rx ( pkb, nxt_hdr, src, dest, 0 /* fixme */ );
+       return tcpip_rx ( iobuf, nxt_hdr, src, dest, phcsm );
 }
 
 /**
  * Process incoming IP6 packets
  *
- * @v pkb              Packet buffer
+ * @v iobuf            I/O buffer
  * @v netdev           Network device
  * @v ll_source                Link-layer source address
  *
  * This function processes a IPv6 packet
  */
-static int ipv6_rx ( struct pk_buff *pkb,
-                    struct net_device *netdev,
-                    const void *ll_source ) {
+static int ipv6_rx ( struct io_buffer *iobuf,
+                    __unused struct net_device *netdev,
+                    __unused const void *ll_source ) {
 
-       struct ip6_header *ip6hdr = pkb->data;
+       struct ip6_header *ip6hdr = iobuf->data;
        union {
                struct sockaddr_in6 sin6;
                struct sockaddr_tcpip st;
        } src, dest;
+       uint16_t phcsm = 0;
 
        /* Sanity check */
-       if ( pkb_len ( pkb ) < sizeof ( *ip6hdr ) ) {
-               DBG ( "Packet too short (%d bytes)\n", pkb_len ( pkb ) );
+       if ( iob_len ( iobuf ) < sizeof ( *ip6hdr ) ) {
+               DBG ( "Packet too short (%zd bytes)\n", iob_len ( iobuf ) );
                goto drop;
        }
 
-       /* TODO: Verify checksum */
-
        /* Print IP6 header for debugging */
        ipv6_dump ( ip6hdr );
 
        /* Check header version */
-       if ( ip6hdr->ver_traffic_class_flow_label & 0xf0000000 != 0x60000000 ) {
+       if ( ( ntohl( ip6hdr->ver_traffic_class_flow_label ) & 0xf0000000 ) != 0x60000000 ) {
                DBG ( "Invalid protocol version\n" );
                goto drop;
        }
 
        /* Check the payload length */
-       if ( ntohs ( ip6hdr->payload_len ) > pkb_len ( pkb ) ) {
+       if ( ntohs ( ip6hdr->payload_len ) > iob_len ( iobuf ) ) {
                DBG ( "Inconsistent packet length (%d bytes)\n",
                        ip6hdr->payload_len );
                goto drop;
@@ -358,38 +347,141 @@ static int ipv6_rx ( struct pk_buff *pkb,
        dest.sin6.sin_family = AF_INET6;
        dest.sin6.sin6_addr = ip6hdr->dest;
 
+       /* Calculate the psuedo-header checksum before the IP6 header is
+        * stripped away. */
+       phcsm = ipv6_tx_csum ( iobuf, 0 );
+
        /* Strip header */
-       pkb_unput ( pkb, pkb_len ( pkb ) - ntohs ( ip6hdr->payload_len ) -
+       iob_unput ( iobuf, iob_len ( iobuf ) - ntohs ( ip6hdr->payload_len ) -
                                                        sizeof ( *ip6hdr ) );
-       pkb_pull ( pkb, sizeof ( *ip6hdr ) );
+       iob_pull ( iobuf, sizeof ( *ip6hdr ) );
 
        /* Send it to the transport layer */
-       return ipv6_process_nxt_hdr ( pkb, ip6hdr->nxt_hdr, &src.st, &dest.st );
+       return ipv6_process_nxt_hdr ( iobuf, ip6hdr->nxt_hdr, &src.st, &dest.st,
+                                     phcsm );
 
   drop:
-       DBG ( "Packet dropped\n" );
-       free_pkb ( pkb );
+       DBG ( "IP6 packet dropped\n" );
+       free_iob ( iobuf );
        return -1;
 }
 
 /**
- * Print a IP6 address as xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx
+ * Convert an IPv6 address to a string.
+ *
+ * @v in6   Address to convert to string.
+ *
+ * Converts an IPv6 address to a string, and applies zero-compression as needed
+ * to condense the address for easier reading/typing.
  */
 char * inet6_ntoa ( struct in6_addr in6 ) {
        static char buf[40];
        uint16_t *bytes = ( uint16_t* ) &in6;
-       sprintf ( buf, "%x:%x:%x:%x:%x:%x:%x:%x", bytes[0], bytes[1], bytes[2],
-                       bytes[3], bytes[4], bytes[5], bytes[6], bytes[7] );
+       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++ ) {
+               if ( !bytes[i] )
+                       tmp++;
+               else if(tmp > longest) {
+                       longest = tmp;
+                       long_idx = i - longest;
+                       
+                       tmp = 0;
+               }
+       }
+       
+       /* Check for last word being zero. This will cause long_idx to be zero,
+        * which confuses the actual buffer fill code. */
+       if(tmp && (tmp > longest)) {
+               longest = tmp;
+               long_idx = 8 - longest;
+       }
+
+       /* Inject into the buffer. */
+       tmp = 0;
+       for ( i = 0; i < 8; i++ ) {
+               /* Should we skip over a string of zeroes? */
+               if ( i == long_idx ) {
+                       i += longest;
+                       tmp += sprintf( buf + tmp, ":" );
+
+                       /* Handle end-of-string. */
+                       if(i > 7)
+                               break;
+               }
+
+               /* Insert this component of the address. */
+               tmp += sprintf(buf + tmp, "%x", ntohs(bytes[i]));
+
+               /* Add the next colon, if needed. */
+               if ( i < 7 )
+                       tmp += sprintf( buf + tmp, ":" );
+       }
+
+       buf[tmp] = 0;
+
        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 ) );
 }
 
 /** IPv6 protocol */
 struct net_protocol ipv6_protocol __net_protocol = {
-       .name = "IPv6",
+       .name = "IPV6",
        .net_proto = htons ( ETH_P_IPV6 ),
        .net_addr_len = sizeof ( struct in6_addr ),
        .rx = ipv6_rx,