diff --git a/src/include/ipxe/ip.h b/src/include/ipxe/ip.h index a2c5d4265..e2cd512ac 100644 --- a/src/include/ipxe/ip.h +++ b/src/include/ipxe/ip.h @@ -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 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 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. + * + * 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; diff --git a/src/net/ipv4.c b/src/net/ipv4.c index 425656f6c..ec96e4242 100644 --- a/src/net/ipv4.c +++ b/src/net/ipv4.c @@ -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 */ diff --git a/src/tests/ipv4_test.c b/src/tests/ipv4_test.c index a5aa4a4b1..63e1e1dc5 100644 --- a/src/tests/ipv4_test.c +++ b/src/tests/ipv4_test.c @@ -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 */ diff --git a/src/usr/route_ipv4.c b/src/usr/route_ipv4.c index 6260335ac..f79c0ad8f 100644 --- a/src/usr/route_ipv4.c +++ b/src/usr/route_ipv4.c @@ -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" ); }