]> git.ipfire.org Git - thirdparty/ipxe.git/commitdiff
[ipv4] Extend routing mechanism to handle non-default routes
authorMichael Brown <mcb30@ipxe.org>
Thu, 5 Jun 2025 15:49:42 +0000 (16:49 +0100)
committerMichael Brown <mcb30@ipxe.org>
Tue, 10 Jun 2025 12:54:15 +0000 (13:54 +0100)
Extend the definition of an IPv4 routing table entry to allow for the
expression of non-default gateways for specified off-link subnets, and
of on-link secondary subnets (where we can send directly to the
destination address even though our source address is not within the
subnet).

This more precise definition also allows us to correctly handle
routing in the (uncommon for iPXE) case when multiple network
interfaces are open concurrently and more than one interface has a
default gateway.

The common case of a single IPv4 address/netmask and a default gateway
now results in two routing table entries.  To retain backwards
compatibility with existing documentation (and to avoid on-screen
clutter), the "route" command prints default gateways on the same line
as the locally assigned address.  There is therefore no change in
output from the "route" command unless explicit additional (off-link
or on-link) routes are present.

Signed-off-by: Michael Brown <mcb30@ipxe.org>
src/include/ipxe/ip.h
src/net/ipv4.c
src/tests/ipv4_test.c
src/usr/route_ipv4.c

index a2c5d4265fe22a873bb1f641a1f162413dc596bd..e2cd512ac69b8c45eeb0a660178376259c77e258 100644 (file)
@@ -54,38 +54,83 @@ struct ipv4_pseudo_header {
        uint16_t len;
 };
 
-/** An IPv4 address/routing table entry */
+/** An IPv4 address/routing table entry
+ *
+ * Routing table entries are maintained in order of specificity.  For
+ * a given destination address, the first matching table entry will be
+ * used as the egress route.
+ */
 struct ipv4_miniroute {
        /** List of miniroutes */
        struct list_head list;
 
-       /** Network device */
+       /** Network device
+        *
+        * When this routing table entry is matched, this is the
+        * egress network device to be used.
+        */
        struct net_device *netdev;
 
-       /** IPv4 address */
+       /** IPv4 address
+        *
+        * When this routing table entry is matched, this is the
+        * source address to be used.
+        *
+        * The presence of this routing table entry also indicates
+        * that this address is a valid local destination address for
+        * the matching network device.
+        */
        struct in_addr address;
+       /** Subnet network address
+        *
+        * A subnet is a range of addresses defined by a network
+        * address and subnet mask.  A destination address with all of
+        * the subnet mask bits in common with the network address is
+        * within the subnet and therefore matches this routing table
+        * entry.
+        */
+       struct in_addr network;
        /** Subnet mask
         *
-        * An address with all of these bits in common with our IPv4
-        * address is in the local subnet.
+        * An address with all of these bits in common with the
+        * network address matches this routing table entry.
         */
        struct in_addr netmask;
+       /** Gateway address, or zero
+        *
+        * When this routing table entry is matched and this address
+        * is non-zero, it will be used as the next-hop address.
+        *
+        * When this routing table entry is matched and this address
+        * is zero, the subnet is local (on-link) and the next-hop
+        * address will be the original destination address.
+        */
+       struct in_addr gateway;
        /** Host mask
         *
-        * An address in the local subnet with all of these bits set
-        * to zero represents the network address, and an address in
-        * the local subnet with all of these bits set to one
-        * represents the directed broadcast address.  All other
-        * addresses in the local subnet are valid host addresses.
+        * An address in a local subnet with all of these bits set to
+        * zero represents the network address, and an address in a
+        * local subnet with all of these bits set to one represents
+        * the local directed broadcast address.  All other addresses
+        * in a local subnet are valid host addresses.
+        *
+        * For most local subnets, this is the inverse of the subnet
+        * mask.  In a small subnet (/31 or /32) there is no network
+        * address or directed broadcast address, and all addresses in
+        * the subnet are valid host addresses.
         *
-        * For most subnets, this is the inverse of the subnet mask.
-        * In a small subnet (/31 or /32) there is no network address
-        * or directed broadcast address, and all addresses in the
-        * subnet are valid host addresses.
+        * When this routing table entry is matched and the subnet is
+        * local, a next-hop address with all of these bits set to one
+        * will be treated as a local broadcast address.  All other
+        * next-hop addresses will be treated as unicast addresses.
+        *
+        * When this routing table entry is matched and the subnet is
+        * non-local, the next-hop address is always a unicast
+        * address.  The host mask for non-local subnets is therefore
+        * set to @c INADDR_NONE to allow the same logic to be used as
+        * for local subnets.
         */
        struct in_addr hostmask;
-       /** Gateway address, or zero for no gateway */
-       struct in_addr gateway;
 };
 
 extern struct list_head ipv4_miniroutes;
index 425656f6c86cd9651a36a32f39d0151426468120..ec96e4242888500b763b8dd625109ceb51c04532 100644 (file)
@@ -77,24 +77,32 @@ static struct profiler ipv4_rx_profiler __profiler = { .name = "ipv4.rx" };
  *
  * @v netdev           Network device
  * @v address          IPv4 address
+ * @v network          Subnet address
  * @v netmask          Subnet mask
  * @v gateway          Gateway address (if any)
  * @ret rc             Return status code
  */
-static int add_ipv4_miniroute ( struct net_device *netdev,
-                               struct in_addr address, struct in_addr netmask,
+static int ipv4_add_miniroute ( struct net_device *netdev,
+                               struct in_addr address,
+                               struct in_addr network,
+                               struct in_addr netmask,
                                struct in_addr gateway ) {
        struct ipv4_miniroute *miniroute;
+       struct ipv4_miniroute *before;
        struct in_addr hostmask;
        struct in_addr broadcast;
 
        /* Calculate host mask */
-       hostmask.s_addr = ( IN_IS_SMALL ( netmask.s_addr ) ?
-                           INADDR_NONE : ~netmask.s_addr );
-       broadcast.s_addr = ( address.s_addr | hostmask.s_addr );
+       if ( gateway.s_addr || IN_IS_SMALL ( netmask.s_addr ) ) {
+               hostmask.s_addr = INADDR_NONE;
+       } else {
+               hostmask.s_addr = ~netmask.s_addr;
+       }
+       broadcast.s_addr = ( network.s_addr | hostmask.s_addr );
 
        /* Print debugging information */
        DBGC ( netdev, "IPv4 add %s", inet_ntoa ( address ) );
+       DBGC ( netdev, " for %s", inet_ntoa ( network ) );
        DBGC ( netdev, "/%s ", inet_ntoa ( netmask ) );
        DBGC ( netdev, "bc %s ", inet_ntoa ( broadcast ) );
        if ( gateway.s_addr )
@@ -111,18 +119,49 @@ static int add_ipv4_miniroute ( struct net_device *netdev,
        /* Record routing information */
        miniroute->netdev = netdev_get ( netdev );
        miniroute->address = address;
+       miniroute->network = network;
        miniroute->netmask = netmask;
        miniroute->hostmask = hostmask;
        miniroute->gateway = gateway;
 
-       /* Add to end of list if we have a gateway, otherwise
-        * to start of list.
-        */
-       if ( gateway.s_addr ) {
-               list_add_tail ( &miniroute->list, &ipv4_miniroutes );
-       } else {
-               list_add ( &miniroute->list, &ipv4_miniroutes );
+       /* Add to routing table ahead of any less specific routes */
+       list_for_each_entry ( before, &ipv4_miniroutes, list ) {
+               if ( netmask.s_addr & ~before->netmask.s_addr )
+                       break;
        }
+       list_add_tail ( &miniroute->list, &before->list );
+
+       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 none = { 0 };
+       struct in_addr network;
+       int rc;
+
+       /* 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;
 
        return 0;
 }
@@ -132,10 +171,11 @@ static int add_ipv4_miniroute ( struct net_device *netdev,
  *
  * @v miniroute                Routing table entry
  */
-static void del_ipv4_miniroute ( struct ipv4_miniroute *miniroute ) {
+static void ipv4_del_miniroute ( struct ipv4_miniroute *miniroute ) {
        struct net_device *netdev = miniroute->netdev;
 
        DBGC ( netdev, "IPv4 del %s", inet_ntoa ( miniroute->address ) );
+       DBGC ( netdev, " for %s", inet_ntoa ( miniroute->network ) );
        DBGC ( netdev, "/%s ", inet_ntoa ( miniroute->netmask ) );
        if ( miniroute->gateway.s_addr )
                DBGC ( netdev, "gw %s ", inet_ntoa ( miniroute->gateway ) );
@@ -146,6 +186,19 @@ static void del_ipv4_miniroute ( struct ipv4_miniroute *miniroute ) {
        free ( miniroute );
 }
 
+/**
+ * Delete IPv4 minirouting table entries
+ *
+ */
+static void ipv4_del_miniroutes ( void ) {
+       struct ipv4_miniroute *miniroute;
+       struct ipv4_miniroute *tmp;
+
+       /* Delete all existing routes */
+       list_for_each_entry_safe ( miniroute, tmp, &ipv4_miniroutes, list )
+               ipv4_del_miniroute ( miniroute );
+}
+
 /**
  * Perform IPv4 routing
  *
@@ -170,27 +223,23 @@ struct ipv4_miniroute * ipv4_route ( unsigned int scope_id,
 
                if ( IN_IS_MULTICAST ( dest->s_addr ) ) {
 
-                       /* If destination is non-global, and the scope ID
-                        * matches this network device, then use this route.
+                       /* If destination is non-global, and the scope
+                        * ID matches this network device, then use
+                        * the first matching route.
                         */
                        if ( miniroute->netdev->scope_id == scope_id )
                                return miniroute;
 
                } else {
 
-                       /* If destination is an on-link global
-                        * address, then use this route.
+                       /* If destination is global, then use the
+                        * first matching route (via its gateway if
+                        * specified).
                         */
-                       if ( ( ( dest->s_addr ^ miniroute->address.s_addr )
-                              & miniroute->netmask.s_addr ) == 0 )
-                               return miniroute;
-
-                       /* If destination is an off-link global
-                        * address, and we have a default gateway,
-                        * then use this route.
-                        */
-                       if ( miniroute->gateway.s_addr ) {
-                               *dest = miniroute->gateway;
+                       if ( ( ( dest->s_addr ^ miniroute->network.s_addr )
+                              & miniroute->netmask.s_addr ) == 0 ) {
+                               if ( miniroute->gateway.s_addr )
+                                       *dest = miniroute->gateway;
                                return miniroute;
                        }
                }
@@ -913,20 +962,17 @@ static int ipv4_settings ( int ( * apply ) ( struct net_device *netdev,
  *
  * @ret rc             Return status code
  */
-static int ipv4_create_routes ( void ) {
-       struct ipv4_miniroute *miniroute;
-       struct ipv4_miniroute *tmp;
+static int ipv4_apply_routes ( void ) {
        int rc;
 
        /* Send gratuitous ARPs for any new IPv4 addresses */
        ipv4_settings ( ipv4_gratuitous_arp );
 
        /* Delete all existing routes */
-       list_for_each_entry_safe ( miniroute, tmp, &ipv4_miniroutes, list )
-               del_ipv4_miniroute ( miniroute );
+       ipv4_del_miniroutes();
 
-       /* Create a route for each configured network device */
-       if ( ( rc = ipv4_settings ( add_ipv4_miniroute ) ) != 0 )
+       /* Create routes for each configured network device */
+       if ( ( rc = ipv4_settings ( ipv4_add_miniroutes ) ) != 0 )
                return rc;
 
        return 0;
@@ -934,7 +980,7 @@ static int ipv4_create_routes ( void ) {
 
 /** IPv4 settings applicator */
 struct settings_applicator ipv4_settings_applicator __settings_applicator = {
-       .apply = ipv4_create_routes,
+       .apply = ipv4_apply_routes,
 };
 
 /* Drag in objects via ipv4_protocol */
index a5aa4a4b1758233fe588fcb76348e4351de112ad..63e1e1dc5c2a981094c3525fce169d5f3f80ea81 100644 (file)
@@ -297,6 +297,36 @@ static void ipv4_test_exec ( void ) {
                        "192.168.87.1", &net4, "192.168.86.1", 0 );
        ipv4_route_ok ( "192.168.96.1", NULL, NULL, NULL, NULL, 0 );
        testnet_remove_ok ( &net4 );
+
+       /* Multiple interfaces */
+       testnet_ok ( &net0 );
+       testnet_ok ( &net1 );
+       testnet_ok ( &net2 );
+       testnet_close_ok ( &net1 );
+       ipv4_route_ok ( "192.168.0.9", NULL,
+                       "192.168.0.9", &net0, "192.168.0.1", 0 );
+       ipv4_route_ok ( "10.31.31.1", NULL,
+                       "10.31.31.1", &net2, "10.31.31.0", 0 );
+       testnet_close_ok ( &net0 );
+       testnet_open_ok ( &net1 );
+       ipv4_route_ok ( "192.168.0.9", NULL,
+                       "192.168.0.9", &net1, "192.168.0.2", 0 );
+       ipv4_route_ok ( "10.31.31.1", NULL,
+                       "10.31.31.1", &net2, "10.31.31.0", 0 );
+       testnet_close_ok ( &net2 );
+       ipv4_route_ok ( "8.8.8.8", NULL,
+                       "192.168.0.254", &net1, "192.168.0.2", 0 );
+       testnet_close_ok ( &net1 );
+       testnet_open_ok ( &net0 );
+       ipv4_route_ok ( "8.8.8.8", NULL,
+                       "192.168.0.254", &net0, "192.168.0.1", 0 );
+       testnet_close_ok ( &net0 );
+       testnet_open_ok ( &net2 );
+       ipv4_route_ok ( "8.8.8.8", NULL,
+                       "10.31.31.1", &net2, "10.31.31.0", 0 );
+       testnet_remove_ok ( &net2 );
+       testnet_remove_ok ( &net1 );
+       testnet_remove_ok ( &net0 );
 }
 
 /** IPv4 self-test */
index 6260335acc383e41c05dba807c4b5e3b62c07130..f79c0ad8f45bc4b09a7b5ce1a98d35f14db09e38 100644 (file)
@@ -41,16 +41,51 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
  */
 static void route_ipv4_print ( struct net_device *netdev ) {
        struct ipv4_miniroute *miniroute;
+       struct ipv4_miniroute *defroute;
+       struct in_addr address;
+       struct in_addr network;
+       struct in_addr netmask;
+       struct in_addr gateway;
+       int remote;
 
+       /* Print routing table */
        list_for_each_entry ( miniroute, &ipv4_miniroutes, list ) {
+
+               /* Skip non-matching network devices */
                if ( miniroute->netdev != netdev )
                        continue;
-               printf ( "%s: %s/", netdev->name,
-                        inet_ntoa ( miniroute->address ) );
-               printf ( "%s", inet_ntoa ( miniroute->netmask ) );
-               if ( miniroute->gateway.s_addr )
-                       printf ( " gw %s", inet_ntoa ( miniroute->gateway ) );
-               if ( ! netdev_is_open ( miniroute->netdev ) )
+               address = miniroute->address;
+               network = miniroute->network;
+               netmask = miniroute->netmask;
+               gateway = miniroute->gateway;
+               assert ( ( network.s_addr & ~netmask.s_addr ) == 0 );
+
+               /* Defer default routes to be printed with local addresses */
+               if ( ! netmask.s_addr )
+                       continue;
+
+               /* Print local address and destination subnet */
+               remote = ( ( address.s_addr ^ network.s_addr ) &
+                          netmask.s_addr );
+               printf ( "%s: %s", netdev->name, inet_ntoa ( address ) );
+               if ( remote )
+                       printf ( " for %s", inet_ntoa ( network ) );
+               printf ( "/%s", inet_ntoa ( netmask ) );
+               if ( gateway.s_addr )
+                       printf ( " gw %s", inet_ntoa ( gateway ) );
+
+               /* Print default routes with local subnets */
+               list_for_each_entry ( defroute, &ipv4_miniroutes, list ) {
+                       if ( ( defroute->netdev == netdev ) &&
+                            ( defroute->address.s_addr = address.s_addr ) &&
+                            ( ! defroute->netmask.s_addr ) && ( ! remote ) ) {
+                               printf ( " gw %s",
+                                        inet_ntoa ( defroute->gateway ) );
+                       }
+               }
+
+               /* Print trailer */
+               if ( ! netdev_is_open ( netdev ) )
                        printf ( " (inaccessible)" );
                printf ( "\n" );
        }