]> git.ipfire.org Git - thirdparty/ipxe.git/commitdiff
[ipv4] Add support for classless static routes
authorMichael Brown <mcb30@ipxe.org>
Tue, 10 Jun 2025 15:55:18 +0000 (16:55 +0100)
committerMichael Brown <mcb30@ipxe.org>
Tue, 10 Jun 2025 17:22:32 +0000 (18:22 +0100)
Add support for RFC 3442 classless static routes provided via DHCP
option 121.

Originally-implemented-by: Hazel Smith <hazel.smith@leicester.ac.uk>
Originally-implemented-by: Raphael Pour <raphael.pour@hetzner.com>
Signed-off-by: Michael Brown <mcb30@ipxe.org>
src/include/ipxe/dhcp.h
src/include/ipxe/settings.h
src/net/ipv4.c
src/net/udp/dhcp.c
src/tests/ipv4_test.c

index 4d68d3ca5f1607fecd102d6d96a2a5d0e3cc5b67..43729d0c5c9acf277ce4a31da762f0ef7b150dd3 100644 (file)
@@ -344,6 +344,9 @@ struct dhcp_client_uuid {
 /** DNS domain search list */
 #define DHCP_DOMAIN_SEARCH 119
 
+/** Classless static routes */
+#define DHCP_STATIC_ROUTES 121
+
 /** Etherboot-specific encapsulated options
  *
  * This encapsulated options field is used to contain all options
index 0301da12e9b8eff6088e7d77c81ccc29e5d5129c..689e011d383581aa5a8ba25646ace9ea88c64681 100644 (file)
@@ -437,6 +437,8 @@ netmask_setting __setting ( SETTING_IP4, netmask );
 extern const struct setting
 gateway_setting __setting ( SETTING_IP4, gateway );
 extern const struct setting
+static_route_setting __setting ( SETTING_IP4, static_routes );
+extern const struct setting
 dns_setting __setting ( SETTING_IP4_EXTRA, dns );
 extern const struct setting
 ip6_setting __setting ( SETTING_IP6, ip6 );
index ec96e4242888500b763b8dd625109ceb51c04532..03517840b6d195cc5ddee3cc9759beb2f451187e 100644 (file)
@@ -134,36 +134,132 @@ static int ipv4_add_miniroute ( struct net_device *netdev,
        return 0;
 }
 
+/**
+ * Add static route minirouting table entries
+ *
+ * @v netdev           Network device
+ * @v address          IPv4 address
+ * @v routes           Static routes
+ * @v len              Length of static routes
+ * @ret rc             Return status code
+ */
+static int ipv4_add_static ( struct net_device *netdev, struct in_addr address,
+                            const void *routes, size_t len ) {
+       const struct {
+               struct in_addr address;
+       } __attribute__ (( packed )) *encoded;
+       struct in_addr netmask;
+       struct in_addr network;
+       struct in_addr gateway;
+       unsigned int width;
+       unsigned int masklen;
+       size_t remaining;
+       const void *data;
+       int rc;
+
+       /* Parse and add static routes */
+       for ( data = routes, remaining = len ; remaining ; ) {
+
+               /* Extract subnet mask width */
+               width = *( ( uint8_t * ) data );
+               data++;
+               remaining--;
+               masklen = ( ( width + 7 ) / 8 );
+
+               /* Check remaining length */
+               if ( ( masklen + sizeof ( gateway ) ) > remaining ) {
+                       DBGC ( netdev, "IPv4 invalid static route:\n" );
+                       DBGC_HDA ( netdev, 0, routes, len );
+                       return -EINVAL;
+               }
+
+               /* Calculate subnet mask */
+               if ( width ) {
+                       netmask.s_addr = htonl ( -1UL << ( 32 - width ) );
+               } else {
+                       netmask.s_addr = 0;
+               }
+
+               /* Extract network address */
+               encoded = data;
+               network.s_addr = ( encoded->address.s_addr & netmask.s_addr );
+               data += masklen;
+               remaining -= masklen;
+
+               /* Extract gateway address */
+               encoded = data;
+               gateway.s_addr = encoded->address.s_addr;
+               data += sizeof ( gateway );
+               remaining -= sizeof ( gateway );
+
+               /* Add route */
+               if ( ( rc = ipv4_add_miniroute ( netdev, address, network,
+                                                netmask, gateway ) ) != 0 )
+                       return rc;
+       }
+
+       return 0;
+}
+
 /**
  * Add IPv4 minirouting table entries
  *
  * @v netdev           Network device
  * @v address          IPv4 address
- * @v netmask          Subnet mask
- * @v gateway          Gateway address (if any)
  * @ret rc             Return status code
  */
 static int ipv4_add_miniroutes ( struct net_device *netdev,
-                                struct in_addr address,
-                                struct in_addr netmask,
-                                struct in_addr gateway ) {
+                                struct in_addr address ) {
+       struct settings *settings = netdev_settings ( netdev );
        struct in_addr none = { 0 };
+       struct in_addr netmask;
+       struct in_addr gateway;
        struct in_addr network;
+       void *routes;
+       int len;
        int rc;
 
+       /* Get subnet mask */
+       fetch_ipv4_setting ( settings, &netmask_setting, &netmask );
+
+       /* Calculate default netmask, if necessary */
+       if ( ! netmask.s_addr ) {
+               if ( IN_IS_CLASSA ( address.s_addr ) ) {
+                       netmask.s_addr = INADDR_NET_CLASSA;
+               } else if ( IN_IS_CLASSB ( address.s_addr ) ) {
+                       netmask.s_addr = INADDR_NET_CLASSB;
+               } else if ( IN_IS_CLASSC ( address.s_addr ) ) {
+                       netmask.s_addr = INADDR_NET_CLASSC;
+               }
+       }
+
+       /* Get default gateway, if present */
+       fetch_ipv4_setting ( settings, &gateway_setting, &gateway );
+
+       /* Get static routes, if present */
+       len = fetch_raw_setting_copy ( settings, &static_route_setting,
+                                      &routes );
+
        /* Add local address */
        network.s_addr = ( address.s_addr & netmask.s_addr );
        if ( ( rc = ipv4_add_miniroute ( netdev, address, network, netmask,
                                         none ) ) != 0 )
-               return rc;
-
-       /* Add default gateway, if applicable */
-       if ( gateway.s_addr &&
-            ( ( rc = ipv4_add_miniroute ( netdev, address, none, none,
-                                          gateway ) ) != 0 ) )
-               return rc;
+               goto done;
+
+       /* Add static routes or default gateway, as applicable */
+       if ( len >= 0 ) {
+               if ( ( rc = ipv4_add_static ( netdev, address, routes,
+                                             len ) ) != 0 )
+                       goto done;
+       } else if ( gateway.s_addr ) {
+               if ( ( rc = ipv4_add_miniroute ( netdev, address, none, none,
+                                                gateway ) ) != 0 )
+                       goto done;
+       }
 
-       return 0;
+ done:
+       free ( routes );
+       return rc;
 }
 
 /**
@@ -871,19 +967,24 @@ const struct setting gateway_setting __setting ( SETTING_IP4, gateway ) = {
        .type = &setting_type_ipv4,
 };
 
+/** Classless static routes setting */
+const struct setting static_route_setting __setting ( SETTING_IP4,
+                                                     static_routes ) = {
+       .name = "static-routes",
+       .description = "Static routes",
+       .tag = DHCP_STATIC_ROUTES,
+       .type = &setting_type_hex,
+};
+
 /**
  * Send gratuitous ARP, if applicable
  *
  * @v netdev           Network device
  * @v address          IPv4 address
- * @v netmask          Subnet mask
- * @v gateway          Gateway address (if any)
  * @ret rc             Return status code
  */
 static int ipv4_gratuitous_arp ( struct net_device *netdev,
-                                struct in_addr address,
-                                struct in_addr netmask __unused,
-                                struct in_addr gateway __unused ) {
+                                struct in_addr address ) {
        int rc;
 
        /* Do nothing if network device already has this IPv4 address */
@@ -910,14 +1011,10 @@ static int ipv4_gratuitous_arp ( struct net_device *netdev,
  * @ret rc             Return status code
  */
 static int ipv4_settings ( int ( * apply ) ( struct net_device *netdev,
-                                            struct in_addr address,
-                                            struct in_addr netmask,
-                                            struct in_addr gateway ) ) {
+                                            struct in_addr address ) ) {
        struct net_device *netdev;
        struct settings *settings;
-       struct in_addr address = { 0 };
-       struct in_addr netmask = { 0 };
-       struct in_addr gateway = { 0 };
+       struct in_addr address;
        int rc;
 
        /* Process settings for each network device */
@@ -927,30 +1024,12 @@ static int ipv4_settings ( int ( * apply ) ( struct net_device *netdev,
                settings = netdev_settings ( netdev );
 
                /* Get IPv4 address */
-               address.s_addr = 0;
                fetch_ipv4_setting ( settings, &ip_setting, &address );
                if ( ! address.s_addr )
                        continue;
 
-               /* Get subnet mask */
-               fetch_ipv4_setting ( settings, &netmask_setting, &netmask );
-
-               /* Calculate default netmask, if necessary */
-               if ( ! netmask.s_addr ) {
-                       if ( IN_IS_CLASSA ( address.s_addr ) ) {
-                               netmask.s_addr = INADDR_NET_CLASSA;
-                       } else if ( IN_IS_CLASSB ( address.s_addr ) ) {
-                               netmask.s_addr = INADDR_NET_CLASSB;
-                       } else if ( IN_IS_CLASSC ( address.s_addr ) ) {
-                               netmask.s_addr = INADDR_NET_CLASSC;
-                       }
-               }
-
-               /* Get default gateway, if present */
-               fetch_ipv4_setting ( settings, &gateway_setting, &gateway );
-
                /* Apply settings */
-               if ( ( rc = apply ( netdev, address, netmask, gateway ) ) != 0 )
+               if ( ( rc = apply ( netdev, address ) ) != 0 )
                        return rc;
        }
 
index 8e2e97f10254d8c27d91023b250342162500d0ee..daa37b96beddb8ae94b22304ae2c4ef44acdebc7 100644 (file)
@@ -94,7 +94,7 @@ static uint8_t dhcp_request_options_data[] = {
                      DHCP_ROOT_PATH, DHCP_MTU, DHCP_NTP_SERVERS,
                      DHCP_VENDOR_ENCAP, DHCP_VENDOR_CLASS_ID,
                      DHCP_TFTP_SERVER_NAME, DHCP_BOOTFILE_NAME,
-                     DHCP_DOMAIN_SEARCH,
+                     DHCP_DOMAIN_SEARCH, DHCP_STATIC_ROUTES,
                      128, 129, 130, 131, 132, 133, 134, 135, /* for PXE */
                      DHCP_EB_ENCAP, DHCP_ISCSI_INITIATOR_IQN ),
        DHCP_END
index 63e1e1dc5c2a981094c3525fce169d5f3f80ea81..be2760027f7f659a10c557e1e190de77ec40c38f 100644 (file)
@@ -214,6 +214,17 @@ TESTNET ( net4,
          { "ip", "192.168.86.1" },
          { "netmask", "255.255.240.0" } );
 
+/** net5: Static routes */
+TESTNET ( net5,
+         { "ip", "10.42.0.1" },
+         { "netmask", "255.255.0.0" },
+         { "gateway", "10.42.0.254" /* should be ignored */ },
+         { "static-routes",
+           "19:0a:2b:2b:80:0a:2a:2b:2b:" /* 10.43.43.128/25 via 10.42.43.43 */
+           "10:c0:a8:0a:2a:c0:a8:" /* 192.168.0.0/16 via 10.42.192.168 */
+           "18:c0:a8:00:00:00:00:00:" /* 192.168.0.0/24 on-link */
+           "00:0a:2a:01:01" /* default via 10.42.1.1 */ } );
+
 /**
  * Perform IPv4 self-tests
  *
@@ -327,6 +338,24 @@ static void ipv4_test_exec ( void ) {
        testnet_remove_ok ( &net2 );
        testnet_remove_ok ( &net1 );
        testnet_remove_ok ( &net0 );
+
+       /* Static routes */
+       testnet_ok ( &net5 );
+       ipv4_route_ok ( "10.42.99.0", NULL,
+                       "10.42.99.0", &net5, "10.42.0.1", 0 );
+       ipv4_route_ok ( "8.8.8.8", NULL,
+                       "10.42.1.1", &net5, "10.42.0.1", 0 );
+       ipv4_route_ok ( "10.43.43.1", NULL,
+                       "10.42.1.1", &net5, "10.42.0.1", 0 );
+       ipv4_route_ok ( "10.43.43.129", NULL,
+                       "10.42.43.43", &net5, "10.42.0.1", 0 );
+       ipv4_route_ok ( "192.168.54.8", NULL,
+                       "10.42.192.168", &net5, "10.42.0.1", 0 );
+       ipv4_route_ok ( "192.168.0.8", NULL,
+                       "192.168.0.8", &net5, "10.42.0.1", 0 );
+       ipv4_route_ok ( "192.168.0.255", NULL,
+                       "192.168.0.255", &net5, "10.42.0.1", 1 );
+       testnet_remove_ok ( &net5 );
 }
 
 /** IPv4 self-test */