]> git.ipfire.org Git - thirdparty/ipxe.git/commitdiff
[ipv6] Replace IPv6 stack
authorMichael Brown <mcb30@ipxe.org>
Mon, 26 Aug 2013 13:23:54 +0000 (14:23 +0100)
committerMichael Brown <mcb30@ipxe.org>
Tue, 3 Sep 2013 15:30:46 +0000 (16:30 +0100)
Replace the existing partially-implemented IPv6 stack with a fresh
implementation.

This implementation is not yet complete.  The IPv6 transmit and
receive datapaths are functional (including fragment reassembly and
parsing of arbitrary extension headers).  NDP neighbour solicitations
and advertisements are supported.  ICMPv6 echo is supported.

At present, only link-local addresses may be used, and there is no way
to specify an IPv6 address as part of a URI (either directly or via
a DNS lookup).

Signed-off-by: Michael Brown <mcb30@ipxe.org>
16 files changed:
src/config/config.c
src/config/config_route.c
src/config/general.h
src/include/ipxe/icmp6.h [deleted file]
src/include/ipxe/icmpv6.h [new file with mode: 0644]
src/include/ipxe/in.h
src/include/ipxe/ip6.h [deleted file]
src/include/ipxe/ipv6.h [new file with mode: 0644]
src/include/ipxe/ndp.h
src/net/icmpv6.c
src/net/ipv4.c
src/net/ipv6.c
src/net/ndp.c
src/tests/ipv6_test.c [new file with mode: 0644]
src/tests/tests.c
src/usr/route_ipv6.c [new file with mode: 0644]

index f063523c778e7a7ccbc7410780b3b122ad914841..6596e951cf34b6ebd47d4a2a261f20b9ab5762ad 100644 (file)
@@ -101,6 +101,9 @@ REQUIRE_OBJECT ( debugcon );
 #ifdef NET_PROTO_IPV4
 REQUIRE_OBJECT ( ipv4 );
 #endif
+#ifdef NET_PROTO_IPV6
+REQUIRE_OBJECT ( ipv6 );
+#endif
 
 /*
  * Drag in all requested PXE support
index c31d2dae0bd887e83cf65ef7c02e9f56d83d51a7..33e18cdd3e939bd2bdb8494fdb6a26f1a6614a5b 100644 (file)
@@ -22,3 +22,6 @@ FILE_LICENCE ( GPL2_OR_LATER );
 #ifdef NET_PROTO_IPV4
 REQUIRE_OBJECT ( route_ipv4 );
 #endif
+#ifdef NET_PROTO_IPV6
+REQUIRE_OBJECT ( route_ipv6 );
+#endif
index ae14ed3da1cf9788404246e1d86feac97778f725..2e93efdeeb7d18fd68dfd1329a4b1d2af1873c49 100644 (file)
@@ -40,6 +40,7 @@ FILE_LICENCE ( GPL2_OR_LATER );
  */
 
 #define        NET_PROTO_IPV4          /* IPv4 protocol */
+#undef NET_PROTO_IPV6          /* IPv6 protocol */
 #undef NET_PROTO_FCOE          /* Fibre Channel over Ethernet protocol */
 
 /*
diff --git a/src/include/ipxe/icmp6.h b/src/include/ipxe/icmp6.h
deleted file mode 100644 (file)
index 1d43340..0000000
+++ /dev/null
@@ -1,59 +0,0 @@
-#ifndef _IPXE_ICMP6_H
-#define _IPXE_ICMP6_H
-
-/** @file
- *
- * ICMP6 protocol
- *
- */
-
-FILE_LICENCE ( GPL2_OR_LATER );
-
-#include <ipxe/ip6.h>
-#include <ipxe/ndp.h>
-
-#define ICMP6_NSOLICIT 135
-#define ICMP6_NADVERT 136
-
-extern struct tcpip_protocol icmp6_protocol __tcpip_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;
-  /* 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
-
-int icmp6_send_solicit ( struct net_device *netdev, struct in6_addr *src, struct in6_addr *dest );
-
-#endif /* _IPXE_ICMP6_H */
diff --git a/src/include/ipxe/icmpv6.h b/src/include/ipxe/icmpv6.h
new file mode 100644 (file)
index 0000000..c8f0be0
--- /dev/null
@@ -0,0 +1,78 @@
+#ifndef _IPXE_ICMP6_H
+#define _IPXE_ICMP6_H
+
+/** @file
+ *
+ * ICMPv6 protocol
+ *
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+#include <stdint.h>
+#include <ipxe/tables.h>
+#include <ipxe/iobuf.h>
+#include <ipxe/netdevice.h>
+
+/** An ICMPv6 header */
+struct icmpv6_header {
+       /** Type */
+       uint8_t type;
+       /** Code */
+       uint8_t code;
+       /** Checksum */
+       uint16_t chksum;
+} __attribute__ (( packed ));
+
+/** An ICMPv6 echo request/reply */
+struct icmpv6_echo {
+       /** ICMPv6 header */
+       struct icmpv6_header icmp;
+       /** Identifier */
+       uint16_t ident;
+       /** Sequence number */
+       uint16_t sequence;
+       /** Data */
+       uint8_t data[0];
+} __attribute__ (( packed ));
+
+/** An ICMPv6 handler */
+struct icmpv6_handler {
+       /** Type */
+       unsigned int type;
+       /** Process received packet
+        *
+        * @v iobuf             I/O buffer
+        * @v netdev            Network device
+        * @v sin6_src          Source socket address
+        * @v sin6_dest         Destination socket address
+        * @ret rc              Return status code
+        *
+        * This function takes ownership of the I/O buffer.
+        */
+       int ( * rx ) ( struct io_buffer *iobuf, struct net_device *netdev,
+                      struct sockaddr_in6 *sin6_src,
+                      struct sockaddr_in6 *sin6_dest );
+};
+
+/** ICMPv6 handler table */
+#define ICMPV6_HANDLERS __table ( struct icmpv6_handler, "icmpv6_handlers" )
+
+/** Declare an ICMPv6 handler */
+#define __icmpv6_handler __table_entry ( ICMPV6_HANDLERS, 01 )
+
+/** ICMPv6 echo request */
+#define ICMPV6_ECHO_REQUEST 128
+
+/** ICMPv6 echo reply */
+#define ICMPV6_ECHO_REPLY 129
+
+/** ICMPv6 neighbour solicitation */
+#define ICMPV6_NDP_NEIGHBOUR_SOLICITATION 135
+
+/** ICMPv6 neighbour advertisement */
+#define ICMPV6_NDP_NEIGHBOUR_ADVERTISEMENT 136
+
+extern struct tcpip_protocol icmpv6_protocol __tcpip_protocol;
+
+#endif /* _IPXE_ICMP6_H */
index eee9159fb6fc7e531faacc7a9c5af5ef746efa72..a1821b1fbe35192c1af0f9bd370870739cf0e502 100644 (file)
@@ -50,6 +50,13 @@ struct in6_addr {
 #define s6_addr32       in6_u.u6_addr32
 };
 
+#define IN6_IS_ADDR_MULTICAST( addr )                                  \
+       ( *( ( const uint8_t * ) (addr) ) == 0xff )
+
+#define IN6_IS_ADDR_LINKLOCAL( addr )                                  \
+       ( ( *( ( const uint16_t * ) (addr) ) & htons ( 0xffc0 ) ) ==    \
+         htonl ( 0xfe80 ) )
+
 /**
  * IPv4 socket address
  */
@@ -90,9 +97,13 @@ struct sockaddr_in6 {
        uint16_t sin6_flags;
        /** TCP/IP port (part of struct @c sockaddr_tcpip) */
        uint16_t sin6_port;
-        uint32_t        sin6_flowinfo;  /* Flow number */
-        struct in6_addr sin6_addr;      /* 128-bit destination address */
-        uint32_t        sin6_scope_id;  /* Scope ID */
+       /** Scope ID
+        *
+        * For link-local addresses, this is the network device index.
+        */
+        uint16_t sin6_scope_id;
+       /** IPv6 address */
+        struct in6_addr sin6_addr;
        /** Padding
         *
         * This ensures that a struct @c sockaddr_in6 is large
@@ -103,20 +114,12 @@ struct sockaddr_in6 {
                  ( sizeof ( sa_family_t ) /* sin6_family */ +
                    sizeof ( uint16_t ) /* sin6_flags */ +
                    sizeof ( uint16_t ) /* sin6_port */ +
-                   sizeof ( uint32_t ) /* sin6_flowinfo */ +
-                   sizeof ( struct in6_addr ) /* sin6_addr */ +
-                   sizeof ( uint32_t ) /* sin6_scope_id */ ) ];
+                   sizeof ( uint16_t ) /* sin6_scope_id */ +
+                   sizeof ( struct in6_addr ) /* sin6_addr */ ) ];
 } __attribute__ (( may_alias ));
 
 extern int inet_aton ( const char *cp, struct in_addr *inp );
 extern char * inet_ntoa ( struct in_addr in );
-
-/* Adding the following for IP6 support
- *
-
-extern int inet6_aton ( const char *cp, struct in6_addr *inp );
-extern char * inet6_ntoa ( struct in_addr in );
-
- */
+extern char * inet6_ntoa ( const struct in6_addr *in6 );
 
 #endif /* _IPXE_IN_H */
diff --git a/src/include/ipxe/ip6.h b/src/include/ipxe/ip6.h
deleted file mode 100644 (file)
index e9584bd..0000000
+++ /dev/null
@@ -1,80 +0,0 @@
-#ifndef _IPXE_IP6_H
-#define _IPXE_IP6_H
-
-/** @file
- *
- * IP6 protocol
- *
- */
-
-FILE_LICENCE ( GPL2_OR_LATER );
-
-#include <stdint.h>
-#include <ipxe/in.h>
-#include <ipxe/netdevice.h>
-#include <ipxe/tcpip.h>
-
-/* IP6 constants */
-
-#define IP6_VERSION    0x6
-#define IP6_HOP_LIMIT  255
-
-/**
- * I/O buffer contents
- * This is duplicated in tcp.h and here. Ideally it should go into iobuf.h
- */
-#define MAX_HDR_LEN    100
-#define MAX_IOB_LEN    1500
-#define MIN_IOB_LEN    MAX_HDR_LEN + 100 /* To account for padding by LL */
-
-#define IP6_EQUAL( in6_addr1, in6_addr2 ) \
-        ( memcmp ( ( 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        ver_traffic_class_flow_label;
-       uint16_t        payload_len;
-       uint8_t         nxt_hdr;
-       uint8_t         hop_limit;
-       struct in6_addr src;
-       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 io_buffer;
-
-extern struct net_protocol ipv6_protocol __net_protocol;
-extern struct tcpip_net_protocol ipv6_tcpip_protocol __tcpip_net_protocol;
-extern char * inet6_ntoa ( struct in6_addr in6 );
-
-extern int add_ipv6_address ( struct net_device *netdev,
-                             struct in6_addr prefix, int prefix_len,
-                             struct in6_addr address,
-                             struct in6_addr gateway );
-extern void del_ipv6_address ( struct net_device *netdev );
-
-#endif /* _IPXE_IP6_H */
diff --git a/src/include/ipxe/ipv6.h b/src/include/ipxe/ipv6.h
new file mode 100644 (file)
index 0000000..f404ba6
--- /dev/null
@@ -0,0 +1,218 @@
+#ifndef _IPXE_IPV6_H
+#define _IPXE_IPV6_H
+
+/** @file
+ *
+ * IPv6 protocol
+ *
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+#include <stdint.h>
+#include <string.h>
+#include <byteswap.h>
+#include <ipxe/in.h>
+#include <ipxe/list.h>
+#include <ipxe/netdevice.h>
+
+/** IPv6 version */
+#define IPV6_VER 0x60000000UL
+
+/** IPv6 version mask */
+#define IPV6_MASK_VER 0xf0000000UL
+
+/** IPv6 maximum hop limit */
+#define IPV6_HOP_LIMIT 0xff
+
+/** IPv6 header */
+struct ipv6_header {
+       /** Version (4 bits), Traffic class (8 bits), Flow label (20 bits) */
+       uint32_t ver_tc_label;
+       /** Payload length, including any extension headers */
+       uint16_t len;
+       /** Next header type */
+       uint8_t next_header;
+       /** Hop limit */
+       uint8_t hop_limit;
+       /** Source address */
+       struct in6_addr src;
+       /** Destination address */
+       struct in6_addr dest;
+} __attribute__ (( packed ));
+
+/** IPv6 extension header common fields */
+struct ipv6_extension_header_common {
+       /** Next header type */
+       uint8_t next_header;
+       /** Header extension length (excluding first 8 bytes) */
+       uint8_t len;
+} __attribute__ (( packed ));
+
+/** IPv6 type-length-value options */
+struct ipv6_option {
+       /** Type */
+       uint8_t type;
+       /** Length */
+       uint8_t len;
+       /** Value */
+       uint8_t value[0];
+} __attribute__ (( packed ));
+
+/** IPv6 option types */
+enum ipv6_option_type {
+       /** Pad1 */
+       IPV6_OPT_PAD1 = 0x00,
+       /** PadN */
+       IPV6_OPT_PADN = 0x01,
+};
+
+/** Test if IPv6 option can be safely ignored */
+#define IPV6_CAN_IGNORE_OPT( type ) ( ( (type) & 0xc0 ) == 0x00 )
+
+/** IPv6 option-based extension header */
+struct ipv6_options_header {
+       /** Extension header common fields */
+       struct ipv6_extension_header_common common;
+       /** Options */
+       struct ipv6_option options[0];
+} __attribute__ (( packed ));
+
+/** IPv6 routing header */
+struct ipv6_routing_header {
+       /** Extension header common fields */
+       struct ipv6_extension_header_common common;
+       /** Routing type */
+       uint8_t type;
+       /** Segments left */
+       uint8_t remaining;
+       /** Type-specific data */
+       uint8_t data[0];
+} __attribute__ (( packed ));
+
+/** IPv6 fragment header */
+struct ipv6_fragment_header {
+       /** Extension header common fields */
+       struct ipv6_extension_header_common common;
+       /** Fragment offset (13 bits), reserved, more fragments (1 bit) */
+       uint16_t offset_more;
+       /** Identification */
+       uint32_t ident;
+} __attribute__ (( packed ));
+
+/** Fragment offset mask */
+#define IPV6_MASK_OFFSET 0xfff8
+
+/** More fragments */
+#define IPV6_MASK_MOREFRAGS 0x0001
+
+/** IPv6 extension header */
+union ipv6_extension_header {
+       /** Extension header common fields */
+       struct ipv6_extension_header_common common;
+       /** Minimum size padding */
+       uint8_t pad[8];
+       /** Generic options header */
+       struct ipv6_options_header options;
+       /** Hop-by-hop options header */
+       struct ipv6_options_header hopbyhop;
+       /** Routing header */
+       struct ipv6_routing_header routing;
+       /** Fragment header */
+       struct ipv6_fragment_header fragment;
+       /** Destination options header */
+       struct ipv6_options_header destination;
+};
+
+/** IPv6 header types */
+enum ipv6_header_type {
+       /** IPv6 hop-by-hop options header type */
+       IPV6_HOPBYHOP = 0,
+       /** IPv6 routing header type */
+       IPV6_ROUTING = 43,
+       /** IPv6 fragment header type */
+       IPV6_FRAGMENT = 44,
+       /** IPv6 no next header type */
+       IPV6_NO_HEADER = 59,
+       /** IPv6 destination options header type */
+       IPV6_DESTINATION = 60,
+};
+
+/** IPv6 pseudo-header */
+struct ipv6_pseudo_header {
+       /** Source address */
+       struct in6_addr src;
+       /** Destination address */
+       struct in6_addr dest;
+       /** Upper-layer packet length */
+       uint32_t len;
+       /** Zero padding */
+       uint8_t zero[3];
+       /** Next header */
+       uint8_t next_header;
+} __attribute__ (( packed ));
+
+/** An IPv6 address/routing table entry */
+struct ipv6_miniroute {
+       /** List of miniroutes */
+       struct list_head list;
+
+       /** Network device */
+       struct net_device *netdev;
+
+       /** IPv6 address */
+       struct in6_addr address;
+       /** Prefix length */
+       unsigned int prefix_len;
+       /** IPv6 prefix mask (derived from prefix length) */
+       struct in6_addr prefix_mask;
+       /** Router address is present */
+       int has_router;
+       /** Router address */
+       struct in6_addr router;
+};
+
+/**
+ * Construct link-local address (via EUI-64)
+ *
+ * @v addr             Address to construct
+ * @v netdev           Network device
+ * @ret prefix_len     Prefix length, or negative error
+ */
+static inline int ipv6_link_local ( struct in6_addr *addr,
+                                   struct net_device *netdev ) {
+       struct ll_protocol *ll_protocol = netdev->ll_protocol;
+       const void *ll_addr = netdev->ll_addr;
+       int rc;
+
+       memset ( addr, 0, sizeof ( *addr ) );
+       addr->s6_addr16[0] = htons ( 0xfe80 );
+       if ( ( rc = ll_protocol->eui64 ( ll_addr, &addr->s6_addr[8] ) ) != 0 )
+               return rc;
+       addr->s6_addr[8] ^= 0x02;
+       return 64;
+}
+
+/**
+ * Construct solicited-node multicast address
+ *
+ * @v addr             Address to construct
+ * @v unicast          Unicast address
+ */
+static inline void ipv6_solicited_node ( struct in6_addr *addr,
+                                        const struct in6_addr *unicast ) {
+
+       memset ( addr, 0, sizeof ( *addr ) );
+       addr->s6_addr16[0] = htons ( 0xff02 );
+       addr->s6_addr[11] = 1;
+       addr->s6_addr[12] = 0xff;
+       memcpy ( &addr->s6_addr[13], &unicast->s6_addr[13], 3 );
+}
+
+extern struct list_head ipv6_miniroutes;
+
+extern struct net_protocol ipv6_protocol __net_protocol;
+
+extern int ipv6_has_addr ( struct net_device *netdev, struct in6_addr *addr );
+
+#endif /* _IPXE_IPV6_H */
index 42bb2fe006b5c60ae935f151f9cfbd0f7ae36b29..7b98637f826a242fe9210f554536227ad848c60a 100644 (file)
@@ -1,21 +1,80 @@
+#ifndef _IPXE_NDP_H
+#define _IPXE_NDP_H
+
+/** @file
+ *
+ * Neighbour discovery protocol
+ *
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
 #include <stdint.h>
-#include <byteswap.h>
-#include <string.h>
-#include <ipxe/icmp6.h>
-#include <ipxe/ip6.h>
 #include <ipxe/in.h>
-#include <ipxe/netdevice.h>
-#include <ipxe/iobuf.h>
-#include <ipxe/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
-
-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 );
+#include <ipxe/ipv6.h>
+#include <ipxe/icmpv6.h>
+#include <ipxe/neighbour.h>
+
+/** An NDP option */
+struct ndp_option {
+       /** Type */
+       uint8_t type;
+       /** Length (in blocks of 8 bytes) */
+       uint8_t blocks;
+       /** Value */
+       uint8_t value[0];
+} __attribute__ (( packed ));
+
+/** NDP option block size */
+#define NDP_OPTION_BLKSZ 8
+
+/** An NDP header */
+struct ndp_header {
+       /** ICMPv6 header */
+       struct icmpv6_header icmp;
+       /** Flags */
+       uint8_t flags;
+       /** Reserved */
+       uint8_t reserved[3];
+       /** Target address */
+       struct in6_addr target;
+       /** Options */
+       struct ndp_option option[0];
+} __attribute__ (( packed ));
+
+/** NDP router flag */
+#define NDP_ROUTER 0x80
+
+/** NDP solicited flag */
+#define NDP_SOLICITED 0x40
+
+/** NDP override flag */
+#define NDP_OVERRIDE 0x20
+
+/** NDP source link-layer address option */
+#define NDP_OPT_LL_SOURCE 1
+
+/** NDP target link-layer address option */
+#define NDP_OPT_LL_TARGET 2
+
+extern struct neighbour_discovery ndp_discovery;
+
+/**
+ * Transmit packet, determining link-layer address via NDP
+ *
+ * @v iobuf            I/O buffer
+ * @v netdev           Network device
+ * @v net_dest         Destination network-layer address
+ * @v net_source       Source network-layer address
+ * @v ll_source                Source link-layer address
+ * @ret rc             Return status code
+ */
+static inline int ndp_tx ( struct io_buffer *iobuf, struct net_device *netdev,
+                          const void *net_dest, const void *net_source,
+                          const void *ll_source ) {
+
+       return neighbour_tx ( iobuf, netdev, &ipv6_protocol, net_dest,
+                             &ndp_discovery, net_source, ll_source );
+}
+
+#endif /* _IPXE_NDP_H */
index 7242380627b6f4c452d2ee5e2b71dece37b56bf1..54426be8ad2cf8d91631b6f8293346ae6386eb20 100644 (file)
-#include <stdint.h>
+/*
+ * Copyright (C) 2013 Michael Brown <mbrown@fensystems.co.uk>.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
 #include <string.h>
-#include <byteswap.h>
 #include <errno.h>
+#include <byteswap.h>
 #include <ipxe/in.h>
-#include <ipxe/ip6.h>
-#include <ipxe/if_ether.h>
 #include <ipxe/iobuf.h>
-#include <ipxe/ndp.h>
-#include <ipxe/icmp6.h>
 #include <ipxe/tcpip.h>
-#include <ipxe/netdevice.h>
+#include <ipxe/icmpv6.h>
+
+/** @file
+ *
+ * ICMPv6 protocol
+ *
+ */
 
 /**
- * Send neighbour solicitation packet
+ * Process received ICMPv6 echo request packet
  *
- * @v netdev   Network device
- * @v src      Source address
- * @v dest     Destination address
+ * @v iobuf            I/O buffer
+ * @v netdev           Network device
+ * @v sin6_src         Source socket address
+ * @v sin6_dest                Destination socket address
+ * @ret rc             Return status code
+ */
+static int icmpv6_rx_echo ( struct io_buffer *iobuf,
+                           struct net_device *netdev,
+                           struct sockaddr_in6 *sin6_src,
+                           struct sockaddr_in6 *sin6_dest __unused ) {
+       struct sockaddr_tcpip *st_src =
+               ( ( struct sockaddr_tcpip * ) sin6_src );
+       struct icmpv6_echo *echo = iobuf->data;
+       size_t len = iob_len ( iobuf );
+       int rc;
+
+       /* Sanity check */
+       if ( iob_len ( iobuf ) < sizeof ( *echo ) ) {
+               DBGC ( netdev, "ICMPv6 echo request too short at %zd bytes "
+                      "(min %zd bytes)\n", iob_len ( iobuf ),
+                      sizeof ( *echo ) );
+               rc = -EINVAL;
+               goto done;
+       }
+       DBGC ( netdev, "ICMPv6 echo request from %s (id %#04x seq %#04x)\n",
+              inet6_ntoa ( &sin6_dest->sin6_addr ), ntohs ( echo->ident ),
+              ntohs ( echo->sequence ) );
+
+       /* Convert echo request to echo reply and recalculate checksum */
+       echo->icmp.type = ICMPV6_ECHO_REPLY;
+       echo->icmp.chksum = 0;
+       echo->icmp.chksum = tcpip_chksum ( echo, len );
+
+       /* Transmit echo reply */
+       if ( ( rc = tcpip_tx ( iob_disown ( iobuf ), &icmpv6_protocol, NULL,
+                              st_src, netdev, &echo->icmp.chksum ) ) != 0 ) {
+               DBGC ( netdev, "ICMPv6 could not transmit reply: %s\n",
+                      strerror ( rc ) );
+               goto done;
+       }
+
+ done:
+       free_iob ( iobuf );
+       return rc;
+}
+
+/** ICMPv6 echo request handlers */
+struct icmpv6_handler icmpv6_echo_handler __icmpv6_handler = {
+       .type = ICMPV6_ECHO_REQUEST,
+       .rx = icmpv6_rx_echo,
+};
+
+/**
+ * Identify ICMPv6 handler
  *
- * This function prepares a neighbour solicitation packet and sends it to the
- * network layer.
+ * @v type             ICMPv6 type
+ * @ret handler                ICMPv6 handler, or NULL if not found
  */
-int icmp6_send_solicit ( struct net_device *netdev, struct in6_addr *src __unused,
-                        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 io_buffer *iobuf = alloc_iob ( sizeof ( *nsolicit ) + MIN_IOB_LEN );
-       iob_reserve ( iobuf, MAX_HDR_LEN );
-       nsolicit = iob_put ( iobuf, 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.sin6_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 */
-       return tcpip_tx ( iobuf, &icmp6_protocol, NULL, &st_dest.st,
-                         NULL, &nsolicit->csum );
+static struct icmpv6_handler * icmpv6_handler ( unsigned int type ) {
+       struct icmpv6_handler *handler;
+
+       for_each_table_entry ( handler, ICMPV6_HANDLERS ) {
+               if ( handler->type == type )
+                       return handler;
+       }
+       return NULL;
 }
 
 /**
- * Process ICMP6 headers
+ * Process a received packet
  *
- * @v iobuf    I/O buffer
- * @v st_src   Source address
- * @v st_dest  Destination address
+ * @v iobuf            I/O buffer
+ * @v netdev           Network device
+ * @v st_src           Partially-filled source address
+ * @v st_dest          Partially-filled destination address
+ * @v pshdr_csum       Pseudo-header checksum
+ * @ret rc             Return status code
  */
-static int icmp6_rx ( struct io_buffer *iobuf, struct net_device *netdev __unused, struct sockaddr_tcpip *st_src,
-                     struct sockaddr_tcpip *st_dest, __unused uint16_t pshdr_csum ) {
-       struct icmp6_header *icmp6hdr = iobuf->data;
+static int icmpv6_rx ( struct io_buffer *iobuf, struct net_device *netdev,
+                      struct sockaddr_tcpip *st_src,
+                      struct sockaddr_tcpip *st_dest, uint16_t pshdr_csum ) {
+       struct sockaddr_in6 *sin6_src = ( ( struct sockaddr_in6 * ) st_src );
+       struct sockaddr_in6 *sin6_dest = ( ( struct sockaddr_in6 * ) st_dest );
+       struct icmpv6_header *icmp = iobuf->data;
+       size_t len = iob_len ( iobuf );
+       struct icmpv6_handler *handler;
+       unsigned int csum;
+       int rc;
 
        /* Sanity check */
-       if ( iob_len ( iobuf ) < sizeof ( *icmp6hdr ) ) {
-               DBG ( "Packet too short (%zd bytes)\n", iob_len ( iobuf ) );
-               free_iob ( iobuf );
-               return -EINVAL;
+       if ( len < sizeof ( *icmp ) ) {
+               DBGC ( netdev, "ICMPv6 packet too short at %zd bytes (min %zd "
+                      "bytes)\n", len, sizeof ( *icmp ) );
+               rc = -EINVAL;
+               goto done;
        }
 
-       /* TODO: Verify checksum */
+       /* Verify checksum */
+       csum = tcpip_continue_chksum ( pshdr_csum, icmp, len );
+       if ( csum != 0 ) {
+               DBGC ( netdev, "ICMPv6 checksum incorrect (is %04x, should be "
+                      "0000)\n", csum );
+               DBGC_HDA ( netdev, 0, icmp, len );
+               rc = -EINVAL;
+               goto done;
+       }
 
-       /* Process the ICMP header */
-       switch ( icmp6hdr->type ) {
-       case ICMP6_NADVERT:
-               return ndp_process_advert ( iobuf, st_src, st_dest );
+       /* Identify handler */
+       handler = icmpv6_handler ( icmp->type );
+       if ( ! handler ) {
+               DBGC ( netdev, "ICMPv6 unrecognised type %d\n", icmp->type );
+               rc = -ENOTSUP;
+               goto done;
+       }
+
+       /* Pass to handler */
+       if ( ( rc = handler->rx ( iob_disown ( iobuf ), netdev, sin6_src,
+                                 sin6_dest ) ) != 0 ) {
+               DBGC ( netdev, "ICMPv6 could not handle type %d: %s\n",
+                      icmp->type, strerror ( rc ) );
+               goto done;
        }
-       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 io_buffer *rxiobuf = alloc_iob ( 500 );
-                iob_reserve ( rxiobuf, MAX_HDR_LEN );
-                struct neighbour_advert *nadvert = iob_put ( rxiobuf, 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 = iob_push ( rxiobuf, 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 ( rxiobuf->data, iob_len ( rxiobuf ) );
-                net_rx ( rxiobuf, netdev, htons ( ETH_P_IPV6 ), ll_addr );
+ done:
+       free_iob ( iobuf );
+       return rc;
 }
-#endif
 
-/** ICMP6 protocol */
-struct tcpip_protocol icmp6_protocol __tcpip_protocol = {
-       .name = "ICMP6",
-       .rx = icmp6_rx,
-       .tcpip_proto = IP_ICMP6, // 58
+/** ICMPv6 TCP/IP protocol */
+struct tcpip_protocol icmpv6_protocol __tcpip_protocol = {
+       .name = "ICMPv6",
+       .rx = icmpv6_rx,
+       .tcpip_proto = IP_ICMP6,
 };
index bd318806234359af434916a42d318bb70831bd9e..46f774e9544079fc8fcf2f6880954c9cdb59f122 100644 (file)
@@ -290,7 +290,7 @@ static int ipv4_tx ( struct io_buffer *iobuf,
                        DBGC ( sin_dest->sin_addr, "IPv4 could not hash "
                               "multicast %s: %s\n",
                               inet_ntoa ( next_hop ), strerror ( rc ) );
-                       return rc;
+                       goto err;
                }
                ll_dest = ll_dest_buf;
        } else {
index 077118df691f842cbf867e0f5177f6f0f3a027fa..69feba193cd848df7583c22989a6c77ebfc76dd5 100644 (file)
-#include <errno.h>
+/*
+ * Copyright (C) 2013 Michael Brown <mbrown@fensystems.co.uk>.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
 #include <stdint.h>
-#include <string.h>
-#include <stdlib.h>
 #include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <assert.h>
 #include <byteswap.h>
-#include <ipxe/in.h>
-#include <ipxe/ip6.h>
-#include <ipxe/ndp.h>
-#include <ipxe/list.h>
-#include <ipxe/icmp6.h>
-#include <ipxe/tcpip.h>
-#include <ipxe/socket.h>
 #include <ipxe/iobuf.h>
-#include <ipxe/netdevice.h>
+#include <ipxe/tcpip.h>
 #include <ipxe/if_ether.h>
+#include <ipxe/crc32.h>
+#include <ipxe/fragment.h>
+#include <ipxe/ndp.h>
+#include <ipxe/ipv6.h>
 
-/* Unspecified IP6 address */
-static struct in6_addr ip6_none = {
-        .in6_u.u6_addr32 = { 0,0,0,0 }
-};
+/** @file
+ *
+ * IPv6 protocol
+ *
+ */
 
-/** An IPv6 routing table entry */
-struct ipv6_miniroute {
-       /* List of miniroutes */
-       struct list_head list;
+/* Disambiguate the various error causes */
+#define EINVAL_LEN __einfo_error ( EINFO_EINVAL_LEN )
+#define EINFO_EINVAL_LEN \
+       __einfo_uniqify ( EINFO_EINVAL, 0x01, "Invalid length" )
+#define ENOTSUP_VER __einfo_error ( EINFO_ENOTSUP_VER )
+#define EINFO_ENOTSUP_VER \
+       __einfo_uniqify ( EINFO_ENOTSUP, 0x01, "Unsupported version" )
+#define ENOTSUP_HDR __einfo_error ( EINFO_ENOTSUP_HDR )
+#define EINFO_ENOTSUP_HDR \
+       __einfo_uniqify ( EINFO_ENOTSUP, 0x02, "Unsupported header type" )
+#define ENOTSUP_OPT __einfo_error ( EINFO_ENOTSUP_OPT )
+#define EINFO_ENOTSUP_OPT \
+       __einfo_uniqify ( EINFO_ENOTSUP, 0x03, "Unsupported option" )
 
-       /* Network device */
-       struct net_device *netdev;
+/** List of IPv6 miniroutes */
+struct list_head ipv6_miniroutes = LIST_HEAD_INIT ( ipv6_miniroutes );
 
-       /* 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;
-};
+/**
+ * Determine debugging colour for IPv6 debug messages
+ *
+ * @v in               IPv6 address
+ * @ret col            Debugging colour (for DBGC())
+ */
+static uint32_t ipv6col ( struct in6_addr *in ) {
+       return crc32_le ( 0, in, sizeof ( *in ) );
+}
 
-/** List of IPv6 miniroutes */
-static LIST_HEAD ( miniroutes );
+/**
+ * Check if network device has a specific IPv6 address
+ *
+ * @v netdev           Network device
+ * @v addr             IPv6 address
+ * @ret has_addr       Network device has this IPv6 address
+ */
+int ipv6_has_addr ( struct net_device *netdev, struct in6_addr *addr ) {
+       struct ipv6_miniroute *miniroute;
+
+       list_for_each_entry ( miniroute, &ipv6_miniroutes, list ) {
+               if ( ( miniroute->netdev == netdev ) &&
+                    ( memcmp ( &miniroute->address, addr,
+                               sizeof ( miniroute->address ) ) == 0 ) ) {
+                       /* Found matching address */
+                       return 1;
+               }
+       }
+       return 0;
+}
+
+/**
+ * Check if IPv6 address is within a routing table entry's local network
+ *
+ * @v miniroute                Routing table entry
+ * @v address          IPv6 address
+ * @ret is_local       Address is within this entry's local network
+ */
+static int ipv6_is_local ( struct ipv6_miniroute *miniroute,
+                          struct in6_addr *address ) {
+       unsigned int i;
+
+       for ( i = 0 ; i < ( sizeof ( address->s6_addr32 ) /
+                           sizeof ( address->s6_addr32[0] ) ) ; i++ ) {
+               if ( (( address->s6_addr32[i] ^ miniroute->address.s6_addr32[i])
+                     & miniroute->prefix_mask.s6_addr32[i] ) != 0 )
+                       return 0;
+       }
+       return 1;
+}
 
 /**
  * Add IPv6 minirouting table entry
  *
  * @v netdev           Network device
- * @v prefix           Destination prefix
- * @v address          Address of the interface
- * @v gateway          Gateway address (or ::0 for no gateway)
- * @ret miniroute      Routing table entry, or NULL
+ * @v address          IPv6 address
+ * @v prefix_len       Prefix length
+ * @v router           Router address (or NULL)
+ * @ret miniroute      Routing table entry, or NULL on failure
  */
-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 ) {
+static struct ipv6_miniroute * __malloc
+add_ipv6_miniroute ( struct net_device *netdev, struct in6_addr *address,
+                    unsigned int prefix_len, struct in6_addr *router ) {
        struct ipv6_miniroute *miniroute;
-       
-       miniroute = malloc ( sizeof ( *miniroute ) );
-       if ( miniroute ) {
-               /* Record routing information */
-               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 );
-               }
+       uint8_t *prefix_mask;
+
+       DBGC ( netdev, "IPv6 add %s/%d ", inet6_ntoa ( address ), prefix_len );
+       if ( router )
+               DBGC ( netdev, "router %s ", inet6_ntoa ( router ) );
+       DBGC ( netdev, "via %s\n", netdev->name );
+
+       /* Allocate and populate miniroute structure */
+       miniroute = zalloc ( sizeof ( *miniroute ) );
+       if ( ! miniroute )
+               return NULL;
+
+       /* Record routing information */
+       miniroute->netdev = netdev_get ( netdev );
+       memcpy ( &miniroute->address, address, sizeof ( miniroute->address ) );
+       miniroute->prefix_len = prefix_len;
+       assert ( prefix_len <= ( 8 * sizeof ( miniroute->prefix_mask ) ) );
+       for ( prefix_mask = miniroute->prefix_mask.s6_addr ; prefix_len >= 8 ;
+             prefix_mask++, prefix_len -= 8 ) {
+               *prefix_mask = 0xff;
+       }
+       if ( prefix_len )
+               *prefix_mask <<= ( 8 - prefix_len );
+       if ( router ) {
+               miniroute->has_router = 1;
+               memcpy ( &miniroute->router, router,
+                        sizeof ( miniroute->router ) );
+       }
+
+       /* Add to end of list if we have a gateway, otherwise to start
+        * of list.
+        */
+       if ( router ) {
+               list_add_tail ( &miniroute->list, &ipv6_miniroutes );
+       } else {
+               list_add ( &miniroute->list, &ipv6_miniroutes );
        }
 
        return miniroute;
@@ -82,290 +168,516 @@ add_ipv6_miniroute ( struct net_device *netdev, struct in6_addr prefix,
  * @v miniroute                Routing table entry
  */
 static void del_ipv6_miniroute ( struct ipv6_miniroute *miniroute ) {
+       struct net_device *netdev = miniroute->netdev;
+
+       DBGC ( netdev, "IPv6 del %s/%d ", inet6_ntoa ( &miniroute->address ),
+              miniroute->prefix_len );
+       if ( miniroute->has_router )
+               DBGC ( netdev, "router %s ", inet6_ntoa ( &miniroute->router ));
+       DBGC ( netdev, "via %s\n", netdev->name );
+
        netdev_put ( miniroute->netdev );
        list_del ( &miniroute->list );
        free ( miniroute );
 }
 
 /**
- * Add IPv6 interface
+ * Perform IPv6 routing
  *
- * @v netdev   Network device
- * @v prefix   Destination prefix
- * @v address  Address of the interface
- * @v gateway  Gateway address (or ::0 for no gateway)
+ * @v scope_id         Destination address scope ID (for link-local addresses)
+ * @v dest             Final destination address
+ * @ret dest           Next hop destination address
+ * @ret miniroute      Routing table entry to use, or NULL if no route
  */
-int add_ipv6_address ( struct net_device *netdev, struct in6_addr prefix,
-                      int prefix_len, struct in6_addr address,
-                      struct in6_addr gateway ) {
+static struct ipv6_miniroute * ipv6_route ( unsigned int scope_id,
+                                          struct in6_addr **dest ) {
        struct ipv6_miniroute *miniroute;
+       int local;
+
+       /* Find first usable route in routing table */
+       list_for_each_entry ( miniroute, &ipv6_miniroutes, list ) {
+
+               /* Skip closed network devices */
+               if ( ! netdev_is_open ( miniroute->netdev ) )
+                       continue;
+
+               /* For link-local addresses, skip devices that are not
+                * the specified network device.
+                */
+               if ( IN6_IS_ADDR_LINKLOCAL ( *dest ) &&
+                    ( miniroute->netdev->index != scope_id ) )
+                       continue;
+
+               /* Skip non-gateway devices for which the prefix does
+                * not match.
+                */
+               local = ipv6_is_local ( miniroute, *dest );
+               if ( ! ( local || miniroute->has_router ) )
+                       continue;
+
+               /* Update next hop if applicable */
+               if ( ! local )
+                       *dest = &miniroute->router;
+
+               return miniroute;
+       }
 
-       /* Clear any existing address for this net device */
-       del_ipv6_address ( netdev );
-
-       /* Add new miniroute */
-       miniroute = add_ipv6_miniroute ( netdev, prefix, prefix_len, address,
-                                        gateway );
-       if ( ! miniroute )
-               return -ENOMEM;
-
-       return 0;
+       return NULL;
 }
 
 /**
- * Remove IPv6 interface
+ * Check that received options can be safely ignored
  *
- * @v netdev   Network device
+ * @v iphdr            IPv6 header
+ * @v options          Options extension header
+ * @v len              Maximum length of header
+ * @ret rc             Return status code
  */
-void del_ipv6_address ( struct net_device *netdev ) {
-       struct ipv6_miniroute *miniroute;
-
-       list_for_each_entry ( miniroute, &miniroutes, list ) {
-               if ( miniroute->netdev == netdev ) {
-                       del_ipv6_miniroute ( miniroute );
-                       break;
+static int ipv6_check_options ( struct ipv6_header *iphdr,
+                               struct ipv6_options_header *options,
+                               size_t len ) {
+       struct ipv6_option *option = options->options;
+       struct ipv6_option *end = ( ( ( void * ) options ) + len );
+
+       while ( option < end ) {
+               if ( ! IPV6_CAN_IGNORE_OPT ( option->type ) ) {
+                       DBGC ( ipv6col ( &iphdr->src ), "IPv6 unrecognised "
+                              "option type %#02x:\n", option->type );
+                       DBGC_HDA ( ipv6col ( &iphdr->src ), 0,
+                                  options, len );
+                       return -ENOTSUP_OPT;
+               }
+               if ( option->type == IPV6_OPT_PAD1 ) {
+                       option = ( ( ( void * ) option ) + 1 );
+               } else {
+                       option = ( ( ( void * ) option ) + option->len );
                }
        }
+       return 0;
 }
 
 /**
- * Calculate TCPIP checksum
+ * Check if fragment matches fragment reassembly buffer
  *
- * @v iobuf    I/O buffer
- * @v tcpip    TCP/IP protocol
+ * @v fragment         Fragment reassembly buffer
+ * @v iobuf            I/O buffer
+ * @v hdrlen           Length of non-fragmentable potion of I/O buffer
+ * @ret is_fragment    Fragment matches this reassembly buffer
+ */
+static int ipv6_is_fragment ( struct fragment *fragment,
+                             struct io_buffer *iobuf, size_t hdrlen ) {
+       struct ipv6_header *frag_iphdr = fragment->iobuf->data;
+       struct ipv6_fragment_header *frag_fhdr =
+               ( fragment->iobuf->data + fragment->hdrlen -
+                 sizeof ( *frag_fhdr ) );
+       struct ipv6_header *iphdr = iobuf->data;
+       struct ipv6_fragment_header *fhdr =
+               ( iobuf->data + hdrlen - sizeof ( *fhdr ) );
+
+       return ( ( memcmp ( &iphdr->src, &frag_iphdr->src,
+                           sizeof ( iphdr->src ) ) == 0 ) &&
+                ( fhdr->ident == frag_fhdr->ident ) );
+}
+
+/**
+ * Get fragment offset
  *
- * This function constructs the pseudo header and completes the checksum in the
- * upper layer header.
+ * @v iobuf            I/O buffer
+ * @v hdrlen           Length of non-fragmentable potion of I/O buffer
+ * @ret offset         Offset
  */
-static uint16_t ipv6_tx_csum ( struct io_buffer *iobuf, uint16_t csum ) {
-       struct ip6_header *ip6hdr = iobuf->data;
-       struct ipv6_pseudo_header pshdr;
+static size_t ipv6_fragment_offset ( struct io_buffer *iobuf, size_t hdrlen ) {
+       struct ipv6_fragment_header *fhdr =
+               ( iobuf->data + hdrlen - sizeof ( *fhdr ) );
 
-       /* Calculate pseudo header */
-       memset ( &pshdr, 0, sizeof ( pshdr ) );
-       pshdr.src = ip6hdr->src;
-       pshdr.dest = ip6hdr->dest;
-       pshdr.len = htons ( iob_len ( iobuf ) - sizeof ( *ip6hdr ) );
-       pshdr.nxt_hdr = ip6hdr->nxt_hdr;
+       return ( ntohs ( fhdr->offset_more ) & IPV6_MASK_OFFSET );
+}
 
-       /* Update checksum value */
-       return tcpip_continue_chksum ( csum, &pshdr, sizeof ( pshdr ) );
+/**
+ * Check if more fragments exist
+ *
+ * @v iobuf            I/O buffer
+ * @v hdrlen           Length of non-fragmentable potion of I/O buffer
+ * @ret more_frags     More fragments exist
+ */
+static int ipv6_more_fragments ( struct io_buffer *iobuf, size_t hdrlen ) {
+       struct ipv6_fragment_header *fhdr =
+               ( iobuf->data + hdrlen - sizeof ( *fhdr ) );
+
+       return ( fhdr->offset_more & htons ( IPV6_MASK_MOREFRAGS ) );
 }
 
+/** Fragment reassembler */
+static struct fragment_reassembler ipv6_reassembler = {
+       .list = LIST_HEAD_INIT ( ipv6_reassembler.list ),
+       .is_fragment = ipv6_is_fragment,
+       .fragment_offset = ipv6_fragment_offset,
+       .more_fragments = ipv6_more_fragments,
+};
+
 /**
- * Dump IP6 header for debugging
+ * Calculate IPv6 pseudo-header checksum
  *
- * ip6hdr      IPv6 header
+ * @v iphdr            IPv6 header
+ * @v len              Payload length
+ * @v next_header      Next header type
+ * @v csum             Existing checksum
+ * @ret csum           Updated checksum
  */
-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 ) );
+static uint16_t ipv6_pshdr_chksum ( struct ipv6_header *iphdr, size_t len,
+                                   int next_header, uint16_t csum ) {
+       struct ipv6_pseudo_header pshdr;
+
+       /* Build pseudo-header */
+       memcpy ( &pshdr.src, &iphdr->src, sizeof ( pshdr.src ) );
+       memcpy ( &pshdr.dest, &iphdr->dest, sizeof ( pshdr.dest ) );
+       pshdr.len = htonl ( len );
+       memset ( pshdr.zero, 0, sizeof ( pshdr.zero ) );
+       pshdr.next_header = next_header;
+
+       /* Update the checksum value */
+       return tcpip_continue_chksum ( csum, &pshdr, sizeof ( pshdr ) );
 }
 
 /**
- * Transmit IP6 packet
+ * Transmit IPv6 packet
  *
- * iobuf               I/O buffer
- * tcpip       TCP/IP protocol
- * st_dest     Destination socket address
+ * @v iobuf            I/O buffer
+ * @v tcpip            Transport-layer protocol
+ * @v st_src           Source network-layer address
+ * @v st_dest          Destination network-layer address
+ * @v netdev           Network device to use if no route found, or NULL
+ * @v trans_csum       Transport-layer checksum to complete, or NULL
+ * @ret rc             Status
  *
- * This function prepends the IPv6 headers to the payload an transmits it.
+ * This function expects a transport-layer segment and prepends the
+ * IPv6 header
  */
 static int ipv6_tx ( struct io_buffer *iobuf,
-                    struct tcpip_protocol *tcpip,
-                    struct sockaddr_tcpip *st_src __unused,
+                    struct tcpip_protocol *tcpip_protocol,
+                    struct sockaddr_tcpip *st_src,
                     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 sockaddr_in6 *sin6_src = ( ( struct sockaddr_in6 * ) st_src );
+       struct sockaddr_in6 *sin6_dest = ( ( struct sockaddr_in6 * ) st_dest );
        struct ipv6_miniroute *miniroute;
+       struct ipv6_header *iphdr;
+       struct in6_addr *next_hop;
        uint8_t ll_dest_buf[MAX_LL_ADDR_LEN];
-       const uint8_t *ll_dest = ll_dest_buf;
+       const void *ll_dest;
+       size_t len;
        int rc;
 
-       /* Construct the IPv6 packet */
-       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 ( 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.
-        */
-       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 ) ) ) {
-                       netdev = miniroute->netdev;
-                       ip6hdr->src = miniroute->address;
-                       if ( ! ( IS_UNSPECIFIED ( miniroute->gateway ) ) ) {
-                               next_hop = miniroute->gateway;
-                       }
-                       break;
-               }
+       /* Fill up the IPv6 header, except source address */
+       len = iob_len ( iobuf );
+       iphdr = iob_push ( iobuf, sizeof ( *iphdr ) );
+       memset ( iphdr, 0, sizeof ( *iphdr ) );
+       iphdr->ver_tc_label = htonl ( IPV6_VER );
+       iphdr->len = htons ( len );
+       iphdr->next_header = tcpip_protocol->tcpip_proto;
+       iphdr->hop_limit = IPV6_HOP_LIMIT;
+       memcpy ( &iphdr->dest, &sin6_dest->sin6_addr, sizeof ( iphdr->dest ) );
+
+       /* Use routing table to identify next hop and transmitting netdev */
+       next_hop = &iphdr->dest;
+       if ( sin6_src ) {
+               memcpy ( &iphdr->src, &sin6_src->sin6_addr,
+                        sizeof ( iphdr->src ) );
+       }
+       if ( ( ! IN6_IS_ADDR_MULTICAST ( next_hop ) ) &&
+            ( ( miniroute = ipv6_route ( ntohl ( sin6_dest->sin6_scope_id ),
+                                         &next_hop ) ) != NULL ) ) {
+               memcpy ( &iphdr->src, &miniroute->address,
+                        sizeof ( iphdr->src ) );
+               netdev = miniroute->netdev;
        }
-       /* No network interface identified */
-       if ( !netdev ) {
-               DBG ( "No route to host %s\n", inet6_ntoa ( ip6hdr->dest ) );
+       if ( ! netdev ) {
+               DBGC ( ipv6col ( &iphdr->dest ), "IPv6 has no route to %s\n",
+                      inet6_ntoa ( &iphdr->dest ) );
                rc = -ENETUNREACH;
                goto err;
        }
 
-       /* Complete the transport layer checksum */
-       if ( 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;
-               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 ) );
+       /* Fix up checksums */
+       if ( trans_csum ) {
+               *trans_csum = ipv6_pshdr_chksum ( iphdr, len,
+                                                 tcpip_protocol->tcpip_proto,
+                                                 *trans_csum );
+       }
+
+       /* Print IPv6 header for debugging */
+       DBGC2 ( ipv6col ( &iphdr->dest ), "IPv6 TX %s->",
+               inet6_ntoa ( &iphdr->src ) );
+       DBGC2 ( ipv6col ( &iphdr->dest ), "%s len %zd next %d\n",
+               inet6_ntoa ( &iphdr->dest ), len, iphdr->next_header );
+
+       /* Calculate link-layer destination address, if possible */
+       if ( IN6_IS_ADDR_MULTICAST ( next_hop ) ) {
+               /* Multicast address */
+               if ( ( rc = netdev->ll_protocol->mc_hash ( AF_INET6, next_hop,
+                                                          ll_dest_buf ) ) !=0){
+                       DBGC ( ipv6col ( &iphdr->dest ), "IPv6 could not hash "
+                              "multicast %s: %s\n", inet6_ntoa ( next_hop ),
+                              strerror ( rc ) );
                        goto err;
                }
+               ll_dest = ll_dest_buf;
+       } else {
+               /* Unicast address */
+               ll_dest = NULL;
+       }
+
+       /* Hand off to link layer (via NDP if applicable) */
+       if ( ll_dest ) {
+               if ( ( rc = net_tx ( iobuf, netdev, &ipv6_protocol, ll_dest,
+                                    netdev->ll_addr ) ) != 0 ) {
+                       DBGC ( ipv6col ( &iphdr->dest ), "IPv6 could not "
+                              "transmit packet via %s: %s\n",
+                              netdev->name, strerror ( rc ) );
+                       return rc;
+               }
+       } else {
+               if ( ( rc = ndp_tx ( iobuf, netdev, next_hop, &iphdr->src,
+                                    netdev->ll_addr ) ) != 0 ) {
+                       DBGC ( ipv6col ( &iphdr->dest ), "IPv6 could not "
+                              "transmit packet via %s: %s\n",
+                              netdev->name, strerror ( rc ) );
+                       return rc;
+               }
        }
 
-       /* Transmit packet */
-       return net_tx ( iobuf, netdev, &ipv6_protocol, ll_dest,
-                       netdev->ll_addr );
+       return 0;
 
 err:
+ err:
        free_iob ( iobuf );
        return rc;
 }
 
 /**
- * Process next IP6 header
- *
- * @v iobuf    I/O 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 io_buffer *iobuf,
-                                 struct net_device *netdev, 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 ( iobuf, netdev, nxt_hdr, src, dest, 0 /* fixme */ );
-}
-
-/**
- * Process incoming IP6 packets
+ * Process incoming IPv6 packets
  *
  * @v iobuf            I/O buffer
  * @v netdev           Network device
  * @v ll_dest          Link-layer destination address
- * @v ll_source                Link-layer source address
+ * @v ll_source                Link-layer destination source
  * @v flags            Packet flags
+ * @ret rc             Return status code
  *
- * This function processes a IPv6 packet
+ * This function expects an IPv6 network datagram. It processes the
+ * headers and sends it to the transport layer.
  */
-static int ipv6_rx ( struct io_buffer *iobuf,
-                    __unused struct net_device *netdev,
-                    __unused const void *ll_dest,
-                    __unused const void *ll_source,
-                    __unused unsigned int flags ) {
-
-       struct ip6_header *ip6hdr = iobuf->data;
+static int ipv6_rx ( struct io_buffer *iobuf, struct net_device *netdev,
+                    const void *ll_dest __unused,
+                    const void *ll_source __unused,
+                    unsigned int flags __unused ) {
+       struct ipv6_header *iphdr = iobuf->data;
+       union ipv6_extension_header *ext;
        union {
                struct sockaddr_in6 sin6;
                struct sockaddr_tcpip st;
        } src, dest;
+       uint16_t pshdr_csum;
+       size_t len;
+       size_t hdrlen;
+       size_t extlen;
+       int this_header;
+       int next_header;
+       int rc;
 
-       /* Sanity check */
-       if ( iob_len ( iobuf ) < sizeof ( *ip6hdr ) ) {
-               DBG ( "Packet too short (%zd bytes)\n", iob_len ( iobuf ) );
-               goto drop;
+       /* Sanity check the IPv6 header */
+       if ( iob_len ( iobuf ) < sizeof ( *iphdr ) ) {
+               DBGC ( ipv6col ( &iphdr->src ), "IPv6 packet too short at %zd "
+                      "bytes (min %zd bytes)\n", iob_len ( iobuf ),
+                      sizeof ( *iphdr ) );
+               rc = -EINVAL_LEN;
+               goto err;
        }
-
-       /* 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;
+       if ( ( iphdr->ver_tc_label & htonl ( IPV6_MASK_VER ) ) !=
+            htonl ( IPV6_VER ) ) {
+               DBGC ( ipv6col ( &iphdr->src ), "IPv6 version %#08x not "
+                      "supported\n", ntohl ( iphdr->ver_tc_label ) );
+               rc = -ENOTSUP_VER;
+               goto err;
        }
 
-       /* Check the payload length */
-       if ( ntohs ( ip6hdr->payload_len ) > iob_len ( iobuf ) ) {
-               DBG ( "Inconsistent packet length (%d bytes)\n",
-                       ip6hdr->payload_len );
-               goto drop;
+       /* Truncate packet to specified length */
+       len = ntohs ( iphdr->len );
+       if ( len > iob_len ( iobuf ) ) {
+               DBGC ( ipv6col ( &iphdr->src ), "IPv6 length too long at %zd "
+                      "bytes (packet is %zd bytes)\n", len, iob_len ( iobuf ));
+               rc = -EINVAL_LEN;
+               goto err;
        }
+       iob_unput ( iobuf, ( iob_len ( iobuf ) - len - sizeof ( *iphdr ) ) );
+       hdrlen = sizeof ( *iphdr );
+
+       /* Print IPv6 header for debugging */
+       DBGC2 ( ipv6col ( &iphdr->src ), "IPv6 RX %s<-",
+               inet6_ntoa ( &iphdr->dest ) );
+       DBGC2 ( ipv6col ( &iphdr->src ), "%s len %zd next %d\n",
+               inet6_ntoa ( &iphdr->src ), len, iphdr->next_header );
+
+       /* Discard unicast packets not destined for us */
+       if ( ( ! ( flags & LL_MULTICAST ) ) &&
+            ( ! ipv6_has_addr ( netdev, &iphdr->dest ) ) ) {
+               DBGC ( ipv6col ( &iphdr->src ), "IPv6 discarding non-local "
+                      "unicast packet for %s\n", inet6_ntoa ( &iphdr->dest ) );
+               rc = -EPIPE;
+               goto err;
+       }
+
+       /* Process any extension headers */
+       next_header = iphdr->next_header;
+       while ( 1 ) {
+
+               /* Extract extension header */
+               this_header = next_header;
+               ext = ( iobuf->data + hdrlen );
+               extlen = sizeof ( ext->pad );
+               if ( iob_len ( iobuf ) < ( hdrlen + extlen ) ) {
+                       DBGC ( ipv6col ( &iphdr->src ), "IPv6 too short for "
+                              "extension header type %d at %zd bytes (min "
+                              "%zd bytes)\n", this_header,
+                              ( iob_len ( iobuf ) - hdrlen ), extlen );
+                       rc = -EINVAL_LEN;
+                       goto err;
+               }
 
-       /* Ignore the traffic class and flow control values */
+               /* Determine size of extension header (if applicable) */
+               if ( ( this_header == IPV6_HOPBYHOP ) ||
+                    ( this_header == IPV6_DESTINATION ) ||
+                    ( this_header == IPV6_ROUTING ) ) {
+                       /* Length field is present */
+                       extlen += ext->common.len;
+               } else if ( this_header == IPV6_FRAGMENT ) {
+                       /* Length field is reserved and ignored (RFC2460) */
+               } else {
+                       /* Not an extension header; assume rest is payload */
+                       break;
+               }
+               if ( iob_len ( iobuf ) < ( hdrlen + extlen ) ) {
+                       DBGC ( ipv6col ( &iphdr->src ), "IPv6 too short for "
+                              "extension header type %d at %zd bytes (min "
+                              "%zd bytes)\n", this_header,
+                              ( iob_len ( iobuf ) - hdrlen ), extlen );
+                       rc = -EINVAL_LEN;
+                       goto err;
+               }
+               hdrlen += extlen;
+               next_header = ext->common.next_header;
+               DBGC2 ( ipv6col ( &iphdr->src ), "IPv6 RX %s<-",
+                       inet6_ntoa ( &iphdr->dest ) );
+               DBGC2 ( ipv6col ( &iphdr->src ), "%s ext type %d len %zd next "
+                       "%d\n", inet6_ntoa ( &iphdr->src ), this_header,
+                       extlen, next_header );
+
+               /* Process this extension header */
+               if ( ( this_header == IPV6_HOPBYHOP ) ||
+                    ( this_header == IPV6_DESTINATION ) ) {
+
+                       /* Check that all options can be ignored */
+                       if ( ( rc = ipv6_check_options ( iphdr, &ext->options,
+                                                        extlen ) ) != 0 )
+                               goto err;
+
+               } else if ( this_header == IPV6_FRAGMENT ) {
+
+                       /* Reassemble fragments */
+                       iobuf = fragment_reassemble ( &ipv6_reassembler, iobuf,
+                                                     &hdrlen );
+                       if ( ! iobuf )
+                               return 0;
+                       iphdr = iobuf->data;
+               }
+       }
 
-       /* Construct socket address */
+       /* Construct socket address, calculate pseudo-header checksum,
+        * and hand off to transport layer
+        */
        memset ( &src, 0, sizeof ( src ) );
        src.sin6.sin6_family = AF_INET6;
-       src.sin6.sin6_addr = ip6hdr->src;
+       memcpy ( &src.sin6.sin6_addr, &iphdr->src,
+                sizeof ( src.sin6.sin6_addr ) );
+       src.sin6.sin6_scope_id = htonl ( netdev->index );
        memset ( &dest, 0, sizeof ( dest ) );
        dest.sin6.sin6_family = AF_INET6;
-       dest.sin6.sin6_addr = ip6hdr->dest;
-
-       /* Strip header */
-       iob_unput ( iobuf, iob_len ( iobuf ) - ntohs ( ip6hdr->payload_len ) -
-                                                       sizeof ( *ip6hdr ) );
-       iob_pull ( iobuf, sizeof ( *ip6hdr ) );
+       memcpy ( &dest.sin6.sin6_addr, &iphdr->dest,
+                sizeof ( dest.sin6.sin6_addr ) );
+       dest.sin6.sin6_scope_id = htonl ( netdev->index );
+       iob_pull ( iobuf, hdrlen );
+       pshdr_csum = ipv6_pshdr_chksum ( iphdr, iob_len ( iobuf ),
+                                        next_header, TCPIP_EMPTY_CSUM );
+       if ( ( rc = tcpip_rx ( iobuf, netdev, next_header, &src.st, &dest.st,
+                              pshdr_csum ) ) != 0 ) {
+               DBGC ( ipv6col ( &src.sin6.sin6_addr ), "IPv6 received packet "
+                               "rejected by stack: %s\n", strerror ( rc ) );
+               return rc;
+       }
 
-       /* Send it to the transport layer */
-       return ipv6_process_nxt_hdr ( iobuf, netdev, ip6hdr->nxt_hdr, &src.st, &dest.st );
+       return 0;
 
-  drop:
-       DBG ( "Packet dropped\n" );
+ err:
        free_iob ( iobuf );
-       return -1;
+       return rc;
 }
 
 /**
- * Print a IP6 address as xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx
+ * Convert IPv6 address to standard notation
+ *
+ * @v in       IPv6 address
+ * @ret string IPv6 address in standard notation
+ *
+ * RFC5952 defines the canonical format for IPv6 textual representation.
  */
-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;
+char * inet6_ntoa ( const struct in6_addr *in ) {
+       static char buf[41]; /* ":xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx" */
+       char *out = buf;
+       char *longest_start = NULL;
+       char *start = NULL;
+       int longest_len = 1;
+       int len = 0;
+       char *dest;
+       unsigned int i;
+       uint16_t value;
+
+       /* Format address, keeping track of longest run of zeros */
+       for ( i = 0 ; i < ( sizeof ( in->s6_addr16 ) /
+                           sizeof ( in->s6_addr16[0] ) ) ; i++ ) {
+               value = ntohs ( in->s6_addr16[i] );
+               if ( value == 0 ) {
+                       if ( len++ == 0 )
+                               start = out;
+                       if ( len > longest_len ) {
+                               longest_start = start;
+                               longest_len = len;
+                       }
+               } else {
+                       len = 0;
+               }
+               out += sprintf ( out, ":%x", value );
+       }
+
+       /* Abbreviate longest run of zeros, if applicable */
+       if ( longest_start ) {
+               dest = strcpy ( ( longest_start + 1 ),
+                               ( longest_start + ( 2 * longest_len ) ) );
+               if ( dest[0] == '\0' )
+                       dest[1] = '\0';
+               dest[0] = ':';
+       }
+       return ( ( longest_start == buf ) ? buf : ( buf + 1 ) );
 }
 
+/**
+ * Transcribe IPv6 address
+ *
+ * @v net_addr IPv6 address
+ * @ret string IPv6 address in standard notation
+ *
+ */
 static const char * ipv6_ntoa ( const void *net_addr ) {
-       return inet6_ntoa ( * ( ( struct in6_addr * ) net_addr ) );
+       return inet6_ntoa ( net_addr );
 }
 
 /** IPv6 protocol */
@@ -383,3 +695,72 @@ struct tcpip_net_protocol ipv6_tcpip_protocol __tcpip_net_protocol = {
        .sa_family = AF_INET6,
        .tx = ipv6_tx,
 };
+
+/**
+ * Create IPv6 network device
+ *
+ * @v netdev           Network device
+ * @ret rc             Return status code
+ */
+static int ipv6_probe ( struct net_device *netdev ) {
+       struct ipv6_miniroute *miniroute;
+       struct in6_addr address;
+       int prefix_len;
+       int rc;
+
+       /* Construct link-local address from EUI-64 as per RFC 2464 */
+       prefix_len = ipv6_link_local ( &address, netdev );
+       if ( prefix_len < 0 ) {
+               rc = prefix_len;
+               DBGC ( netdev, "IPv6 %s could not construct link-local "
+                      "address: %s\n", netdev->name, strerror ( rc ) );
+               return rc;
+       }
+
+       /* Create link-local address for this network device */
+       miniroute = add_ipv6_miniroute ( netdev, &address, prefix_len, NULL );
+       if ( ! miniroute )
+               return -ENOMEM;
+
+       return 0;
+}
+
+/**
+ * Handle IPv6 network device or link state change
+ *
+ * @v netdev           Network device
+ */
+static void ipv6_notify ( struct net_device *netdev __unused ) {
+
+       /* Nothing to do */
+}
+
+/**
+ * Destroy IPv6 network device
+ *
+ * @v netdev           Network device
+ */
+static void ipv6_remove ( struct net_device *netdev ) {
+       struct ipv6_miniroute *miniroute;
+       struct ipv6_miniroute *tmp;
+
+       /* Delete all miniroutes for this network device */
+       list_for_each_entry_safe ( miniroute, tmp, &ipv6_miniroutes, list ) {
+               if ( miniroute->netdev == netdev )
+                       del_ipv6_miniroute ( miniroute );
+       }
+}
+
+/** IPv6 network device driver */
+struct net_driver ipv6_driver __net_driver = {
+       .name = "IPv6",
+       .probe = ipv6_probe,
+       .notify = ipv6_notify,
+       .remove = ipv6_remove,
+};
+
+/* Drag in ICMPv6 */
+REQUIRE_OBJECT ( icmpv6 );
+
+/* Drag in NDP */
+REQUIRE_OBJECT ( ndp );
index 4d3713353eb525b0ee38eb36eacb8caabb9d2fef..48b16d0251f78627d156dba7775f50534a62f515 100644 (file)
-#include <stdint.h>
+/*
+ * Copyright (C) 2013 Michael Brown <mbrown@fensystems.co.uk>.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
 #include <string.h>
-#include <byteswap.h>
 #include <errno.h>
-#include <ipxe/if_ether.h>
+#include <byteswap.h>
+#include <ipxe/in.h>
 #include <ipxe/iobuf.h>
+#include <ipxe/tcpip.h>
+#include <ipxe/ipv6.h>
+#include <ipxe/icmpv6.h>
+#include <ipxe/neighbour.h>
 #include <ipxe/ndp.h>
-#include <ipxe/icmp6.h>
-#include <ipxe/ip6.h>
-#include <ipxe/netdevice.h>
 
 /** @file
  *
- * Neighbour Discovery Protocol
+ * IPv6 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
+ * Transmit NDP neighbour solicitation/advertisement packet
  *
- * @v in6      IP6 address
+ * @v netdev           Network device
+ * @v sin6_src         Source socket address
+ * @v sin6_dest                Destination socket address
+ * @v target           Neighbour target address
+ * @v icmp_type                ICMPv6 type
+ * @v flags            NDP flags
+ * @v option_type      NDP option type
+ * @ret rc             Return status code
  */
-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;
-               }
+static int ndp_tx_neighbour ( struct net_device *netdev,
+                             struct sockaddr_in6 *sin6_src,
+                             struct sockaddr_in6 *sin6_dest,
+                             const struct in6_addr *target,
+                             unsigned int icmp_type,
+                             unsigned int flags,
+                             unsigned int option_type ) {
+       struct sockaddr_tcpip *st_src =
+               ( ( struct sockaddr_tcpip * ) sin6_src );
+       struct sockaddr_tcpip *st_dest =
+               ( ( struct sockaddr_tcpip * ) sin6_dest );
+       struct ll_protocol *ll_protocol = netdev->ll_protocol;
+       struct io_buffer *iobuf;
+       struct ndp_header *ndp;
+       size_t option_len;
+       size_t len;
+       int rc;
+
+       /* Allocate and populate buffer */
+       option_len = ( ( sizeof ( ndp->option[0] ) + ll_protocol->ll_addr_len +
+                        NDP_OPTION_BLKSZ - 1 ) &
+                      ~( NDP_OPTION_BLKSZ - 1 ) );
+       len = ( sizeof ( *ndp ) + option_len );
+       iobuf = alloc_iob ( MAX_LL_NET_HEADER_LEN + len );
+       if ( ! iobuf )
+               return -ENOMEM;
+       iob_reserve ( iobuf, MAX_LL_NET_HEADER_LEN );
+       ndp = iob_put ( iobuf, len );
+       memset ( ndp, 0, len );
+       ndp->icmp.type = icmp_type;
+       ndp->flags = flags;
+       memcpy ( &ndp->target, target, sizeof ( ndp->target ) );
+       ndp->option[0].type = option_type;
+       ndp->option[0].blocks = ( option_len / NDP_OPTION_BLKSZ );
+       memcpy ( ndp->option[0].value, netdev->ll_addr,
+                ll_protocol->ll_addr_len );
+       ndp->icmp.chksum = tcpip_chksum ( ndp, len );
+
+       /* Transmit packet */
+       if ( ( rc = tcpip_tx ( iobuf, &icmpv6_protocol, st_src, st_dest,
+                              netdev, &ndp->icmp.chksum ) ) != 0 ) {
+               DBGC ( netdev, "NDP could not transmit packet: %s\n",
+                      strerror ( rc ) );
+               return rc;
        }
-       return NULL;
+
+       return 0;
 }
 
 /**
- * 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
+ * Transmit NDP neighbour discovery request
+ *
+ * @v netdev           Network device
+ * @v net_protocol     Network-layer protocol
+ * @v net_dest         Destination network-layer address
+ * @v net_source       Source network-layer address
+ * @ret rc             Return status code
  */
-static 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: IP6 %s => %s %s\n",
-             inet6_ntoa ( ndp->in6 ), netdev->ll_protocol->name,
-             netdev->ll_protocol->ntoa ( ndp->ll_addr ) );
+static int ndp_tx_request ( struct net_device *netdev,
+                           struct net_protocol *net_protocol __unused,
+                           const void *net_dest, const void *net_source ) {
+       struct sockaddr_in6 sin6_src;
+       struct sockaddr_in6 sin6_dest;
+
+       /* Construct source address */
+       memset ( &sin6_src, 0, sizeof ( sin6_src ) );
+       sin6_src.sin6_family = AF_INET6;
+       memcpy ( &sin6_src.sin6_addr, net_source,
+                sizeof ( sin6_src.sin6_addr ) );
+       sin6_src.sin6_scope_id = htons ( netdev->index );
+
+       /* Construct multicast destination address */
+       memset ( &sin6_dest, 0, sizeof ( sin6_dest ) );
+       sin6_dest.sin6_family = AF_INET6;
+       sin6_dest.sin6_scope_id = htons ( netdev->index );
+       ipv6_solicited_node ( &sin6_dest.sin6_addr, net_dest );
+
+       /* Transmit neighbour discovery packet */
+       return ndp_tx_neighbour ( netdev, &sin6_src, &sin6_dest, net_dest,
+                                 ICMPV6_NDP_NEIGHBOUR_SOLICITATION, 0,
+                                 NDP_OPT_LL_SOURCE );
 }
 
+/** NDP neighbour discovery protocol */
+struct neighbour_discovery ndp_discovery = {
+       .name = "NDP",
+       .tx_request = ndp_tx_request,
+};
+
 /**
- * Resolve the link-layer address
+ * Process NDP neighbour solicitation source link-layer address option
  *
  * @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.
+ * @v sin6_src         Source socket address
+ * @v ndp              NDP packet
+ * @v ll_addr          Source link-layer address
+ * @v ll_addr_len      Source link-layer address length
+ * @ret rc             Return status code
  */
-int ndp_resolve ( struct net_device *netdev, struct in6_addr *dest,
-                 struct in6_addr *src, void *dest_ll_addr ) {
+static int ndp_rx_neighbour_solicitation ( struct net_device *netdev,
+                                          struct sockaddr_in6 *sin6_src,
+                                          struct ndp_header *ndp __unused,
+                                          const void *ll_addr,
+                                          size_t ll_addr_len ) {
        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 );
+       /* Silently ignore neighbour solicitations for addresses we do
+        * not own.
+        */
+       if ( ! ipv6_has_addr ( netdev, &ndp->target ) )
                return 0;
-       }
 
-       /* Check if the entry was already created */
-       if ( ndp ) {
-               DBG ( "Awaiting neighbour advertisement\n" );
-               /* 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;
+       /* Sanity check */
+       if ( ll_addr_len < ll_protocol->ll_addr_len ) {
+               DBGC ( netdev, "NDP neighbour solicitation link-layer address "
+                      "too short at %zd bytes (min %d bytes)\n",
+                      ll_addr_len, ll_protocol->ll_addr_len );
+               return -EINVAL;
        }
-       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 );
+       /* Create or update neighbour cache entry */
+       if ( ( rc = neighbour_define ( netdev, &ipv6_protocol,
+                                      &sin6_src->sin6_addr,
+                                      ll_addr ) ) != 0 ) {
+               DBGC ( netdev, "NDP could not define %s => %s: %s\n",
+                      inet6_ntoa ( &sin6_src->sin6_addr ),
+                      ll_protocol->ntoa ( ll_addr ), strerror ( rc ) );
+               return rc;
+       }
 
-       /* Send neighbour solicitation */
-       if ( ( rc = icmp6_send_solicit ( netdev, src, dest ) ) != 0 ) {
+       /* Send neighbour advertisement */
+       if ( ( rc = ndp_tx_neighbour ( netdev, NULL, sin6_src, &ndp->target,
+                                      ICMPV6_NDP_NEIGHBOUR_ADVERTISEMENT,
+                                      ( NDP_SOLICITED | NDP_OVERRIDE ),
+                                      NDP_OPT_LL_TARGET ) ) != 0 ) {
                return rc;
        }
-       return -ENOENT;
+
+       return 0;
 }
 
 /**
- * Process neighbour advertisement
+ * Process NDP neighbour advertisement target link-layer address option
  *
- * @v iobuf    I/O buffer
- * @v st_src   Source address
- * @v st_dest  Destination address 
+ * @v netdev           Network device
+ * @v sin6_src         Source socket address
+ * @v ndp              NDP packet
+ * @v ll_addr          Target link-layer address
+ * @v ll_addr_len      Target link-layer address length
+ * @ret rc             Return status code
  */
-int ndp_process_advert ( struct io_buffer *iobuf, struct sockaddr_tcpip *st_src __unused,
-                          struct sockaddr_tcpip *st_dest __unused ) {
-       struct neighbour_advert *nadvert = iobuf->data;
-       struct ndp_entry *ndp;
+static int
+ndp_rx_neighbour_advertisement ( struct net_device *netdev,
+                                struct sockaddr_in6 *sin6_src __unused,
+                                struct ndp_header *ndp, const void *ll_addr,
+                                size_t ll_addr_len ) {
+       struct ll_protocol *ll_protocol = netdev->ll_protocol;
+       int rc;
 
        /* Sanity check */
-       if ( iob_len ( iobuf ) < sizeof ( *nadvert ) ) {
-               DBG ( "Packet too short (%zd bytes)\n", iob_len ( iobuf ) );
+       if ( ll_addr_len < ll_protocol->ll_addr_len ) {
+               DBGC ( netdev, "NDP neighbour advertisement link-layer address "
+                      "too short at %zd bytes (min %d bytes)\n",
+                      ll_addr_len, ll_protocol->ll_addr_len );
                return -EINVAL;
        }
 
-       assert ( nadvert->code == 0 );
-       assert ( nadvert->flags & ICMP6_FLAGS_SOLICITED );
-       assert ( nadvert->opt_type == 2 );
+       /* Update neighbour cache entry, if any */
+       if ( ( rc = neighbour_update ( netdev, &ipv6_protocol, &ndp->target,
+                                      ll_addr ) ) != 0 ) {
+               DBGC ( netdev, "NDP could not update %s => %s: %s\n",
+                      inet6_ntoa ( &ndp->target ),
+                      ll_protocol->ntoa ( ll_addr ), strerror ( rc ) );
+               return rc;
+       }
 
-       /* Update the neighbour cache, if entry is present */
-       ndp = ndp_find_entry ( &nadvert->target );
-       if ( ndp ) {
+       return 0;
+}
 
-       assert ( nadvert->opt_len ==
-                       ( ( 2 + ndp->ll_protocol->ll_addr_len ) / 8 ) );
+/** An NDP option handler */
+struct ndp_option_handler {
+       /** ICMPv6 type */
+       uint8_t icmp_type;
+       /** Option type */
+       uint8_t option_type;
+       /**
+        * Handle received option
+        *
+        * @v netdev            Network device
+        * @v sin6_src          Source socket address
+        * @v ndp               NDP packet
+        * @v value             Option value
+        * @v len               Option length
+        * @ret rc              Return status code
+        */
+       int ( * rx ) ( struct net_device *netdev, struct sockaddr_in6 *sin6_src,
+                      struct ndp_header *ndp, const void *value, size_t len );
+};
 
-               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;
+/** NDP option handlers */
+static struct ndp_option_handler ndp_option_handlers[] = {
+       {
+               .icmp_type = ICMPV6_NDP_NEIGHBOUR_SOLICITATION,
+               .option_type = NDP_OPT_LL_SOURCE,
+               .rx = ndp_rx_neighbour_solicitation,
+       },
+       {
+               .icmp_type = ICMPV6_NDP_NEIGHBOUR_ADVERTISEMENT,
+               .option_type = NDP_OPT_LL_TARGET,
+               .rx = ndp_rx_neighbour_advertisement,
+       },
+};
+
+/**
+ * Process received NDP option
+ *
+ * @v netdev           Network device
+ * @v sin6_src         Source socket address
+ * @v ndp              NDP packet
+ * @v type             Option type
+ * @v value            Option value
+ * @v len              Option length
+ * @ret rc             Return status code
+ */
+static int ndp_rx_option ( struct net_device *netdev,
+                          struct sockaddr_in6 *sin6_src,
+                          struct ndp_header *ndp, unsigned int type,
+                          const void *value, size_t len ) {
+       struct ndp_option_handler *handler;
+       unsigned int i;
+
+       /* Locate a suitable option handler, if any */
+       for ( i = 0 ; i < ( sizeof ( ndp_option_handlers ) /
+                           sizeof ( ndp_option_handlers[0] ) ) ; i++ ) {
+               handler = &ndp_option_handlers[i];
+               if ( ( handler->icmp_type == ndp->icmp.type ) &&
+                    ( handler->option_type == type ) ) {
+                       return handler->rx ( netdev, sin6_src, ndp,
+                                            value, len );
                }
        }
-       DBG ( "Unsolicited advertisement (dropping packet)\n" );
+
+       /* Silently ignore unknown options as per RFC 4861 */
        return 0;
 }
+
+/**
+ * Process received NDP packet
+ *
+ * @v iobuf            I/O buffer
+ * @v netdev           Network device
+ * @v sin6_src         Source socket address
+ * @v sin6_dest                Destination socket address
+ * @ret rc             Return status code
+ */
+static int ndp_rx ( struct io_buffer *iobuf,
+                   struct net_device *netdev,
+                   struct sockaddr_in6 *sin6_src,
+                   struct sockaddr_in6 *sin6_dest __unused ) {
+       struct ndp_header *ndp = iobuf->data;
+       struct ndp_option *option;
+       size_t remaining;
+       size_t option_len;
+       size_t option_value_len;
+       int rc;
+
+       /* Sanity check */
+       if ( iob_len ( iobuf ) < sizeof ( *ndp ) ) {
+               DBGC ( netdev, "NDP packet too short at %zd bytes (min %zd "
+                      "bytes)\n", iob_len ( iobuf ), sizeof ( *ndp ) );
+               rc = -EINVAL;
+               goto done;
+       }
+
+       /* Search for option */
+       option = ndp->option;
+       remaining = ( iob_len ( iobuf ) - offsetof ( typeof ( *ndp ), option ));
+       while ( remaining ) {
+
+               /* Sanity check */
+               if ( ( remaining < sizeof ( *option ) ) ||
+                    ( option->blocks == 0 ) ||
+                    ( remaining < ( option->blocks * NDP_OPTION_BLKSZ ) ) ) {
+                       DBGC ( netdev, "NDP bad option length:\n" );
+                       DBGC_HDA ( netdev, 0, option, remaining );
+                       rc = -EINVAL;
+                       goto done;
+               }
+               option_len = ( option->blocks * NDP_OPTION_BLKSZ );
+               option_value_len = ( option_len - sizeof ( *option ) );
+
+               /* Handle option */
+               if ( ( rc = ndp_rx_option ( netdev, sin6_src, ndp,
+                                           option->type, option->value,
+                                           option_value_len ) ) != 0 ) {
+                       goto done;
+               }
+
+               /* Move to next option */
+               option = ( ( ( void * ) option ) + option_len );
+               remaining -= option_len;
+       }
+
+ done:
+       free_iob ( iobuf );
+       return rc;
+}
+
+/** NDP ICMPv6 handlers */
+struct icmpv6_handler ndp_handlers[] __icmpv6_handler = {
+       {
+               .type = ICMPV6_NDP_NEIGHBOUR_SOLICITATION,
+               .rx = ndp_rx,
+       },
+       {
+               .type = ICMPV6_NDP_NEIGHBOUR_ADVERTISEMENT,
+               .rx = ndp_rx,
+       },
+};
diff --git a/src/tests/ipv6_test.c b/src/tests/ipv6_test.c
new file mode 100644 (file)
index 0000000..10e964d
--- /dev/null
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2013 Michael Brown <mbrown@fensystems.co.uk>.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+/** @file
+ *
+ * IPv6 tests
+ *
+ */
+
+/* Forcibly enable assertions */
+#undef NDEBUG
+
+#include <stdint.h>
+#include <string.h>
+#include <byteswap.h>
+#include <ipxe/ipv6.h>
+#include <ipxe/test.h>
+
+/** Define inline IPv6 address */
+#define IPV6(...) { __VA_ARGS__ }
+
+/**
+ * Report an inet6_ntoa() test result
+ *
+ * @v addr             IPv6 address
+ * @v text             Expected textual representation
+ */
+#define inet6_ntoa_ok( addr, text ) do {                               \
+       static const struct in6_addr in = {                             \
+               .s6_addr = addr,                                        \
+       };                                                              \
+       static const char expected[] = text;                            \
+       char *actual;                                                   \
+                                                                       \
+       actual = inet6_ntoa ( &in );                                    \
+       DBG ( "inet6_ntoa ( %04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x ) " \
+             "= %s\n", ntohs ( in.s6_addr16[0] ),                      \
+             ntohs ( in.s6_addr16[1] ), ntohs ( in.s6_addr16[2] ),     \
+             ntohs ( in.s6_addr16[3] ), ntohs ( in.s6_addr16[4] ),     \
+             ntohs ( in.s6_addr16[5] ), ntohs ( in.s6_addr16[6] ),     \
+             ntohs ( in.s6_addr16[7] ), actual );                      \
+       ok ( strcmp ( actual, expected ) == 0 );                        \
+       } while ( 0 )
+
+/**
+ * Perform IPv6 self-tests
+ *
+ */
+static void ipv6_test_exec ( void ) {
+
+       /* inet6_ntoa() tests */
+       inet6_ntoa_ok ( IPV6 ( 0x20, 0x01, 0x0b, 0xa8, 0x00, 0x00, 0x01, 0xd4,
+                              0x00, 0x00, 0x00, 0x00, 0x69, 0x50, 0x58, 0x45 ),
+                       "2001:ba8:0:1d4::6950:5845" );
+       /* No zeros */
+       inet6_ntoa_ok ( IPV6 ( 0x20, 0x01, 0x0d, 0xb8, 0x00, 0x01, 0x00, 0x01,
+                              0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01 ),
+                       "2001:db8:1:1:1:1:1:1" );
+       /* Run of zeros */
+       inet6_ntoa_ok ( IPV6 ( 0x20, 0x01, 0x0d, 0xb8, 0x00, 0x00, 0x00, 0x00,
+                              0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 ),
+                       "2001:db8::1" );
+       /* No "::" for single zero */
+       inet6_ntoa_ok ( IPV6 ( 0x20, 0x01, 0x0d, 0xb8, 0x00, 0x00, 0x00, 0x01,
+                              0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01 ),
+                       "2001:db8:0:1:1:1:1:1" );
+       /* Use "::" for longest run of zeros */
+       inet6_ntoa_ok ( IPV6 ( 0x20, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+                              0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 ),
+                       "2001:0:0:1::1" );
+       /* Use "::" for leftmost equal-length run of zeros */
+       inet6_ntoa_ok ( IPV6 ( 0x20, 0x01, 0x0d, 0xb8, 0x00, 0x00, 0x00, 0x00,
+                              0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 ),
+                       "2001:db8::1:0:0:1" );
+       /* Trailing run of zeros */
+       inet6_ntoa_ok ( IPV6 ( 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                              0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 ),
+                       "fe80::" );
+       /* Leading run of zeros */
+       inet6_ntoa_ok ( IPV6 ( 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                              0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 ),
+                       "::1" );
+       /* All zeros */
+       inet6_ntoa_ok ( IPV6 ( 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                              0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 ),
+                       "::" );
+       /* Maximum length */
+       inet6_ntoa_ok ( IPV6 ( 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+                              0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff ),
+                       "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff" );
+}
+
+/** IPv6 self-test */
+struct self_test ipv6_test __self_test = {
+       .name = "ipv6",
+       .exec = ipv6_test_exec,
+};
index f965e6e3c996c53b90e396a3e518621580068fa9..17e22a3a56acdd16167bd354da0398a0ff32b67d 100644 (file)
@@ -36,6 +36,7 @@ REQUIRE_OBJECT ( base16_test );
 REQUIRE_OBJECT ( settings_test );
 REQUIRE_OBJECT ( time_test );
 REQUIRE_OBJECT ( tcpip_test );
+REQUIRE_OBJECT ( ipv6_test );
 REQUIRE_OBJECT ( crc32_test );
 REQUIRE_OBJECT ( md5_test );
 REQUIRE_OBJECT ( sha1_test );
diff --git a/src/usr/route_ipv6.c b/src/usr/route_ipv6.c
new file mode 100644 (file)
index 0000000..8a6fbde
--- /dev/null
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2013 Michael Brown <mbrown@fensystems.co.uk>.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+#include <stdio.h>
+#include <ipxe/netdevice.h>
+#include <ipxe/ipv6.h>
+#include <usr/route.h>
+
+/** @file
+ *
+ * IPv6 routing management
+ *
+ */
+
+/**
+ * Print IPv6 routing table
+ *
+ * @v netdev           Network device
+ */
+static void route_ipv6_print ( struct net_device *netdev ) {
+       struct ipv6_miniroute *miniroute;
+
+       list_for_each_entry ( miniroute, &ipv6_miniroutes, list ) {
+               if ( miniroute->netdev != netdev )
+                       continue;
+               printf ( "%s: %s/%d", netdev->name,
+                        inet6_ntoa ( &miniroute->address ),
+                        miniroute->prefix_len );
+               if ( miniroute->has_router )
+                       printf ( " gw %s", inet6_ntoa ( &miniroute->router ) );
+               if ( ! netdev_is_open ( miniroute->netdev ) )
+                       printf ( " (inaccessible)" );
+               printf ( "\n" );
+       }
+}
+
+/** IPv6 routing family */
+struct routing_family ipv6_routing_family __routing_family ( ROUTING_IPV6 ) = {
+       .print = route_ipv6_print,
+};