From: Michael Brown Date: Tue, 10 Jun 2025 15:55:18 +0000 (+0100) Subject: [ipv4] Add support for classless static routes X-Git-Url: http://git.ipfire.org/gitweb/gitweb.cgi?a=commitdiff_plain;h=b5fb7353fa3856cee7e0a6760c2341ca617d6ef4;p=thirdparty%2Fipxe.git [ipv4] Add support for classless static routes Add support for RFC 3442 classless static routes provided via DHCP option 121. Originally-implemented-by: Hazel Smith Originally-implemented-by: Raphael Pour Signed-off-by: Michael Brown --- diff --git a/src/include/ipxe/dhcp.h b/src/include/ipxe/dhcp.h index 4d68d3ca5..43729d0c5 100644 --- a/src/include/ipxe/dhcp.h +++ b/src/include/ipxe/dhcp.h @@ -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 diff --git a/src/include/ipxe/settings.h b/src/include/ipxe/settings.h index 0301da12e..689e011d3 100644 --- a/src/include/ipxe/settings.h +++ b/src/include/ipxe/settings.h @@ -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 ); diff --git a/src/net/ipv4.c b/src/net/ipv4.c index ec96e4242..03517840b 100644 --- a/src/net/ipv4.c +++ b/src/net/ipv4.c @@ -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; } diff --git a/src/net/udp/dhcp.c b/src/net/udp/dhcp.c index 8e2e97f10..daa37b96b 100644 --- a/src/net/udp/dhcp.c +++ b/src/net/udp/dhcp.c @@ -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 diff --git a/src/tests/ipv4_test.c b/src/tests/ipv4_test.c index 63e1e1dc5..be2760027 100644 --- a/src/tests/ipv4_test.c +++ b/src/tests/ipv4_test.c @@ -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 */