[ipv6] Implement handling of neighbour solicit messages
authorMatthew Iselin <matthew@theiselins.net>
Wed, 1 Jun 2011 12:13:30 +0000 (22:13 +1000)
committerMarty Connor <mdc@etherboot.org>
Thu, 21 Jul 2011 00:34:23 +0000 (20:34 -0400)
Implement handling of neighbour solicit messages and improve handling
of neighbour advert messages.

Also:

  - Add handling of ICMPv6 echoes, mostly for debugging
  - Implement router advertisement handling for autoconfiguration

Signed-off-by: Matthew Iselin <matthew@theiselins.net>
Signed-off-by: Marty Connor <mdc@etherboot.org>
src/include/gpxe/icmp6.h
src/include/gpxe/ndp.h
src/net/icmpv6.c
src/net/ndp.c

index e6f6ccd..6040258 100644 (file)
@@ -11,6 +11,7 @@ FILE_LICENCE ( GPL2_OR_LATER );
 
 #include <gpxe/ip6.h>
 #include <gpxe/ndp.h>
+#include <gpxe/tcpip.h>
 
 #include <gpxe/tables.h>
 
@@ -38,8 +39,12 @@ struct icmp6_net_protocol {
 /** Declare an ICMPv6 protocol */
 #define __icmp6_net_protocol __table_entry ( ICMP6_NET_PROTOCOLS, 01 )
 
-#define ICMP6_NSOLICIT 135
-#define ICMP6_NADVERT 136
+#define ICMP6_ECHO_REQUEST     128
+#define ICMP6_ECHO_RESPONSE    129
+#define ICMP6_ROUTER_SOLICIT   133
+#define ICMP6_ROUTER_ADVERT    134
+#define ICMP6_NSOLICIT         135
+#define ICMP6_NADVERT          136
 
 extern struct tcpip_protocol icmp6_protocol;
 
@@ -50,32 +55,6 @@ struct icmp6_header {
        /* 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;
-  /* FIXME:  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;
-  /* FIXME:  hack alert */
-       uint8_t opt_ll_addr[6];
-};
-
 #define ICMP6_FLAGS_ROUTER 0x80
 #define ICMP6_FLAGS_SOLICITED 0x40
 #define ICMP6_FLAGS_OVERRIDE 0x20
@@ -86,4 +65,6 @@ int icmp6_rx ( struct io_buffer *iobuf, struct sockaddr_tcpip *st_src,
 
 int icmp6_send_solicit ( struct net_device *netdev, struct in6_addr *src, struct in6_addr *dest );
 
+int icmp6_send_advert ( struct net_device *netdev, struct in6_addr *src, struct in6_addr *dest );
+
 #endif /* _GPXE_ICMP6_H */
index db32b0c..6830362 100644 (file)
@@ -1,3 +1,6 @@
+#ifndef _GPXE_NDP_H
+#define _GPXE_NDP_H
+
 #include <stdint.h>
 #include <byteswap.h>
 #include <string.h>
 #define NDP_STATE_PROBE 4
 #define NDP_STATE_STALE 5
 
+#define NDP_OPTION_SOURCE_LL        1
+#define NDP_OPTION_TARGET_LL        2
+#define NDP_OPTION_PREFIX_INFO      3
+#define NDP_OPTION_REDIRECT         4
+#define NDP_OPTION_MTU              5
+
+struct neighbour_solicit {
+       uint8_t type;
+       uint8_t code;
+       uint16_t csum;
+       uint32_t reserved;
+       struct in6_addr target;
+};
+
+struct neighbour_advert {
+       uint8_t type;
+       uint8_t code;
+       uint16_t csum;
+       uint8_t flags;
+       uint8_t reserved;
+       struct in6_addr target;
+};
+
+struct router_solicit {
+       uint8_t type;
+       uint8_t code;
+       uint16_t csum;
+       uint32_t reserved;
+};
+
+struct router_advert {
+       uint8_t type;
+       uint8_t code;
+       uint16_t csum;
+       uint16_t lifetime;
+       uint16_t hops_flags;
+       uint32_t reachable_time;
+       uint32_t retrans_time;
+};
+
+struct ndp_option
+{
+       uint8_t type;
+       uint8_t length;
+};
+
+struct ll_option
+{
+       uint8_t type;
+       uint8_t length;
+       uint8_t address[6];
+};
+
+struct prefix_option
+{
+       uint8_t type;
+       uint8_t length;
+       uint8_t prefix_len;
+       uint8_t flags_rsvd;
+       uint32_t lifetime;
+       uint32_t pref_lifetime;
+       uint32_t rsvd2;
+       uint8_t prefix[16];
+};
+
+#define RADVERT_MANAGED                0x100
+#define RADVERT_OTHERCONF      0x101
+
 int ndp_resolve ( struct net_device *netdev, struct in6_addr *src,
                  struct in6_addr *dest, void *dest_ll_addr );
-int ndp_process_advert ( struct io_buffer *iobuf, struct sockaddr_tcpip *st_src,
-                        struct sockaddr_tcpip *st_dest );
+
+int ndp_process_radvert ( struct io_buffer *iobuf, struct sockaddr_tcpip *st_src,
+                         struct sockaddr_tcpip *st_dest, struct net_device *netdev,
+                         struct icmp6_net_protocol *net_protocol );
+
+int ndp_process_nadvert ( struct io_buffer *iobuf, struct sockaddr_tcpip *st_src,
+                         struct sockaddr_tcpip *st_dest,
+                         struct icmp6_net_protocol *net_protocol );
+
+int ndp_process_nsolicit ( struct io_buffer *iobuf, struct sockaddr_tcpip *st_src,
+                          struct sockaddr_tcpip *st_dest, struct net_device *netdev,
+                          struct icmp6_net_protocol *net_protocol );
+
+#endif
+
index 50a548b..fb4d9ea 100644 (file)
@@ -11,6 +11,8 @@
 #include <gpxe/tcpip.h>
 #include <gpxe/netdevice.h>
 
+#include <gpxe/ethernet.h>
+
 struct tcpip_protocol icmp6_protocol;
 
 /**
@@ -31,22 +33,27 @@ int icmp6_send_solicit ( struct net_device *netdev, struct in6_addr *src __unuse
        } st_dest;
        struct ll_protocol *ll_protocol = netdev->ll_protocol;
        struct neighbour_solicit *nsolicit;
-       struct io_buffer *iobuf = alloc_iob ( sizeof ( *nsolicit ) + MIN_IOB_LEN );
+       struct ll_option *llopt;
+       struct io_buffer *iobuf = alloc_iob ( sizeof ( struct ll_option ) + 
+                                             sizeof ( *nsolicit ) + MIN_IOB_LEN );
        iob_reserve ( iobuf, MAX_HDR_LEN );
        nsolicit = iob_put ( iobuf, sizeof ( *nsolicit ) );
+       llopt = iob_put ( iobuf, sizeof ( *llopt ) );
 
        /* 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 );
+       
+       /* Fill in the link-layer address. FIXME: ll_option assumes 6 bytes. */
+       llopt->type = 1;
+       llopt->length = ( 2 + ll_protocol->ll_addr_len ) / 8;
+       memcpy ( llopt->address, netdev->ll_addr, netdev->ll_protocol->ll_addr_len );
+       
        /* Partial checksum */
        nsolicit->csum = 0;
-       nsolicit->csum = tcpip_chksum ( nsolicit, sizeof ( *nsolicit ) );
+       nsolicit->csum = tcpip_chksum ( nsolicit, sizeof ( *nsolicit ) + sizeof ( *llopt ) );
 
        /* Solicited multicast address - FF02::1 (all stations on local network) */
        memset(&st_dest.sin6, 0, sizeof(st_dest.sin6));
@@ -60,6 +67,104 @@ int icmp6_send_solicit ( struct net_device *netdev, struct in6_addr *src __unuse
                          NULL, &nsolicit->csum );
 }
 
+/**
+ * Send neighbour advertisement packet
+ *
+ * @v netdev   Network device
+ * @v src      Source address
+ * @v dest     Destination address
+ *
+ * This function prepares a neighbour advertisement packet and sends it to the
+ * network layer.
+ */
+int icmp6_send_advert ( 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_advert *nadvert;
+       struct ll_option *llopt;
+       struct io_buffer *iobuf = alloc_iob ( sizeof ( struct ll_option ) + 
+                                             sizeof ( *nadvert ) + MIN_IOB_LEN );
+       iob_reserve ( iobuf, MAX_HDR_LEN );
+       nadvert = iob_put ( iobuf, sizeof ( *nadvert ) );
+       llopt = iob_put ( iobuf, sizeof ( *llopt ) );
+
+       /* Fill up the headers */
+       memset ( nadvert, 0, sizeof ( *nadvert ) );
+       nadvert->type = ICMP6_NADVERT;
+       nadvert->code = 0;
+       nadvert->target = *src;
+       nadvert->flags = ICMP6_FLAGS_SOLICITED | ICMP6_FLAGS_OVERRIDE;
+       
+       /* Fill in the link-layer address. FIXME: ll_option assumes 6 bytes. */
+       llopt->type = 2;
+       llopt->length = ( 2 + ll_protocol->ll_addr_len ) / 8;
+       memcpy ( llopt->address, netdev->ll_addr, netdev->ll_protocol->ll_addr_len );
+
+       /* Partial checksum */
+       nadvert->csum = 0;
+       nadvert->csum = tcpip_chksum ( nadvert, sizeof ( *nadvert ) + sizeof ( *llopt ) );
+
+       /* Target network address. */
+       st_dest.sin6.sin_family = AF_INET6;
+       st_dest.sin6.sin6_addr = *dest;
+
+       /* Send packet over IP6 */
+       return tcpip_tx ( iobuf, &icmp6_protocol, NULL, &st_dest.st,
+                         NULL, &nadvert->csum );
+}
+
+/**
+ * Process ICMP6 Echo Request
+ *
+ * @v iobuf I/O buffer containing the original ICMPv6 packet.
+ * @v st_src Address of the source station.
+ * @v st_dest Address of the destination station.
+ */
+int icmp6_handle_echo ( struct io_buffer *iobuf, struct sockaddr_tcpip *st_src,
+                       struct sockaddr_tcpip *st_dest,
+                       struct icmp6_net_protocol *net_protocol __unused ) {
+       struct icmp6_header *icmp6hdr = iobuf->data;
+       size_t len = iob_len ( iobuf );
+       int rc;
+
+       /* Change type to response and recalculate checksum */
+       icmp6hdr->type = ICMP6_ECHO_RESPONSE;
+       icmp6hdr->csum = 0;
+       icmp6hdr->csum = tcpip_chksum ( icmp6hdr, len );
+
+       /* Transmit the response */
+       if ( ( rc = tcpip_tx ( iob_disown ( iobuf ), &icmp6_protocol, st_dest,
+                              st_src, NULL, &icmp6hdr->csum ) ) != 0 ) {
+               DBG ( "ICMP could not transmit ping response: %s\n",
+                     strerror ( rc ) );
+       }
+
+       free_iob(iobuf);
+       return rc;
+}
+
+/**
+ * Identify ICMP6 network layer protocol
+ *
+ * @v net_proto                        Network-layer protocol, in network-endian order
+ * @ret arp_net_protocol       ARP protocol, or NULL
+ *
+ */
+static struct icmp6_net_protocol * icmp6_find_protocol ( uint16_t net_proto ) {
+       struct icmp6_net_protocol *icmp6_net_protocol;
+
+       for_each_table_entry ( icmp6_net_protocol, ICMP6_NET_PROTOCOLS ) {
+               if ( icmp6_net_protocol->net_protocol->net_proto == net_proto ) {
+                       return icmp6_net_protocol;
+               }
+       }
+       return NULL;
+}
+
 /**
  * Process ICMP6 headers
  *
@@ -68,9 +173,13 @@ int icmp6_send_solicit ( struct net_device *netdev, struct in6_addr *src __unuse
  * @v st_dest  Destination address
  */
 int icmp6_rx ( struct io_buffer *iobuf, struct sockaddr_tcpip *st_src,
-              struct sockaddr_tcpip *st_dest, struct net_device *netdev __unused,
-              uint16_t pshdr_csum __unused ) {
+                     struct sockaddr_tcpip *st_dest, struct net_device *netdev,
+                     uint16_t pshdr_csum ) {
        struct icmp6_header *icmp6hdr = iobuf->data;
+       struct icmp6_net_protocol *icmp6_net_protocol;
+       size_t len = iob_len ( iobuf );
+       unsigned int csum;
+       int rc;
 
        /* Sanity check */
        if ( iob_len ( iobuf ) < sizeof ( *icmp6hdr ) ) {
@@ -79,14 +188,42 @@ int icmp6_rx ( struct io_buffer *iobuf, struct sockaddr_tcpip *st_src,
                return -EINVAL;
        }
 
-       /* TODO: Verify checksum */
+       /* Verify checksum */
+       csum = tcpip_continue_chksum ( pshdr_csum, icmp6hdr, len );
+       if ( csum != 0 ) {
+               DBG ( "ICMPv6 checksum incorrect (is %04x, should be 0000)\n",
+                     csum );
+               DBG_HD ( icmp6hdr, len );
+               rc = -EINVAL;
+               goto done;
+       }
+       
+       /* Get the net protocol for this packet. */
+       icmp6_net_protocol = icmp6_find_protocol ( htons ( ETH_P_IPV6 ) );
+       if ( ! icmp6_net_protocol ) {
+               rc = 0;
+               goto done;
+       }
+
+       DBG ( "ICMPv6: packet with type %d and code %x\n", icmp6hdr->type, icmp6hdr->code);
 
        /* Process the ICMP header */
        switch ( icmp6hdr->type ) {
+       case ICMP6_ROUTER_ADVERT:
+           return ndp_process_radvert ( iobuf, st_src, st_dest, netdev, icmp6_net_protocol );
+       case ICMP6_NSOLICIT:
+               return ndp_process_nsolicit ( iobuf, st_src, st_dest, netdev, icmp6_net_protocol );
        case ICMP6_NADVERT:
-               return ndp_process_advert ( iobuf, st_src, st_dest );
+               return ndp_process_nadvert ( iobuf, st_src, st_dest, icmp6_net_protocol );
+       case ICMP6_ECHO_REQUEST:
+               return icmp6_handle_echo ( iobuf, st_src, st_dest, icmp6_net_protocol );
        }
-       return -ENOSYS;
+
+       rc = -ENOSYS;
+
+ done:
+       free_iob ( iobuf );
+       return rc;
 }
 
 #if 0
index 8bea8b3..8e61ca9 100644 (file)
@@ -1,5 +1,6 @@
 #include <stdint.h>
 #include <string.h>
+#include <stdlib.h>
 #include <byteswap.h>
 #include <errno.h>
 #include <gpxe/if_ether.h>
@@ -49,7 +50,7 @@ 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 ) && 
+               if ( IP6_EQUAL ( ( *in6 ), ndp->in6 ) &&
                     ( ndp->state != NDP_STATE_INVALID ) ) {
                        return ndp;
                }
@@ -59,13 +60,13 @@ ndp_find_entry ( struct in6_addr *in6 ) {
 
 /**
  * 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
  */
-static void 
+static void
 add_ndp_entry ( struct net_device *netdev, struct in6_addr *in6,
                void *ll_addr, int state ) {
        struct ndp_entry *ndp;
@@ -139,16 +140,107 @@ int ndp_resolve ( struct net_device *netdev, struct in6_addr *dest,
        return -ENOENT;
 }
 
+/**
+ * Process Router Advertisement
+ *
+ * @v iobuf I/O buffer containing the data.
+ * @v st_src Address of the source station.
+ * @v st_dest Address of the destination station. Typically FF02::1.
+ */
+int ndp_process_radvert ( struct io_buffer *iobuf, struct sockaddr_tcpip *st_src,
+                         struct sockaddr_tcpip *st_dest __unused, struct net_device *netdev,
+                         struct icmp6_net_protocol *net_protocol __unused ) {
+       struct router_advert *radvert = iobuf->data;
+       struct ndp_option *options = iobuf->data + sizeof(struct router_advert);
+       struct in6_addr router_addr = ( ( struct sockaddr_in6 * ) st_src )->sin6_addr;
+       struct in6_addr host_addr;
+       int rc = -ENOENT;
+       uint8_t prefix_len = 0;
+       size_t offset = sizeof ( struct router_advert ), ll_size;
+
+       memset ( &host_addr, 0, sizeof ( host_addr ) );
+
+       /* Verify that we shouldn't be trying DHCPv6 instead. */
+       if ( ntohs ( radvert->hops_flags ) & RADVERT_MANAGED ) {
+               DBG ( "ndp: router advertisement suggests DHCPv6\n" );
+               return 0;
+       }
+
+       /* Parse options. */
+       while ( offset < iob_len( iobuf ) ) {
+
+           switch ( options->type ) {
+           case NDP_OPTION_PREFIX_INFO:
+               {
+               struct prefix_option *opt = (struct prefix_option *) options;
+
+               prefix_len = opt->prefix_len;
+
+               if ( prefix_len % 8 ) {
+                       /* FIXME: non-aligned prefixes unhandled */
+                       DBG ( "ndp: prefix length is unaligned, connectivity may suffer.\n" );
+               }
+
+               if ( prefix_len > 64 ) {
+                       /* > 64-bit prefix shouldn't happen. */
+                       DBG ( "ndp: prefix length is quite long, connectivity may suffer.\n" );
+               }
+
+               /* Create an IPv6 address for this station based on the prefix. */
+               ll_size = netdev->ll_protocol->ll_addr_len;
+               if ( ll_size < 6 ) {
+                       memcpy ( host_addr.s6_addr + (8 - ll_size), netdev->ll_addr, ll_size );
+               } else {
+                       /* Create an EUI-64 identifier. */
+                       memcpy( host_addr.s6_addr + 8, netdev->ll_addr, 3 );
+                       memcpy( host_addr.s6_addr + 8 + 5, netdev->ll_addr + 3, 3 );
+                       host_addr.s6_addr[11] = 0xFF;
+                       host_addr.s6_addr[12] = 0xFE;
+
+                       /* Designate that this is in fact an EUI-64. */
+                       host_addr.s6_addr[8] |= 0x2;
+               }
+
+               memcpy( &host_addr.s6_addr, opt->prefix, prefix_len / 8 );
+
+               rc = 0;
+               }
+               break;
+        default:
+               DBG ( "unhandled ndp option %d\n", options->type );
+           }
+
+           offset += options->length * 8;
+           options = (struct ndp_option *) (iobuf->data + offset);
+       }
+
+       if ( rc ) {
+               DBG ( "ndp: couldn't generate a prefix from a router advertisement\n" );
+               return 0;
+       }
+
+       /* Configure a route based on this router if none exists. */
+       if ( net_protocol->check ( netdev, &host_addr ) ) {
+               DBG ( "ndp: autoconfigured %s/%d via a router advertisement\n", inet6_ntoa( host_addr ), prefix_len);
+
+               add_ipv6_address ( netdev, host_addr, prefix_len, host_addr, router_addr );
+       }
+
+       return 0;
+}
+
 /**
  * Process neighbour advertisement
  *
  * @v iobuf    I/O buffer
  * @v st_src   Source address
- * @v st_dest  Destination address 
+ * @v st_dest  Destination address
  */
-int ndp_process_advert ( struct io_buffer *iobuf, struct sockaddr_tcpip *st_src __unused,
-                          struct sockaddr_tcpip *st_dest __unused ) {
+int ndp_process_nadvert ( struct io_buffer *iobuf, struct sockaddr_tcpip *st_src __unused,
+                          struct sockaddr_tcpip *st_dest __unused,
+                          struct icmp6_net_protocol *net_protocol __unused ) {
        struct neighbour_advert *nadvert = iobuf->data;
+       struct ll_option *ll_opt = iobuf->data + sizeof ( *nadvert );
        struct ndp_entry *ndp;
 
        /* Sanity check */
@@ -157,19 +249,21 @@ int ndp_process_advert ( struct io_buffer *iobuf, struct sockaddr_tcpip *st_src
                return -EINVAL;
        }
 
+       /* FIXME: assumes link-layer option is first. */
+
        assert ( nadvert->code == 0 );
        assert ( nadvert->flags & ICMP6_FLAGS_SOLICITED );
-       assert ( nadvert->opt_type == 2 );
+       assert ( ll_opt->type == 2 );
 
        /* Update the neighbour cache, if entry is present */
        ndp = ndp_find_entry ( &nadvert->target );
        if ( ndp ) {
 
-       assert ( nadvert->opt_len ==
+       assert ( ll_opt->length ==
                        ( ( 2 + ndp->ll_protocol->ll_addr_len ) / 8 ) );
 
                if ( IP6_EQUAL ( ndp->in6, nadvert->target ) ) {
-                       memcpy ( ndp->ll_addr, nadvert->opt_ll_addr,
+                       memcpy ( ndp->ll_addr, ll_opt->address,
                                 ndp->ll_protocol->ll_addr_len );
                        ndp->state = NDP_STATE_REACHABLE;
                        return 0;
@@ -178,3 +272,30 @@ int ndp_process_advert ( struct io_buffer *iobuf, struct sockaddr_tcpip *st_src
        DBG ( "Unsolicited advertisement (dropping packet)\n" );
        return 0;
 }
+
+/**
+ * Process neighbour solicitation
+ *
+ * @v iobuf    I/O buffer
+ * @v st_src   Source address
+ * @v st_dest  Destination address
+ * @v netdev   Network device the packet was received on.
+ */
+int ndp_process_nsolicit ( struct io_buffer *iobuf __unused, struct sockaddr_tcpip *st_src,
+                          struct sockaddr_tcpip *st_dest __unused, struct net_device *netdev,
+                          struct icmp6_net_protocol *net_protocol ) {
+       struct neighbour_solicit *nsolicit = iobuf->data;
+       struct in6_addr *src =  &( ( struct sockaddr_in6 * ) st_src )->sin6_addr;
+
+       /* Does this match any addresses on the interface? */
+       if ( ! net_protocol->check ( netdev, &nsolicit->target ) ) {
+               /* Send an advertisement to the host. */
+               DBG ( "ndp: neighbour solicit received for us\n" );
+               return icmp6_send_advert ( netdev, &nsolicit->target, src );
+       } else {
+               DBG ( "ndp: neighbour solicit received but it's not for us\n" );
+       }
+
+       return 0;
+}
+