[settings] Support IPv6 addresses and allow IPv6 nameservers in DNS
authorMatthew Iselin <matthew@theiselins.net>
Sat, 16 Jul 2011 11:11:00 +0000 (21:11 +1000)
committerMarty Connor <mdc@etherboot.org>
Thu, 21 Jul 2011 02:24:58 +0000 (22:24 -0400)
This will allow DHCPv6 to assign DNS nameservers for lookups.
I have chosen to allow DNS to perform lookups to either an IPv6 server
or an IPv4 server to avoid adding too much IPv6-dependent code to DNS.

When assigning an IPv6 address via the "ip6" setting, note that for most
uses a routable prefix (eg, 64) and gateway must be provided as well.

Signed-off-by: Matthew Iselin <matthew@theiselins.net>
Signed-off-by: Marty Connor <mdc@etherboot.org>
src/core/settings.c
src/include/gpxe/settings.h
src/net/ipv6.c
src/net/udp/dns.c

index 03e09d3..87ddc80 100644 (file)
@@ -661,6 +661,26 @@ int fetch_ipv4_setting ( struct settings *settings, struct setting *setting,
        return len;
 }
 
+/**
+ * Fetch value of IPv6 address setting
+ *
+ * @v settings         Settings block, or NULL to search all blocks
+ * @v setting          Setting to fetch
+ * @v inp              IPv4 address to fill in
+ * @ret len            Length of setting, or negative error
+ */
+int fetch_ipv6_setting ( struct settings *settings, struct setting *setting,
+                        struct in6_addr *inp ) {
+       int len;
+
+       len = fetch_setting ( settings, setting, inp, sizeof ( *inp ) );
+       if ( len < 0 )
+               return len;
+       if ( len < ( int ) sizeof ( *inp ) )
+               return -ERANGE;
+       return len;
+}
+
 /**
  * Fetch value of signed integer setting
  *
@@ -1146,6 +1166,49 @@ struct setting_type setting_type_ipv4 __setting_type = {
        .fetchf = fetchf_ipv4,
 };
 
+/**
+ * Parse and store value of IPv6 address setting
+ *
+ * @v settings         Settings block
+ * @v setting          Setting to store
+ * @v value            Formatted setting data
+ * @ret rc             Return status code
+ */
+static int storef_ipv6 ( struct settings *settings, struct setting *setting,
+                        const char *value ) {
+       struct in6_addr ipv6;
+
+       if ( inet6_aton ( value, &ipv6 ) == 0 )
+               return -EINVAL;
+       return store_setting ( settings, setting, &ipv6, sizeof ( ipv6 ) );
+}
+
+/**
+ * Fetch and format value of IPv4 address setting
+ *
+ * @v settings         Settings block, or NULL to search all blocks
+ * @v setting          Setting to fetch
+ * @v buf              Buffer to contain formatted value
+ * @v len              Length of buffer
+ * @ret len            Length of formatted value, or negative error
+ */
+static int fetchf_ipv6 ( struct settings *settings, struct setting *setting,
+                        char *buf, size_t len ) {
+       struct in6_addr ipv6;
+       int raw_len;
+
+       if ( ( raw_len = fetch_ipv6_setting ( settings, setting, &ipv6 ) ) < 0)
+               return raw_len;
+       return snprintf ( buf, len, "%s", inet6_ntoa ( ipv6 ) );
+}
+
+/** An IPv6 address setting type */
+struct setting_type setting_type_ipv6 __setting_type = {
+       .name = "ipv6",
+       .storef = storef_ipv6,
+       .fetchf = fetchf_ipv6,
+};
+
 /**
  * Parse and store value of integer setting
  *
index efefe73..14a3762 100644 (file)
@@ -16,6 +16,7 @@ FILE_LICENCE ( GPL2_OR_LATER );
 
 struct settings;
 struct in_addr;
+struct in6_addr;
 union uuid;
 
 /** A setting */
@@ -195,6 +196,8 @@ extern int fetch_string_setting_copy ( struct settings *settings,
                                       char **data );
 extern int fetch_ipv4_setting ( struct settings *settings,
                                struct setting *setting, struct in_addr *inp );
+extern int fetch_ipv6_setting ( struct settings *settings,
+                               struct setting *setting, struct in6_addr *inp );
 extern int fetch_int_setting ( struct settings *settings,
                               struct setting *setting, long *value );
 extern int fetch_uint_setting ( struct settings *settings,
@@ -219,6 +222,7 @@ extern int fetchf_named_setting ( const char *name, char *buf, size_t len );
 
 extern struct setting_type setting_type_string __setting_type;
 extern struct setting_type setting_type_ipv4 __setting_type;
+extern struct setting_type setting_type_ipv6 __setting_type;
 extern struct setting_type setting_type_int8 __setting_type;
 extern struct setting_type setting_type_int16 __setting_type;
 extern struct setting_type setting_type_int32 __setting_type;
@@ -229,9 +233,13 @@ extern struct setting_type setting_type_hex __setting_type;
 extern struct setting_type setting_type_uuid __setting_type;
 
 extern struct setting ip_setting __setting;
+extern struct setting ip6_setting __setting; /* Pre-defined IPv6 address */
 extern struct setting netmask_setting __setting;
+extern struct setting prefix_setting __setting; /* IPv6 address prefix */
 extern struct setting gateway_setting __setting;
+extern struct setting gateway6_setting __setting; /* IPv6 gateway */
 extern struct setting dns_setting __setting;
+extern struct setting dns6_setting __setting;
 extern struct setting domain_setting __setting;
 extern struct setting hostname_setting __setting;
 extern struct setting filename_setting __setting;
index bfd78ca..edae038 100644 (file)
@@ -14,6 +14,7 @@
 #include <gpxe/iobuf.h>
 #include <gpxe/netdevice.h>
 #include <gpxe/if_ether.h>
+#include <gpxe/dhcp6.h>
 
 struct net_protocol ipv6_protocol;
 
@@ -644,3 +645,94 @@ struct icmp6_net_protocol ipv6_icmp6_protocol __icmp6_net_protocol = {
        .check = ipv6_check,
 };
 
+
+
+/******************************************************************************
+ *
+ * Settings
+ *
+ ******************************************************************************
+ */
+
+/** IPv6 address setting */
+struct setting ip6_setting __setting = {
+       .name = "ip6",
+       .description = "IPv6 address",
+       .tag = DHCP6_OPT_IAADDR,
+       .type = &setting_type_ipv6,
+};
+
+/** IPv6 prefix setting */
+struct setting prefix_setting __setting = {
+       .name = "prefix",
+       .description = "IPv6 address prefix length",
+       .tag = 0,
+       .type = &setting_type_int16,
+};
+
+/** Default IPv6 gateway setting */
+struct setting gateway6_setting __setting = {
+       .name = "gateway6",
+       .description = "IPv6 Default gateway",
+       .tag = 0,
+       .type = &setting_type_ipv4,
+};
+
+/**
+ * Create IPv6 routes based on configured settings.
+ *
+ * @ret rc             Return status code
+ */
+static int ipv6_create_routes ( void ) {
+       struct ipv6_miniroute *miniroute;
+       struct ipv6_miniroute *tmp;
+       struct net_device *netdev;
+       struct settings *settings;
+       struct in6_addr address;
+       struct in6_addr gateway;
+       long prefix = 0;
+       int rc = 0;
+       
+       /* Create a route for each configured network device */
+       for_each_netdev ( netdev ) {
+               settings = netdev_settings ( netdev );
+       
+               /* Read the settings first. We may need to clear routes. */
+               fetch_ipv6_setting ( settings, &ip6_setting, &address );
+               fetch_ipv6_setting ( settings, &gateway6_setting, &gateway );
+               fetch_int_setting ( settings, &prefix_setting, &prefix );
+       
+               /* Sanity check! */
+               if ( ( prefix == 0 ) || ( prefix > 128 ) ) {
+                       DBG ( "dhcp6: attempt to apply settings without a valid prefix, ignoring\n" );
+                       return -EINVAL;
+               }
+       
+               /* Remove any existing routes for this address. */
+               list_for_each_entry_safe ( miniroute, tmp, &miniroutes, list ) {
+                       if ( ipv6_match_prefix ( &address,
+                                                &miniroute->prefix,
+                                                prefix ) ) {
+                               DBG ( "dhcp6: existing route for a configured setting, deleting\n" );
+                               del_ipv6_miniroute ( miniroute );
+                       }
+               }
+               
+               /* Configure route */
+               rc = add_ipv6_address ( netdev, address, prefix,
+                                              address, gateway );
+               if ( ! rc )
+                       return rc;
+       }
+       
+       return 0;
+}
+
+/** IPv6 settings applicator */
+struct settings_applicator ipv6_settings_applicator __settings_applicator = {
+       .apply = ipv6_create_routes,
+};
+
+/* Drag in ICMP6 and DHCP6 */
+REQUIRE_OBJECT ( icmpv6 );
+REQUIRE_OBJECT ( dhcp6 );
index cf513bf..5456600 100644 (file)
@@ -588,6 +588,14 @@ struct setting dns_setting __setting = {
        .type = &setting_type_ipv4,
 };
 
+/** IPv6 DNS server setting */
+struct setting dns6_setting __setting = {
+       .name = "dns6",
+       .description = "IPv6 DNS server",
+       .tag = 0,
+       .type = &setting_type_ipv6,
+};
+
 /** Domain name setting */
 struct setting domain_setting __setting = {
        .name = "domain",
@@ -604,10 +612,18 @@ struct setting domain_setting __setting = {
 static int apply_dns_settings ( void ) {
        struct sockaddr_in *sin_nameserver =
                ( struct sockaddr_in * ) &nameserver;
+       struct sockaddr_in6 *sin6_nameserver =
+               ( struct sockaddr_in6 * ) &nameserver;
        int len;
 
-       if ( ( len = fetch_ipv4_setting ( NULL, &dns_setting,
-                                         &sin_nameserver->sin_addr ) ) >= 0 ){
+       /* Favor IPv6 nameservers if they are available. */
+       if ( ( len = fetch_ipv6_setting ( NULL, &dns6_setting,
+                                         &sin6_nameserver->sin6_addr ) ) >= 0 ){
+               sin6_nameserver->sin_family = AF_INET6;
+               DBG ( "DNS using IPv6 nameserver %s\n",
+                     inet6_ntoa ( sin6_nameserver->sin6_addr ) );
+       } else if ( ( len = fetch_ipv4_setting ( NULL, &dns_setting,
+                                                &sin_nameserver->sin_addr ) ) >= 0 ){
                sin_nameserver->sin_family = AF_INET;
                DBG ( "DNS using nameserver %s\n",
                      inet_ntoa ( sin_nameserver->sin_addr ) );