]> git.ipfire.org Git - thirdparty/ipxe.git/commitdiff
[ipv6] Support stateless address autoconfiguration (SLAAC)
authorMichael Brown <mcb30@ipxe.org>
Wed, 23 Oct 2013 13:00:12 +0000 (14:00 +0100)
committerMichael Brown <mcb30@ipxe.org>
Wed, 23 Oct 2013 13:07:57 +0000 (14:07 +0100)
Signed-off-by: Michael Brown <mcb30@ipxe.org>
src/include/ipxe/ipv6.h
src/include/ipxe/ndp.h
src/net/ipv6.c
src/net/ndp.c

index f404ba64e21df861ab4369896e635bd2669da76d..6a9aa606c2b02ce741a205dcbf482bb56101062d 100644 (file)
@@ -173,26 +173,39 @@ struct ipv6_miniroute {
 };
 
 /**
- * Construct link-local address (via EUI-64)
+ * Construct local IPv6 address via EUI-64
  *
- * @v addr             Address to construct
+ * @v addr             Prefix to be completed
  * @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 ) {
+static inline int ipv6_eui64 ( 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 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 ) {
+
+       memset ( addr, 0, sizeof ( *addr ) );
+       addr->s6_addr16[0] = htons ( 0xfe80 );
+       return ipv6_eui64 ( addr, netdev );
+}
+
 /**
  * Construct solicited-node multicast address
  *
@@ -214,5 +227,7 @@ 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 );
+extern int ipv6_slaac ( struct net_device *netdev, struct in6_addr *prefix,
+                       unsigned int prefix_len, struct in6_addr *router );
 
 #endif /* _IPXE_IPV6_H */
index 92f6da5921ca2920e34e124170b2c1a431799ee9..4edd96a40479c3fdfa09964336ed46e29de19384 100644 (file)
@@ -15,19 +15,68 @@ FILE_LICENCE ( GPL2_OR_LATER );
 #include <ipxe/icmpv6.h>
 #include <ipxe/neighbour.h>
 
-/** An NDP option */
-struct ndp_option {
+/** An NDP option header */
+struct ndp_option_header {
        /** 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
 
+/** NDP source link-layer address option */
+#define NDP_OPT_LL_SOURCE 1
+
+/** NDP target link-layer address option */
+#define NDP_OPT_LL_TARGET 2
+
+/** NDP source or target link-layer address option */
+struct ndp_ll_addr_option {
+       /** NDP option header */
+       struct ndp_option_header header;
+       /** Link-layer address */
+       uint8_t ll_addr[0];
+} __attribute__ (( packed ));
+
+/** NDP prefix information option */
+#define NDP_OPT_PREFIX 3
+
+/** NDP prefix information */
+struct ndp_prefix_information_option {
+       /** NDP option header */
+       struct ndp_option_header header;
+       /** Prefix length */
+       uint8_t prefix_len;
+       /** Flags */
+       uint8_t flags;
+       /** Valid lifetime */
+       uint32_t valid;
+       /** Preferred lifetime */
+       uint32_t preferred;
+       /** Reserved */
+       uint32_t reserved;
+       /** Prefix */
+       struct in6_addr prefix;
+} __attribute__ (( packed ));
+
+/** NDP on-link flag */
+#define NDP_PREFIX_ON_LINK 0x80
+
+/** NDP autonomous address configuration flag */
+#define NDP_PREFIX_AUTONOMOUS 0x40
+
+/** An NDP option */
+union ndp_option {
+       /** Option header */
+       struct ndp_option_header header;
+       /** Source or target link-layer address option */
+       struct ndp_ll_addr_option ll_addr;
+       /** Prefix information option */
+       struct ndp_prefix_information_option prefix;
+} __attribute__ (( packed ));
+
 /** An NDP neighbour solicitation or advertisement header */
 struct ndp_neighbour_header {
        /** ICMPv6 header */
@@ -39,7 +88,7 @@ struct ndp_neighbour_header {
        /** Target address */
        struct in6_addr target;
        /** Options */
-       struct ndp_option option[0];
+       union ndp_option option[0];
 } __attribute__ (( packed ));
 
 /** NDP router flag */
@@ -66,7 +115,7 @@ struct ndp_router_advertisement_header {
        /** Retransmission timer */
        uint32_t retransmit;
        /** Options */
-       struct ndp_option option[0];
+       union ndp_option option[0];
 } __attribute__ (( packed ));
 
 /** NDP managed address configuration */
@@ -85,12 +134,6 @@ union ndp_header {
        struct ndp_router_advertisement_header radv;
 } __attribute__ (( packed ));
 
-/** 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;
 
 /**
index c5f8d2d910add3d5d59c9fbda9958379436eb6b7..4cd6720924b51a7f079f309c0b033584448709b5 100644 (file)
@@ -862,6 +862,53 @@ struct sockaddr_converter ipv6_sockaddr_converter __sockaddr_converter = {
        .aton = ipv6_sock_aton,
 };
 
+/**
+ * Perform IPv6 stateless address autoconfiguration (SLAAC)
+ *
+ * @v netdev           Network device
+ * @v prefix           Prefix
+ * @v prefix_len       Prefix length
+ * @v router           Router address (or NULL)
+ * @ret rc             Return status code
+ */
+int ipv6_slaac ( struct net_device *netdev, struct in6_addr *prefix,
+                unsigned int prefix_len, struct in6_addr *router ) {
+       struct ipv6_miniroute *miniroute;
+       struct ipv6_miniroute *tmp;
+       struct in6_addr address;
+       int check_prefix_len;
+       int rc;
+
+       /* Construct local address */
+       memcpy ( &address, prefix, sizeof ( address ) );
+       check_prefix_len = ipv6_eui64 ( &address, netdev );
+       if ( check_prefix_len < 0 ) {
+               rc = check_prefix_len;
+               DBGC ( netdev, "IPv6 %s could not construct SLAAC address: "
+                      "%s\n", netdev->name, strerror ( rc ) );
+               return rc;
+       }
+       if ( check_prefix_len != ( int ) prefix_len ) {
+               DBGC ( netdev, "IPv6 %s incorrect SLAAC prefix length %d "
+                      "(expected %d)\n", netdev->name, prefix_len,
+                      check_prefix_len );
+               return -EINVAL;
+       }
+
+       /* Delete any existing SLAAC miniroutes for this prefix */
+       list_for_each_entry_safe ( miniroute, tmp, &ipv6_miniroutes, list ) {
+               if ( ipv6_is_local ( miniroute, &address ) )
+                       del_ipv6_miniroute ( miniroute );
+       }
+
+       /* Add miniroute */
+       miniroute = add_ipv6_miniroute ( netdev, &address, prefix_len, router );
+       if ( ! miniroute )
+               return -ENOMEM;
+
+       return 0;
+}
+
 /**
  * Create IPv6 network device
  *
index b05756aa7c7658927aa86174dd90669859405120..cc57478a48e45a3b1d5a7c4801c9a0eb7873594b 100644 (file)
@@ -62,12 +62,13 @@ static int ndp_tx_neighbour ( struct net_device *netdev,
        struct ll_protocol *ll_protocol = netdev->ll_protocol;
        struct io_buffer *iobuf;
        struct ndp_neighbour_header *neigh;
+       struct ndp_ll_addr_option *ll_addr_opt;
        size_t option_len;
        size_t len;
        int rc;
 
        /* Allocate and populate buffer */
-       option_len = ( ( sizeof ( neigh->option[0] ) +
+       option_len = ( ( sizeof ( *ll_addr_opt ) +
                         ll_protocol->ll_addr_len + NDP_OPTION_BLKSZ - 1 ) &
                       ~( NDP_OPTION_BLKSZ - 1 ) );
        len = ( sizeof ( *neigh ) + option_len );
@@ -80,9 +81,10 @@ static int ndp_tx_neighbour ( struct net_device *netdev,
        neigh->icmp.type = icmp_type;
        neigh->flags = flags;
        memcpy ( &neigh->target, target, sizeof ( neigh->target ) );
-       neigh->option[0].type = option_type;
-       neigh->option[0].blocks = ( option_len / NDP_OPTION_BLKSZ );
-       memcpy ( neigh->option[0].value, netdev->ll_addr,
+       ll_addr_opt = &neigh->option[0].ll_addr;
+       ll_addr_opt->header.type = option_type;
+       ll_addr_opt->header.blocks = ( option_len / NDP_OPTION_BLKSZ );
+       memcpy ( ll_addr_opt->ll_addr, netdev->ll_addr,
                 ll_protocol->ll_addr_len );
        neigh->icmp.chksum = tcpip_chksum ( neigh, len );
 
@@ -143,17 +145,18 @@ struct neighbour_discovery ndp_discovery = {
  * @v netdev           Network device
  * @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
+ * @v option           NDP option
+ * @v len              NDP option length
  * @ret rc             Return status code
  */
 static int
 ndp_rx_neighbour_solicitation_ll_source ( struct net_device *netdev,
                                          struct sockaddr_in6 *sin6_src,
                                          union ndp_header *ndp,
-                                         const void *ll_addr,
-                                         size_t ll_addr_len ) {
+                                         union ndp_option *option,
+                                         size_t len ) {
        struct ndp_neighbour_header *neigh = &ndp->neigh;
+       struct ndp_ll_addr_option *ll_addr_opt = &option->ll_addr;
        struct ll_protocol *ll_protocol = netdev->ll_protocol;
        int rc;
 
@@ -164,20 +167,21 @@ ndp_rx_neighbour_solicitation_ll_source ( struct net_device *netdev,
                return 0;
 
        /* Sanity check */
-       if ( ll_addr_len < ll_protocol->ll_addr_len ) {
+       if ( offsetof ( typeof ( *ll_addr_opt ),
+                       ll_addr[ll_protocol->ll_addr_len] ) > 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 );
+                      "option too short at %zd bytes\n", len );
                return -EINVAL;
        }
 
        /* Create or update neighbour cache entry */
        if ( ( rc = neighbour_define ( netdev, &ipv6_protocol,
                                       &sin6_src->sin6_addr,
-                                      ll_addr ) ) != 0 ) {
+                                      ll_addr_opt->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 ) );
+                      ll_protocol->ntoa ( ll_addr_opt->ll_addr ),
+                      strerror ( rc ) );
                return rc;
        }
 
@@ -199,8 +203,8 @@ ndp_rx_neighbour_solicitation_ll_source ( struct net_device *netdev,
  * @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
+ * @v option           NDP option
+ * @v len              NDP option length
  * @ret rc             Return status code
  */
 static int
@@ -208,26 +212,28 @@ ndp_rx_neighbour_advertisement_ll_target ( struct net_device *netdev,
                                           struct sockaddr_in6 *sin6_src
                                                   __unused,
                                           union ndp_header *ndp,
-                                          const void *ll_addr,
-                                          size_t ll_addr_len ) {
+                                          union ndp_option *option,
+                                          size_t len ) {
        struct ndp_neighbour_header *neigh = &ndp->neigh;
+       struct ndp_ll_addr_option *ll_addr_opt = &option->ll_addr;
        struct ll_protocol *ll_protocol = netdev->ll_protocol;
        int rc;
 
        /* Sanity check */
-       if ( ll_addr_len < ll_protocol->ll_addr_len ) {
+       if ( offsetof ( typeof ( *ll_addr_opt ),
+                       ll_addr[ll_protocol->ll_addr_len] ) > 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 );
+                      "option too short at %zd bytes\n", len );
                return -EINVAL;
        }
 
        /* Update neighbour cache entry, if any */
        if ( ( rc = neighbour_update ( netdev, &ipv6_protocol, &neigh->target,
-                                      ll_addr ) ) != 0 ) {
+                                      ll_addr_opt->ll_addr ) ) != 0 ) {
                DBGC ( netdev, "NDP could not update %s => %s: %s\n",
                       inet6_ntoa ( &neigh->target ),
-                      ll_protocol->ntoa ( ll_addr ), strerror ( rc ) );
+                      ll_protocol->ntoa ( ll_addr_opt->ll_addr ),
+                      strerror ( rc ) );
                return rc;
        }
 
@@ -240,40 +246,94 @@ ndp_rx_neighbour_advertisement_ll_target ( struct net_device *netdev,
  * @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
+ * @v option           NDP option
+ * @v len              NDP option length
  * @ret rc             Return status code
  */
 static int
 ndp_rx_router_advertisement_ll_source ( struct net_device *netdev,
                                        struct sockaddr_in6 *sin6_src,
                                        union ndp_header *ndp __unused,
-                                       const void *ll_addr,
-                                       size_t ll_addr_len ) {
+                                       union ndp_option *option, size_t len ) {
+       struct ndp_ll_addr_option *ll_addr_opt = &option->ll_addr;
        struct ll_protocol *ll_protocol = netdev->ll_protocol;
        int rc;
 
        /* Sanity check */
-       if ( ll_addr_len < ll_protocol->ll_addr_len ) {
+       if ( offsetof ( typeof ( *ll_addr_opt ),
+                       ll_addr[ll_protocol->ll_addr_len] ) > len ) {
                DBGC ( netdev, "NDP router advertisement link-layer address "
-                      "too short at %zd bytes (min %d bytes)\n",
-                      ll_addr_len, ll_protocol->ll_addr_len );
+                      "option too short at %zd bytes\n", len );
                return -EINVAL;
        }
 
        /* Define neighbour cache entry */
        if ( ( rc = neighbour_define ( netdev, &ipv6_protocol,
                                       &sin6_src->sin6_addr,
-                                      ll_addr ) ) != 0 ) {
+                                      ll_addr_opt->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 ) );
+                      ll_protocol->ntoa ( ll_addr_opt->ll_addr ),
+                      strerror ( rc ) );
                return rc;
        }
 
        return 0;
 }
 
+/**
+ * Process NDP router advertisement prefix information option
+ *
+ * @v netdev           Network device
+ * @v sin6_src         Source socket address
+ * @v ndp              NDP packet
+ * @v option           NDP option
+ * @v len              NDP option length
+ * @ret rc             Return status code
+ */
+static int
+ndp_rx_router_advertisement_prefix ( struct net_device *netdev,
+                                    struct sockaddr_in6 *sin6_src,
+                                    union ndp_header *ndp,
+                                    union ndp_option *option, size_t len ) {
+       struct ndp_router_advertisement_header *radv = &ndp->radv;
+       struct ndp_prefix_information_option *prefix_opt = &option->prefix;
+       struct in6_addr *router = &sin6_src->sin6_addr;
+       int rc;
+
+       /* Sanity check */
+       if ( sizeof ( *prefix_opt ) > len ) {
+               DBGC ( netdev, "NDP router advertisement prefix option too "
+                      "short at %zd bytes\n", len );
+               return -EINVAL;
+       }
+       DBGC ( netdev, "NDP found %sdefault router %s ",
+              ( radv->lifetime ? "" : "non-" ),
+              inet6_ntoa ( &sin6_src->sin6_addr ) );
+       DBGC ( netdev, "for %s-link %sautonomous prefix %s/%d\n",
+              ( ( prefix_opt->flags & NDP_PREFIX_ON_LINK ) ? "on" : "off" ),
+              ( ( prefix_opt->flags & NDP_PREFIX_AUTONOMOUS ) ? "" : "non-" ),
+              inet6_ntoa ( &prefix_opt->prefix ),
+              prefix_opt->prefix_len );
+
+       /* Perform stateless address autoconfiguration, if applicable */
+       if ( ( prefix_opt->flags &
+              ( NDP_PREFIX_ON_LINK | NDP_PREFIX_AUTONOMOUS ) ) ==
+            ( NDP_PREFIX_ON_LINK | NDP_PREFIX_AUTONOMOUS ) ) {
+               if ( ( rc = ipv6_slaac ( netdev, &prefix_opt->prefix,
+                                        prefix_opt->prefix_len,
+                                        ( radv->lifetime ?
+                                          router : NULL ) ) ) != 0 ) {
+                       DBGC ( netdev, "NDP could not autoconfigure prefix %s/"
+                              "%d: %s\n", inet6_ntoa ( &prefix_opt->prefix ),
+                              prefix_opt->prefix_len, strerror ( rc ) );
+                       return rc;
+               }
+       }
+
+       return 0;
+}
+
 /** An NDP option handler */
 struct ndp_option_handler {
        /** ICMPv6 type */
@@ -286,12 +346,12 @@ struct ndp_option_handler {
         * @v netdev            Network device
         * @v sin6_src          Source socket address
         * @v ndp               NDP packet
-        * @v value             Option value
-        * @v len               Option length
+        * @v option            NDP option
         * @ret rc              Return status code
         */
        int ( * rx ) ( struct net_device *netdev, struct sockaddr_in6 *sin6_src,
-                      union ndp_header *ndp, const void *value, size_t len );
+                      union ndp_header *ndp, union ndp_option *option,
+                      size_t len );
 };
 
 /** NDP option handlers */
@@ -311,6 +371,11 @@ static struct ndp_option_handler ndp_option_handlers[] = {
                .option_type = NDP_OPT_LL_SOURCE,
                .rx = ndp_rx_router_advertisement_ll_source,
        },
+       {
+               .icmp_type = ICMPV6_ROUTER_ADVERTISEMENT,
+               .option_type = NDP_OPT_PREFIX,
+               .rx = ndp_rx_router_advertisement_prefix,
+       },
 };
 
 /**
@@ -319,15 +384,13 @@ static struct ndp_option_handler ndp_option_handlers[] = {
  * @v netdev           Network device
  * @v sin6_src         Source socket address
  * @v ndp              NDP packet
- * @v type             Option type
- * @v value            Option value
+ * @v option           NDP option
  * @v len              Option length
  * @ret rc             Return status code
  */
 static int ndp_rx_option ( struct net_device *netdev,
-                          struct sockaddr_in6 *sin6_src,
-                          union ndp_header *ndp, unsigned int type,
-                          const void *value, size_t len ) {
+                          struct sockaddr_in6 *sin6_src, union ndp_header *ndp,
+                          union ndp_option *option, size_t len ) {
        struct ndp_option_handler *handler;
        unsigned int i;
 
@@ -336,9 +399,9 @@ static int ndp_rx_option ( struct net_device *netdev,
                            sizeof ( ndp_option_handlers[0] ) ) ; i++ ) {
                handler = &ndp_option_handlers[i];
                if ( ( handler->icmp_type == ndp->icmp.type ) &&
-                    ( handler->option_type == type ) ) {
+                    ( handler->option_type == option->header.type ) ) {
                        return handler->rx ( netdev, sin6_src, ndp,
-                                            value, len );
+                                            option, len );
                }
        }
 
@@ -360,10 +423,9 @@ static int ndp_rx ( struct io_buffer *iobuf,
                    struct sockaddr_in6 *sin6_src,
                    size_t offset ) {
        union ndp_header *ndp = iobuf->data;
-       struct ndp_option *option;
+       union ndp_option *option;
        size_t remaining;
        size_t option_len;
-       size_t option_value_len;
        int rc;
 
        /* Sanity check */
@@ -380,23 +442,21 @@ static int ndp_rx ( struct io_buffer *iobuf,
        while ( remaining ) {
 
                /* Sanity check */
-               if ( ( remaining < sizeof ( *option ) ) ||
-                    ( option->blocks == 0 ) ||
-                    ( remaining < ( option->blocks * NDP_OPTION_BLKSZ ) ) ) {
+               if ( ( remaining < sizeof ( option->header ) ) ||
+                    ( option->header.blocks == 0 ) ||
+                    ( remaining < ( option->header.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 ) );
+               option_len = ( option->header.blocks * NDP_OPTION_BLKSZ );
 
                /* Handle option */
-               if ( ( rc = ndp_rx_option ( netdev, sin6_src, ndp,
-                                           option->type, option->value,
-                                           option_value_len ) ) != 0 ) {
+               if ( ( rc = ndp_rx_option ( netdev, sin6_src, ndp, option,
+                                           option_len ) ) != 0 )
                        goto done;
-               }
 
                /* Move to next option */
                option = ( ( ( void * ) option ) + option_len );