IP6 support
authorNikhil Chandru Rao <nikhilcrao@users.sourceforge.net>
Sat, 19 Aug 2006 15:58:22 +0000 (15:58 +0000)
committerNikhil Chandru Rao <nikhilcrao@users.sourceforge.net>
Sat, 19 Aug 2006 15:58:22 +0000 (15:58 +0000)
src/include/gpxe/icmp6.h [new file with mode: 0644]
src/include/gpxe/in.h
src/include/gpxe/ip6.h
src/include/gpxe/ndp.h [new file with mode: 0644]
src/net/icmpv6.c [new file with mode: 0644]
src/net/ipv6.c
src/net/ndp.c [new file with mode: 0644]

diff --git a/src/include/gpxe/icmp6.h b/src/include/gpxe/icmp6.h
new file mode 100644 (file)
index 0000000..3521036
--- /dev/null
@@ -0,0 +1,58 @@
+#ifndef _GPXE_ICMP6_H
+#define _GPXE_ICMP6_H
+
+/** @file
+ *
+ * ICMP6 protocol
+ *
+ */
+
+#include <ip.h>
+#include <gpxe/ip6.h>
+#include <gpxe/ndp.h>
+
+#define ICMP6_NSOLICIT 135
+#define ICMP6_NADVERT 136
+
+extern struct tcpip_protocol icmp6_protocol;
+
+struct icmp6_header {
+       uint8_t type;
+       uint8_t code;
+       uint16_t csum;
+       /* Message body */
+};
+
+struct neighbour_solicit {
+       uint8_t type;
+       uint8_t code;
+       uint16_t csum;
+       uint32_t reserved;
+       struct in6_addr target;
+       /* "Compulsory" options */
+       uint8_t opt_type;
+       uint8_t opt_len;
+#warning hack alert
+       uint8_t opt_ll_addr[6];
+};
+
+struct neighbour_advert {
+       uint8_t type;
+       uint8_t code;
+       uint16_t csum;
+       uint8_t flags;
+       uint8_t reserved;
+       struct in6_addr target;
+       uint8_t opt_type;
+       uint8_t opt_len;
+#warning hack alert
+       uint8_t opt_ll_addr[6];
+};
+
+#define ICMP6_FLAGS_ROUTER 0x80
+#define ICMP6_FLAGS_SOLICITED 0x40
+#define ICMP6_FLAGS_OVERRIDE 0x20
+
+int icmp6_send_solicit ( struct net_device *netdev, struct in6_addr *src, struct in6_addr *dest );
+
+#endif /* _GPXE_ICMP6_H */
index eed9c09..d502c4c 100644 (file)
@@ -10,6 +10,7 @@
 #define IP_IGMP                2
 #define IP_TCP         6
 #define IP_UDP         17
+#define IP_ICMP6       58
 
 /* IP address constants */
 
@@ -36,7 +37,7 @@ struct in6_addr {
                 uint8_t u6_addr8[16];
                 uint16_t u6_addr16[8];
                 uint32_t u6_addr32[4];
-        } in16_u;
+        } in6_u;
 #define s6_addr         in6_u.u6_addr8
 #define s6_addr16       in6_u.u6_addr16
 #define s6_addr32       in6_u.u6_addr32
@@ -67,7 +68,7 @@ struct sockaddr_in6 {
         */
        sa_family_t sin_family;
        /** TCP/IP port (part of struct @c sockaddr_tcpip) */
-       uint16_t sin_port;
+       uint16_t        sin_port;
         uint32_t        sin6_flowinfo;  /* Flow number */
         struct in6_addr sin6_addr;      /* 128-bit destination address */
         uint32_t        sin6_scope_id;  /* Scope ID */
index 69cbd81..0589f8e 100644 (file)
@@ -8,18 +8,33 @@
  */
 
 #include <ip.h>
+#include <gpxe/in.h>
 
 /* IP6 constants */
 
 #define IP6_VERSION    0x6
-#define IP6_HOP_LIMIT  64
+#define IP6_HOP_LIMIT  255
 
-/* IP6 header */
+/**
+ * Packet buffer contents
+ * This is duplicated in tcp.h and here. Ideally it should go into pkbuff.h
+ */
+#define MAX_HDR_LEN    100
+#define MAX_PKB_LEN    1500
+#define MIN_PKB_LEN    MAX_HDR_LEN + 100 /* To account for padding by LL */
+
+#define IP6_EQUAL( in6_addr1, in6_addr2 ) \
+        ( strncmp ( ( char* ) &( in6_addr1 ), ( char* ) &( in6_addr2 ),\
+       sizeof ( struct in6_addr ) ) == 0 )
 
+#define IS_UNSPECIFIED( addr ) \
+       ( ( (addr).in6_u.u6_addr32[0] == 0x00000000 ) && \
+       ( (addr).in6_u.u6_addr32[1] == 0x00000000 ) && \
+       ( (addr).in6_u.u6_addr32[2] == 0x00000000 ) && \
+       ( (addr).in6_u.u6_addr32[3] == 0x00000000 ) )
+/* IP6 header */
 struct ip6_header {
-       uint32_t        vers:4,
-                       traffic_class:8,
-                       flow_label:20;
+       uint32_t        ver_traffic_class_flow_label;
        uint16_t        payload_len;
        uint8_t         nxt_hdr;
        uint8_t         hop_limit;
@@ -27,12 +42,32 @@ struct ip6_header {
        struct in6_addr dest;
 };
 
+/* IP6 pseudo header */
+struct ipv6_pseudo_header {
+       struct in6_addr src;
+       struct in6_addr dest;
+       uint8_t zero_padding;
+       uint8_t nxt_hdr;
+       uint16_t len;
+};
+
+/* Next header numbers */
+#define IP6_HOPBYHOP           0x00
+#define IP6_ROUTING            0x43
+#define IP6_FRAGMENT           0x44
+#define IP6_AUTHENTICATION     0x51
+#define IP6_DEST_OPTS          0x60
+#define IP6_ESP                        0x50
+#define IP6_ICMP6              0x58
+#define IP6_NO_HEADER          0x59
+
 struct pk_buff;
 struct net_device;
 struct net_protocol;
 
 extern struct net_protocol ipv6_protocol;
+extern struct tcpip_net_protocol ipv6_tcpip_protocol;
+extern char * inet6_ntoa ( struct in6_addr in6 );
 
-extern int ipv6_tx ( struct pk_buff *pkb, uint16_t trans_proto, struct in6_addr *dest );
 
 #endif /* _GPXE_IP6_H */
diff --git a/src/include/gpxe/ndp.h b/src/include/gpxe/ndp.h
new file mode 100644 (file)
index 0000000..ed9411f
--- /dev/null
@@ -0,0 +1,23 @@
+#include <stdint.h>
+#include <byteswap.h>
+#include <errno.h>
+#include <string.h>
+#include <gpxe/icmp6.h>
+#include <gpxe/ip6.h>
+#include <gpxe/in.h>
+#include <gpxe/netdevice.h>
+#include <gpxe/pkbuff.h>
+#include <gpxe/tcpip.h>
+
+#define NDP_STATE_INVALID 0
+#define NDP_STATE_INCOMPLETE 1
+#define NDP_STATE_REACHABLE 2
+#define NDP_STATE_DELAY 3
+#define NDP_STATE_PROBE 4
+#define NDP_STATE_STALE 5
+
+static struct ndp_entry * ndp_find_entry ( struct in6_addr *in6 );
+int ndp_resolve ( struct net_device *netdev, struct in6_addr *src,
+                 struct in6_addr *dest, void *dest_ll_addr );
+int ndp_process_advert ( struct pk_buff *pkb, struct sockaddr_tcpip *st_src,
+                        struct sockaddr_tcpip *st_dest );
diff --git a/src/net/icmpv6.c b/src/net/icmpv6.c
new file mode 100644 (file)
index 0000000..183fd06
--- /dev/null
@@ -0,0 +1,128 @@
+#include <stdint.h>
+#include <string.h>
+#include <byteswap.h>
+#include <errno.h>
+#include <gpxe/in.h>
+#include <gpxe/ip6.h>
+#include <gpxe/if_ether.h>
+#include <gpxe/pkbuff.h>
+#include <gpxe/ndp.h>
+#include <gpxe/icmp6.h>
+#include <gpxe/tcpip.h>
+#include <gpxe/netdevice.h>
+
+struct tcpip_protocol icmp6_protocol;
+
+/**
+ * Send neighbour solicitation packet
+ *
+ * @v netdev   Network device
+ * @v src      Source address
+ * @v dest     Destination address
+ *
+ * This function prepares a neighbour solicitation packet and sends it to the
+ * network layer.
+ */
+int icmp6_send_solicit ( struct net_device *netdev, struct in6_addr *src,
+                        struct in6_addr *dest ) {
+       union {
+               struct sockaddr_in6 sin6;
+               struct sockaddr_tcpip st;
+       } st_dest;
+       struct ll_protocol *ll_protocol = netdev->ll_protocol;
+       struct neighbour_solicit *nsolicit;
+       struct pk_buff *pkb = alloc_pkb ( sizeof ( *nsolicit ) + MIN_PKB_LEN );
+       pkb_reserve ( pkb, MAX_HDR_LEN );
+       nsolicit = pkb_put ( pkb, sizeof ( *nsolicit ) );
+
+       /* Fill up the headers */
+       memset ( nsolicit, 0, sizeof ( *nsolicit ) );
+       nsolicit->type = ICMP6_NSOLICIT;
+       nsolicit->code = 0;
+       nsolicit->target = *dest;
+       nsolicit->opt_type = 1;
+       nsolicit->opt_len = ( 2 + ll_protocol->ll_addr_len ) / 8;
+       memcpy ( nsolicit->opt_ll_addr, netdev->ll_addr,
+                               netdev->ll_protocol->ll_addr_len );
+       /* Partial checksum */
+       nsolicit->csum = 0;
+       nsolicit->csum = tcpip_chksum ( nsolicit, sizeof ( *nsolicit ) );
+
+       /* Solicited multicast address */
+       st_dest.sin6.sin_family = AF_INET6;
+       st_dest.sin6.sin6_addr.in6_u.u6_addr8[0] = 0xff;
+       st_dest.sin6.sin6_addr.in6_u.u6_addr8[2] = 0x02;
+       st_dest.sin6.sin6_addr.in6_u.u6_addr16[1] = 0x0000;
+       st_dest.sin6.sin6_addr.in6_u.u6_addr32[1] = 0x00000000;
+       st_dest.sin6.sin6_addr.in6_u.u6_addr16[4] = 0x0000;
+       st_dest.sin6.sin6_addr.in6_u.u6_addr16[5] = 0x0001;
+       st_dest.sin6.sin6_addr.in6_u.u6_addr32[3] = dest->in6_u.u6_addr32[3];
+       st_dest.sin6.sin6_addr.in6_u.u6_addr8[13] = 0xff;
+       
+       /* Send packet over IP6 */
+       tcpip_tx ( pkb, &icmp6_protocol, &st_dest.st );
+}
+
+/**
+ * Process ICMP6 headers
+ *
+ * @v pkb      Packet buffer
+ * @v st_src   Source address
+ * @v st_dest  Destination address
+ */
+static int icmp6_rx ( struct pk_buff *pkb, struct sockaddr_tcpip *st_src,
+                     struct sockaddr_tcpip *st_dest ) {
+       struct icmp6_header *icmp6hdr = pkb->data;
+
+       /* Sanity check */
+       if ( pkb_len ( pkb ) < sizeof ( *icmp6hdr ) ) {
+               DBG ( "Packet too short (%d bytes)\n", pkb_len ( pkb ) );
+               free_pkb ( pkb );
+               return -EINVAL;
+       }
+
+       /* TODO: Verify checksum */
+
+       /* Process the ICMP header */
+       switch ( icmp6hdr->type ) {
+       case ICMP6_NADVERT:
+               return ndp_process_advert ( pkb, st_src, st_dest );
+       }
+       return -ENOSYS;
+}
+
+#if 0
+void icmp6_test_nadvert (struct net_device *netdev, struct sockaddr_in6 *server_p, char *ll_addr) {
+
+               struct sockaddr_in6 server;
+               memcpy ( &server, server_p, sizeof ( server ) );
+                struct pk_buff *rxpkb = alloc_pkb ( 500 );
+                pkb_reserve ( rxpkb, MAX_HDR_LEN );
+                struct neighbour_advert *nadvert = pkb_put ( rxpkb, sizeof ( *nadvert ) );
+                nadvert->type = 136;
+                nadvert->code = 0;
+                nadvert->flags = ICMP6_FLAGS_SOLICITED;
+               nadvert->csum = 0xffff;
+               nadvert->target = server.sin6_addr;
+                nadvert->opt_type = 2;
+                nadvert->opt_len = 1;
+                memcpy ( nadvert->opt_ll_addr, ll_addr, 6 );
+                struct ip6_header *ip6hdr = pkb_push ( rxpkb, sizeof ( *ip6hdr ) );
+                ip6hdr->ver_traffic_class_flow_label = htonl ( 0x60000000 );
+               ip6hdr->hop_limit = 255;
+               ip6hdr->nxt_hdr = 58;
+               ip6hdr->payload_len = htons ( sizeof ( *nadvert ) );
+                ip6hdr->src = server.sin6_addr;
+                ip6hdr->dest = server.sin6_addr;
+               hex_dump ( rxpkb->data, pkb_len ( rxpkb ) );
+                net_rx ( rxpkb, netdev, htons ( ETH_P_IPV6 ), ll_addr );
+}
+#endif
+
+/** ICMP6 protocol */
+struct tcpip_protocol icmp6_protocol __tcpip_protocol = {
+       .name = "ICMP6",
+       .rx = icmp6_rx,
+       .tcpip_proto = IP_ICMP6, // 58
+       .csum_offset = 2,
+};
index 38705aa..d00562a 100644 (file)
 #include <errno.h>
-#include <stdlib.h>
 #include <stdint.h>
 #include <string.h>
+#include <stdlib.h>
+#include <malloc.h>
+#include <vsprintf.h>
 #include <byteswap.h>
+#include <gpxe/in.h>
+#include <gpxe/ip6.h>
+#include <gpxe/ndp.h>
+#include <gpxe/list.h>
+#include <gpxe/icmp6.h>
+#include <gpxe/tcpip.h>
+#include <gpxe/socket.h>
 #include <gpxe/pkbuff.h>
 #include <gpxe/netdevice.h>
-#include <gpxe/in.h>
 #include <gpxe/if_ether.h>
-#include <gpxe/tcpip.h>
+
+struct net_protocol ipv6_protocol;
+
+/* 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,
+};
+
+/** An IPv6 routing table entry */
+struct ipv6_miniroute {
+       /* List of miniroutes */
+       struct list_head list;
+       /* Network device */
+       struct net_device *netdev;
+       /* Destination prefix */
+       struct in6_addr prefix;
+       /* Prefix length */
+       int prefix_len;
+       /* IPv6 address of interface */
+       struct in6_addr address;
+       /* Gateway address */
+       struct in6_addr gateway;
+};
+
+/** List of IPv6 miniroutes */
+static LIST_HEAD ( miniroutes );
 
 /**
- * Transmit IP6 packets
+ * Add IPv6 interface
+ *
+ * @v netdev   Network device
+ * @v prefix   Destination prefix
+ * @v address  Address of the interface
+ * @v gateway  Gateway address (or ::0 for no gateway)
+ */
+int add_ipv6_address ( struct net_device *netdev, struct in6_addr prefix,
+                      int prefix_len, struct in6_addr address,
+                      struct in6_addr gateway ) {
+       struct ipv6_miniroute *miniroute;
+
+       miniroute = malloc ( sizeof ( *miniroute ) );
+       if ( !miniroute ) {
+               DBG ( "Not enough memory\n" );
+               return -ENOMEM;
+       }
+       miniroute->netdev = 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 );
+       }
+       return 0;
+}
+
+/**
+ * Remove IPv6 interface
+ *
+ * @v netdev   Network device
+ */
+void del_ipv6_address ( struct net_device *netdev ) {
+       struct ipv6_miniroute *miniroute;
+
+       list_for_each_entry ( miniroute, &miniroutes, list ) {
+               if ( miniroute->netdev == netdev ) {
+                       list_del ( &miniroute->list );
+                       break;
+               }
+       }
+}
+
+/**
+ * Calculate TCPIP checksum
+ *
+ * @v pkb      Packet buffer
+ * @v tcpip    TCP/IP protocol
+ *
+ * This function constructs the pseudo header and completes the checksum in the
+ * upper layer header.
+ */
+static void ipv6_tx_csum ( struct pk_buff *pkb, struct tcpip_protocol *tcpip ) {
+       struct ip6_header *ip6hdr = pkb->data;
+       struct ipv6_pseudo_header pshdr;
+       uint16_t *csum = ( ( ( void * ) ip6hdr ) + sizeof ( *ip6hdr ) +
+                       tcpip->csum_offset );
+
+       /* 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.nxt_hdr = ip6hdr->nxt_hdr;
+
+       /* Update checksum value */
+       *csum = tcpip_continue_chksum ( *csum, &pshdr, sizeof ( pshdr ) );
+}
+
+/**
+ * Dump IP6 header for debugging
+ *
+ * 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 ) );
+}
+
+/**
+ * Transmit IP6 packet
+ *
+ * pkb         Packet 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,
-                    struct tcpip_protocol *tcpip_protocol,
+                    struct tcpip_protocol *tcpip,
                     struct sockaddr_tcpip *st_dest ) {
-       return -ENOSYS;
+       struct sockaddr_in6 *dest = ( struct sockaddr_in6* ) st_dest;
+       struct in6_addr next_hop;
+       struct ipv6_miniroute *miniroute;
+       struct net_device *netdev = NULL;
+       uint8_t ll_dest_buf[MAX_LL_ADDR_LEN];
+       const uint8_t *ll_dest = ll_dest_buf;
+       int rc;
+
+       /* Construct the IPv6 packet */
+       struct ip6_header *ip6hdr = pkb_push ( pkb, 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->nxt_hdr = tcpip->tcpip_proto;
+       ip6hdr->hop_limit = IP6_HOP_LIMIT; // 255
+
+       /* Determine the next hop address and interface
+        *
+        * TODO: Implement the routing table.
+        */
+       next_hop = dest->sin6_addr;
+       list_for_each_entry ( miniroute, &miniroutes, list ) {
+               if ( ( strncmp ( &ip6hdr->dest, &miniroute->prefix,
+                                       miniroute->prefix_len ) == 0 ) ||
+                    ( IP6_EQUAL ( miniroute->gateway, ip6_none ) ) ) {
+                       netdev = miniroute->netdev;
+                       ip6hdr->src = miniroute->address;
+                       if ( ! ( IS_UNSPECIFIED ( miniroute->gateway ) ) ) {
+                               next_hop = miniroute->gateway;
+                       }
+                       break;
+               }
+       }
+       /* No network interface identified */
+       if ( !netdev ) {
+               DBG ( "No route to host %s\n", inet6_ntoa ( ip6hdr->dest ) );
+               rc = -EHOSTUNREACH;
+               goto err;
+       }
+
+       /* Complete the transport layer checksum */
+       if ( tcpip->csum_offset > 0 ) {
+               ipv6_tx_csum ( pkb, tcpip );
+       }
+
+       /* Print IPv6 header */
+       ipv6_dump ( ip6hdr );
+       
+       /* Resolve link layer address */
+       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];
+               ll_dest_buf[3] = next_hop.in6_u.u6_addr8[13];
+               ll_dest_buf[4] = next_hop.in6_u.u6_addr8[14];
+               ll_dest_buf[5] = next_hop.in6_u.u6_addr8[15];
+       } else {
+               /* Unicast address needs to be resolved by NDP */
+               if ( ( rc = ndp_resolve ( netdev, &next_hop, &ip6hdr->src,
+                                         ll_dest_buf ) ) != 0 ) {
+                       DBG ( "No entry for %s\n", inet6_ntoa ( next_hop ) );
+                       goto err;
+               }
+       }
+
+       /* Transmit packet */
+       return net_tx ( pkb, netdev, &ipv6_protocol, ll_dest );
+
+  err:
+       free_pkb ( pkb );
+       return rc;
+}
+
+/**
+ * Process next IP6 header
+ *
+ * @v pkb      Packet buffer
+ * @v nxt_hdr  Next header number
+ * @v src      Source socket address
+ * @v dest     Destination socket address
+ *
+ * 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 ) {
+       switch ( nxt_hdr ) {
+       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: 
+               break;
+       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 );
 }
 
 /**
  * Process incoming IP6 packets
  *
- * Placeholder function. Should rewrite in net/ipv6.c
+ * @v pkb              Packet 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 __unused,
-                    struct net_device *netdev __unused,
-                    const void *ll_source __unused ) {
-       return -ENOSYS;
+static int ipv6_rx ( struct pk_buff *pkb,
+                    struct net_device *netdev,
+                    const void *ll_source ) {
+
+       struct ip6_header *ip6hdr = pkb->data;
+       union {
+               struct sockaddr_in6 sin6;
+               struct sockaddr_tcpip st;
+       } src, dest;
+
+       /* Sanity check */
+       if ( pkb_len ( pkb ) < sizeof ( *ip6hdr ) ) {
+               DBG ( "Packet too short (%d bytes)\n", pkb_len ( pkb ) );
+               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 ) {
+               DBG ( "Invalid protocol version\n" );
+               goto drop;
+       }
+
+       /* Check the payload length */
+       if ( ntohs ( ip6hdr->payload_len ) > pkb_len ( pkb ) ) {
+               DBG ( "Inconsistent packet length (%d bytes)\n",
+                       ip6hdr->payload_len );
+               goto drop;
+       }
+
+       /* Ignore the traffic class and flow control values */
+
+       /* Construct socket address */
+       memset ( &src, 0, sizeof ( src ) );
+       src.sin6.sin_family = AF_INET6;
+       src.sin6.sin6_addr = ip6hdr->src;
+       memset ( &dest, 0, sizeof ( dest ) );
+       dest.sin6.sin_family = AF_INET6;
+       dest.sin6.sin6_addr = ip6hdr->dest;
+
+       /* Strip header */
+       pkb_unput ( pkb, pkb_len ( pkb ) - ntohs ( ip6hdr->payload_len ) -
+                                                       sizeof ( *ip6hdr ) );
+       pkb_pull ( pkb, sizeof ( *ip6hdr ) );
+
+       /* Send it to the transport layer */
+       return ipv6_process_nxt_hdr ( pkb, ip6hdr->nxt_hdr, &src.st, &dest.st );
+
+  drop:
+       DBG ( "Packet dropped\n" );
+       free_pkb ( pkb );
+       return -1;
+}
+
+/**
+ * Print a IP6 address as xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx
+ */
+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] );
+       return buf;
 }
 
 static const char * ipv6_ntoa ( const void *net_addr ) {
-//     return inet6_ntoa ( * ( ( struct in6_addr * ) net_addr ) );
-       return "no support yet";
+       return inet6_ntoa ( * ( ( struct in6_addr * ) net_addr ) );
 }
 
 /** IPv6 protocol */
 struct net_protocol ipv6_protocol __net_protocol = {
-       .name = "IP6",
+       .name = "IPv6",
        .net_proto = htons ( ETH_P_IPV6 ),
        .net_addr_len = sizeof ( struct in6_addr ),
        .rx = ipv6_rx,
diff --git a/src/net/ndp.c b/src/net/ndp.c
new file mode 100644 (file)
index 0000000..b72cf76
--- /dev/null
@@ -0,0 +1,181 @@
+#include <stdint.h>
+#include <string.h>
+#include <byteswap.h>
+#include <errno.h>
+#include <gpxe/if_ether.h>
+#include <gpxe/pkbuff.h>
+#include <gpxe/ndp.h>
+#include <gpxe/icmp6.h>
+#include <gpxe/ip6.h>
+#include <gpxe/netdevice.h>
+
+/** @file
+ *
+ * Neighbour Discovery Protocol
+ *
+ * This file implements address resolution as specified by the neighbour
+ * discovery protocol in RFC2461. This protocol is part of the IPv6 protocol
+ * family.
+ */
+
+/* A neighbour entry */
+struct ndp_entry {
+       /** Target IP6 address */
+       struct in6_addr in6;
+       /** Link layer protocol */
+       struct ll_protocol *ll_protocol;
+       /** Link-layer address */
+       uint8_t ll_addr[MAX_LL_ADDR_LEN];
+       /** State of the neighbour entry */
+       int state;
+};
+
+/** Number of entries in the neighbour cache table */
+#define NUM_NDP_ENTRIES 4
+
+/** The neighbour cache table */
+static struct ndp_entry ndp_table[NUM_NDP_ENTRIES];
+#define ndp_table_end &ndp_table[NUM_NDP_ENTRIES]
+
+static unsigned int next_new_ndp_entry = 0;
+
+/**
+ * Find entry in the neighbour cache
+ *
+ * @v in6      IP6 address
+ */
+static struct ndp_entry *
+ndp_find_entry ( struct in6_addr *in6 ) {
+       struct ndp_entry *ndp;
+
+       for ( ndp = ndp_table ; ndp < ndp_table_end ; ndp++ ) {
+               if ( IP6_EQUAL ( ( *in6 ), ndp->in6 ) && 
+                    ( ndp->state != NDP_STATE_INVALID ) ) {
+                       return ndp;
+               }
+       }
+       return NULL;
+}
+
+/**
+ * Add NDP entry
+ * 
+ * @v netdev   Network device
+ * @v in6      IP6 address
+ * @v ll_addr  Link-layer address
+ * @v state    State of the entry - one of the NDP_STATE_XXX values
+ */
+void add_ndp_entry ( struct net_device *netdev, struct in6_addr *in6,
+                    void *ll_addr, int state ) {
+       struct ndp_entry *ndp;
+       ndp = &ndp_table[next_new_ndp_entry++ % NUM_NDP_ENTRIES];
+
+       /* Fill up entry */
+       ndp->ll_protocol = netdev->ll_protocol;
+       memcpy ( &ndp->in6, &( *in6 ), sizeof ( *in6 ) );
+       if ( ll_addr ) {
+               memcpy ( ndp->ll_addr, ll_addr, netdev->ll_protocol->ll_addr_len );
+       } else {
+               memset ( ndp->ll_addr, 0, netdev->ll_protocol->ll_addr_len );
+       }
+       ndp->state = state;
+       DBG ( "New neighbour cache entry (%d): IP6 %s => %s %s\n",
+             ( ndp - ndp_table ) / sizeof ( *ndp ),
+             inet6_ntoa ( ndp->in6 ), netdev->ll_protocol->name,
+             netdev->ll_protocol->ntoa ( ndp->ll_addr ) );
+}
+
+/**
+ * Resolve the link-layer address
+ *
+ * @v netdev           Network device
+ * @v dest             Destination address
+ * @v src              Source address
+ * @ret dest_ll_addr   Destination link-layer address or NULL
+ * @ret rc             Status
+ *
+ * This function looks up the neighbour cache for an entry corresponding to the
+ * destination address. If it finds a valid entry, it fills up dest_ll_addr and
+ * returns 0. Otherwise it sends a neighbour solicitation to the solicited
+ * multicast address.
+ */
+int ndp_resolve ( struct net_device *netdev, struct in6_addr *dest,
+                 struct in6_addr *src, void *dest_ll_addr ) {
+       struct ll_protocol *ll_protocol = netdev->ll_protocol;
+       struct ndp_entry *ndp;
+       int rc;
+
+       ndp = ndp_find_entry ( dest );
+       /* Check if the entry is valid */
+       if ( ndp && ndp->state == NDP_STATE_REACHABLE ) {
+               DBG ( "Neighbour cache hit: IP6 %s => %s %s\n",
+                     inet6_ntoa ( *dest ), ll_protocol->name,
+                     ll_protocol->ntoa ( ndp->ll_addr ) );
+               memcpy ( dest_ll_addr, ndp->ll_addr, ll_protocol->ll_addr_len );
+               return 0;
+       }
+
+       /* Check if the entry was already created */
+       if ( ndp ) {
+               DBG ( "Awaiting neighbour advertisement (cache entry %d)\n",
+                     ( ( ndp - ndp_table ) / sizeof ( *ndp ) ) );
+               /* For test */
+//             ndp->state = NDP_STATE_REACHABLE;
+//             memcpy ( ndp->ll_addr, netdev->ll_addr, 6 );
+//             assert ( ndp->ll_protocol->ll_addr_len == 6 );
+//             icmp6_test_nadvert ( netdev, dest, ndp->ll_addr );
+//             assert ( ndp->state == NDP_STATE_REACHABLE );
+               /* Take it out till here */
+               return -ENOENT;
+       }
+       DBG ( "Neighbour cache miss: IP6 %s\n", inet6_ntoa ( *dest ) );
+
+       /* Add entry in the neighbour cache */
+       add_ndp_entry ( netdev, dest, NULL, NDP_STATE_INCOMPLETE );
+
+       /* Send neighbour solicitation */
+       if ( ( rc = icmp6_send_solicit ( netdev, src, dest ) ) != 0 ) {
+               return rc;
+       }
+       return -ENOENT;
+}
+
+/**
+ * Process neighbour advertisement
+ *
+ * @v pkb      Packet buffer
+ * @v st_src   Source address
+ * @v st_dest  Destination address 
+ */
+int ndp_process_advert ( struct pk_buff *pkb, struct sockaddr_tcpip *st_src,
+                          struct sockaddr_tcpip *st_dest ) {
+       struct neighbour_advert *nadvert = pkb->data;
+       struct ndp_entry *ndp;
+
+       /* Sanity check */
+       if ( pkb_len ( pkb ) < sizeof ( *nadvert ) ) {
+               DBG ( "Packet too short (%d bytes)\n", pkb_len ( pkb ) );
+               return -EINVAL;
+       }
+
+       assert ( nadvert->code == 0 );
+       assert ( nadvert->flags & ICMP6_FLAGS_SOLICITED );
+       assert ( nadvert->opt_type == 2 );
+
+       /* Update the neighbour cache, if entry is present */
+       ndp = ndp_find_entry ( &nadvert->target );
+       if ( ndp ) {
+
+       assert ( nadvert->opt_len ==
+                       ( ( 2 + ndp->ll_protocol->ll_addr_len ) / 8 ) );
+
+               if ( IP6_EQUAL ( ndp->in6, nadvert->target ) ) {
+                       memcpy ( ndp->ll_addr, nadvert->opt_ll_addr,
+                                ndp->ll_protocol->ll_addr_len );
+                       ndp->state = NDP_STATE_REACHABLE;
+                       return 0;
+               }
+       }
+       DBG ( "Unsolicited advertisement (dropping packet)\n" );
+       return 0;
+}