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;
*
* @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 )
/* 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;
}
*
* @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 ) );
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
*
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;
}
}
*
* @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;
/** 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 */
"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 */
*/
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" );
}