From da5929189295c3d651648c94515f8e837edc815d Mon Sep 17 00:00:00 2001 From: Timo Gatsonides Date: Sun, 4 Jan 2026 12:10:56 +0100 Subject: [PATCH 1/5] Add support for DHCP option 121 --- src/core/ipv4/dhcp.c | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/core/ipv4/dhcp.c b/src/core/ipv4/dhcp.c index 6698031a..bfb3e0c9 100644 --- a/src/core/ipv4/dhcp.c +++ b/src/core/ipv4/dhcp.c @@ -82,6 +82,9 @@ #include +/* nanos: DHCP Option 121 (Classless Static Routes) support */ +#include + #ifdef LWIP_HOOK_FILENAME #include LWIP_HOOK_FILENAME #endif @@ -170,6 +173,8 @@ static u8_t dhcp_discover_request_options[] = { #if LWIP_DHCP_GET_NTP_SRV , DHCP_OPTION_NTP #endif /* LWIP_DHCP_GET_NTP_SRV */ + /* nanos: request Option 121 (Classless Static Routes) per RFC 3442 */ + , DHCP_OPTION_CLASSLESS_STATIC_ROUTE }; #ifdef DHCP_GLOBAL_XID @@ -733,7 +738,9 @@ dhcp_handle_ack(struct netif *netif, struct dhcp_msg *msg_in) } /* gateway router */ - if (dhcp_option_given(dhcp, DHCP_OPTION_IDX_ROUTER)) { + /* RFC 3442: If Option 121 is present, MUST ignore Option 3 (Router) */ + if (dhcp_option_given(dhcp, DHCP_OPTION_IDX_ROUTER) && + !dhcp_option121_received(netif)) { ip4_addr_set_u32(&dhcp->offered_gw_addr, lwip_htonl(dhcp_get_option_value(dhcp, DHCP_OPTION_IDX_ROUTER))); } @@ -791,6 +798,9 @@ void dhcp_cleanup(struct netif *netif) LWIP_ASSERT_CORE_LOCKED(); LWIP_ASSERT("netif != NULL", netif != NULL); + /* nanos: clear Option 121 routes before cleaning up DHCP state */ + dhcp_option121_clear(netif); + SYS_ARCH_LOCK(&dhcp_mutex); if (netif_dhcp_data(netif) != NULL) { mem_free(netif_dhcp_data(netif)); @@ -1363,6 +1373,9 @@ dhcp_release_and_stop(struct netif *netif) return; } + /* nanos: clear Option 121 routes on release/stop */ + dhcp_option121_clear(netif); + ip_addr_copy(server_ip_addr, dhcp->server_ip_addr); /* clean old DHCP offer */ @@ -1836,11 +1849,16 @@ dhcp_recv(void *arg, struct udp_pcb *pcb, struct pbuf *p, struct ip_globals *ip_ goto free_pbuf_and_return; } /* option fields could be unfold? */ + /* nanos: set current netif for DHCP option hooks (e.g., Option 121) */ + extern struct netif *nanos_dhcp_current_netif; + nanos_dhcp_current_netif = netif; if (dhcp_parse_reply(p, dhcp) != ERR_OK) { + nanos_dhcp_current_netif = NULL; LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_LEVEL_SERIOUS, ("problem unfolding DHCP message - too short on memory?\n")); goto free_pbuf_and_return; } + nanos_dhcp_current_netif = NULL; LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE, ("searching DHCP_OPTION_MESSAGE_TYPE\n")); /* obtain pointer to DHCP message type */ From 879f2276ee529c877cebb95ec36e33903722a408 Mon Sep 17 00:00:00 2001 From: Timo Gatsonides Date: Fri, 20 Mar 2026 17:24:22 +0100 Subject: [PATCH 2/5] net: make DHCP Option 121 a native compile-time feature Move route table and option parser from nanos into lwip, gated by LWIP_DHCP_CLASSLESS_STATIC_ROUTES. Pass netif to dhcp_parse_reply() to eliminate the nanos_dhcp_current_netif workaround. --- src/core/init.c | 9 + src/core/ipv4/dhcp.c | 45 ++-- src/core/ipv4/dhcp_classless_route.c | 230 +++++++++++++++++ src/core/ipv4/ip4_route_table.c | 326 ++++++++++++++++++++++++ src/include/lwip/dhcp_classless_route.h | 99 +++++++ src/include/lwip/ip4_route_table.h | 138 ++++++++++ src/include/lwip/opt.h | 27 ++ src/include/lwip/prot/dhcp.h | 1 + 8 files changed, 860 insertions(+), 15 deletions(-) create mode 100644 src/core/ipv4/dhcp_classless_route.c create mode 100644 src/core/ipv4/ip4_route_table.c create mode 100644 src/include/lwip/dhcp_classless_route.h create mode 100644 src/include/lwip/ip4_route_table.h diff --git a/src/core/init.c b/src/core/init.c index b3737a35..81aea6bd 100644 --- a/src/core/init.c +++ b/src/core/init.c @@ -58,6 +58,11 @@ #include "lwip/mld6.h" #include "lwip/api.h" +#if LWIP_DHCP_CLASSLESS_STATIC_ROUTES +#include "lwip/ip4_route_table.h" +#include "lwip/dhcp_classless_route.h" +#endif /* LWIP_DHCP_CLASSLESS_STATIC_ROUTES */ + #include "netif/ppp/ppp_opts.h" #include "netif/ppp/ppp_impl.h" @@ -370,6 +375,10 @@ lwip_init(void) #if LWIP_DNS dns_init(); #endif /* LWIP_DNS */ +#if LWIP_DHCP_CLASSLESS_STATIC_ROUTES + ip4_route_table_init(); + dhcp_classless_route_init(); +#endif /* LWIP_DHCP_CLASSLESS_STATIC_ROUTES */ #if PPP_SUPPORT ppp_init(); #endif diff --git a/src/core/ipv4/dhcp.c b/src/core/ipv4/dhcp.c index bfb3e0c9..78a93627 100644 --- a/src/core/ipv4/dhcp.c +++ b/src/core/ipv4/dhcp.c @@ -82,8 +82,9 @@ #include -/* nanos: DHCP Option 121 (Classless Static Routes) support */ -#include +#if LWIP_DHCP_CLASSLESS_STATIC_ROUTES +#include "lwip/dhcp_classless_route.h" +#endif #ifdef LWIP_HOOK_FILENAME #include LWIP_HOOK_FILENAME @@ -173,8 +174,10 @@ static u8_t dhcp_discover_request_options[] = { #if LWIP_DHCP_GET_NTP_SRV , DHCP_OPTION_NTP #endif /* LWIP_DHCP_GET_NTP_SRV */ +#if LWIP_DHCP_CLASSLESS_STATIC_ROUTES /* nanos: request Option 121 (Classless Static Routes) per RFC 3442 */ , DHCP_OPTION_CLASSLESS_STATIC_ROUTE +#endif /* LWIP_DHCP_CLASSLESS_STATIC_ROUTES */ }; #ifdef DHCP_GLOBAL_XID @@ -738,9 +741,12 @@ dhcp_handle_ack(struct netif *netif, struct dhcp_msg *msg_in) } /* gateway router */ - /* RFC 3442: If Option 121 is present, MUST ignore Option 3 (Router) */ - if (dhcp_option_given(dhcp, DHCP_OPTION_IDX_ROUTER) && - !dhcp_option121_received(netif)) { + if (dhcp_option_given(dhcp, DHCP_OPTION_IDX_ROUTER) +#if LWIP_DHCP_CLASSLESS_STATIC_ROUTES + /* RFC 3442: If Option 121 is present, MUST ignore Option 3 (Router) */ + && !dhcp_classless_route_received(netif) +#endif /* LWIP_DHCP_CLASSLESS_STATIC_ROUTES */ + ) { ip4_addr_set_u32(&dhcp->offered_gw_addr, lwip_htonl(dhcp_get_option_value(dhcp, DHCP_OPTION_IDX_ROUTER))); } @@ -798,8 +804,10 @@ void dhcp_cleanup(struct netif *netif) LWIP_ASSERT_CORE_LOCKED(); LWIP_ASSERT("netif != NULL", netif != NULL); +#if LWIP_DHCP_CLASSLESS_STATIC_ROUTES /* nanos: clear Option 121 routes before cleaning up DHCP state */ - dhcp_option121_clear(netif); + dhcp_classless_route_clear(netif); +#endif /* LWIP_DHCP_CLASSLESS_STATIC_ROUTES */ SYS_ARCH_LOCK(&dhcp_mutex); if (netif_dhcp_data(netif) != NULL) { @@ -1373,8 +1381,10 @@ dhcp_release_and_stop(struct netif *netif) return; } +#if LWIP_DHCP_CLASSLESS_STATIC_ROUTES /* nanos: clear Option 121 routes on release/stop */ - dhcp_option121_clear(netif); + dhcp_classless_route_clear(netif); +#endif /* LWIP_DHCP_CLASSLESS_STATIC_ROUTES */ ip_addr_copy(server_ip_addr, dhcp->server_ip_addr); @@ -1549,7 +1559,7 @@ dhcp_option_hostname(u16_t options_out_len, u8_t *options, struct netif *netif) * */ static err_t -dhcp_parse_reply(struct pbuf *p, struct dhcp *dhcp) +dhcp_parse_reply(struct pbuf *p, struct dhcp *dhcp, struct netif *netif) { u8_t *options; u16_t offset; @@ -1678,10 +1688,20 @@ dhcp_parse_reply(struct pbuf *p, struct dhcp *dhcp) LWIP_ERROR("len == 4", len == 4, return ERR_VAL;); decode_idx = DHCP_OPTION_IDX_T2; break; +#if LWIP_DHCP_CLASSLESS_STATIC_ROUTES + case (DHCP_OPTION_CLASSLESS_STATIC_ROUTE): + /* nanos: parse classless static routes (RFC 3442), only from ACK */ + if (dhcp_option_given(dhcp, DHCP_OPTION_IDX_MSG_TYPE) && + (dhcp_get_option_value(dhcp, DHCP_OPTION_IDX_MSG_TYPE) == DHCP_ACK)) { + dhcp_parse_classless_routes(netif, q, val_offset, len); + } + decode_len = 0; + break; +#endif /* LWIP_DHCP_CLASSLESS_STATIC_ROUTES */ default: decode_len = 0; LWIP_DEBUGF(DHCP_DEBUG, ("skipping option %"U16_F" in options\n", (u16_t)op)); - LWIP_HOOK_DHCP_PARSE_OPTION(ip_current_netif(), dhcp, dhcp->state, msg_in, + LWIP_HOOK_DHCP_PARSE_OPTION(netif, dhcp, dhcp->state, msg_in, dhcp_option_given(dhcp, DHCP_OPTION_IDX_MSG_TYPE) ? (u8_t)dhcp_get_option_value(dhcp, DHCP_OPTION_IDX_MSG_TYPE) : 0, op, len, q, val_offset); break; @@ -1849,16 +1869,11 @@ dhcp_recv(void *arg, struct udp_pcb *pcb, struct pbuf *p, struct ip_globals *ip_ goto free_pbuf_and_return; } /* option fields could be unfold? */ - /* nanos: set current netif for DHCP option hooks (e.g., Option 121) */ - extern struct netif *nanos_dhcp_current_netif; - nanos_dhcp_current_netif = netif; - if (dhcp_parse_reply(p, dhcp) != ERR_OK) { - nanos_dhcp_current_netif = NULL; + if (dhcp_parse_reply(p, dhcp, netif) != ERR_OK) { LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_LEVEL_SERIOUS, ("problem unfolding DHCP message - too short on memory?\n")); goto free_pbuf_and_return; } - nanos_dhcp_current_netif = NULL; LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE, ("searching DHCP_OPTION_MESSAGE_TYPE\n")); /* obtain pointer to DHCP message type */ diff --git a/src/core/ipv4/dhcp_classless_route.c b/src/core/ipv4/dhcp_classless_route.c new file mode 100644 index 00000000..6ea226b0 --- /dev/null +++ b/src/core/ipv4/dhcp_classless_route.c @@ -0,0 +1,230 @@ +/** + * @file + * DHCP Option 121 (Classless Static Routes) Parser - RFC 3442 + * + * Parses classless static routes from DHCP responses and populates + * the IPv4 static route table. + */ + +/* + * Copyright (c) 2026 Timo Gatsonides + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT + * SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT + * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING + * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + * + * Author: Timo Gatsonides + */ + +#include "lwip/opt.h" + +#if LWIP_DHCP_CLASSLESS_STATIC_ROUTES + +#include "lwip/dhcp_classless_route.h" +#include "lwip/ip4_route_table.h" +#include "lwip/prot/dhcp.h" +#include "lwip/sys.h" +#include "lwip/pbuf.h" +#include "lwip/netif.h" +#include "lwip/def.h" + +#include + +/* Per-interface tracking of whether Option 121 was received */ +static u8_t option121_received[LWIP_DHCP_CLASSLESS_ROUTE_MAX_NETIFS]; + +/* Lock for thread-safe access to option121_received state */ +static sys_lock_t option121_lock; + +/* Get a safe index for the option121_received array, returns -1 if out of bounds */ +static inline int +get_netif_idx(struct netif *netif) +{ + u8_t idx; + if (netif == NULL) + return -1; + idx = netif_get_index(netif); + if (idx == 0 || idx > LWIP_DHCP_CLASSLESS_ROUTE_MAX_NETIFS) + return -1; + return idx - 1; +} + +/* Calculate significant octets for a prefix length: ceil(prefix_len / 8) */ +static inline u8_t +prefix_to_octets(u8_t prefix_len) +{ + return (prefix_len + 7) / 8; +} + +void +dhcp_classless_route_init(void) +{ + SYS_ARCH_LOCK_INIT(&option121_lock); + memset(option121_received, 0, sizeof(option121_received)); +} + +int +dhcp_parse_classless_routes(struct netif *netif, struct pbuf *p, + u16_t offset, u8_t len) +{ + int routes_parsed = 0; + u16_t pos = offset; + u16_t end; + int netif_idx; + + if (netif == NULL || p == NULL) + return ERR_ARG; /* ERR_ARG is already negative */ + + netif_idx = get_netif_idx(netif); + if (netif_idx < 0) + return ERR_ARG; + + /* overflow check */ + if (offset > 0xFFFF - len) + return ERR_ARG; + end = offset + len; + + /* empty option is valid but contains no routes */ + if (len == 0) + return 0; + + /* + * Clear any existing DHCP routes for this interface first. + * This ensures we replace old routes with new ones on lease renewal. + */ + ip4_route_remove_dhcp(netif); + + /* + * Parse each route entry in the option. + * Format per RFC 3442: + * 1 byte: prefix length (0-32) + * N bytes: significant octets of destination (N = ceil(prefix_len/8)) + * 4 bytes: gateway IP address + */ + while (pos < end) { + u8_t prefix_len; + u8_t significant_octets; + ip4_addr_t dest; + ip4_addr_t gateway; + u8_t dest_bytes[4] = {0, 0, 0, 0}; + + /* read prefix length */ + if (pbuf_copy_partial(p, &prefix_len, 1, pos) != 1) + break; + pos++; + + if (prefix_len > 32) + break; + + significant_octets = prefix_to_octets(prefix_len); + + /* check we have enough data remaining */ + if (pos + significant_octets + 4 > end) + break; + + /* read destination network (significant octets only) */ + if (significant_octets > 0) { + if (pbuf_copy_partial(p, dest_bytes, significant_octets, pos) + != significant_octets) + break; + } + pos += significant_octets; + + /* + * Reconstruct destination address. + * The significant octets are the high-order bytes of the address. + * dest_bytes is already zero-initialized for non-significant octets. + */ + dest.addr = (dest_bytes[0] << 24) | (dest_bytes[1] << 16) | + (dest_bytes[2] << 8) | dest_bytes[3]; + dest.addr = lwip_htonl(dest.addr); + + /* apply mask to ensure destination is properly masked (RFC 3442) */ + dest.addr &= ip4_prefix_to_mask(prefix_len); + + /* read gateway address - already in network byte order */ + if (pbuf_copy_partial(p, &gateway.addr, 4, pos) != 4) + break; + pos += 4; + + /* + * Validate gateway address: + * - Reject multicast/broadcast (invalid as next-hop) + * - 0.0.0.0 is valid per RFC 3442: means destination is on-link + * (directly reachable on this interface without a gateway) + */ + if (ip4_addr_ismulticast(&gateway) || + ip4_addr_isbroadcast(&gateway, netif)) + continue; + + /* add route to table */ + if (ip4_route_add(&dest, prefix_len, &gateway, netif, + IP4_ROUTE_FLAG_DHCP) == ERR_OK) { + routes_parsed++; + } + } + + /* + * Mark that this interface received Option 121. + * This is used to determine whether to ignore Option 3 (Router). + */ + if (routes_parsed > 0) { + SYS_ARCH_LOCK(&option121_lock); + option121_received[netif_idx] = 1; + SYS_ARCH_UNLOCK(&option121_lock); + } + + return routes_parsed; +} + +u8_t +dhcp_classless_route_received(struct netif *netif) +{ + u8_t result = 0; + int netif_idx = get_netif_idx(netif); + + if (netif_idx < 0) + return 0; + + SYS_ARCH_LOCK(&option121_lock); + result = (option121_received[netif_idx] != 0); + SYS_ARCH_UNLOCK(&option121_lock); + + return result; +} + +void +dhcp_classless_route_clear(struct netif *netif) +{ + int netif_idx = get_netif_idx(netif); + + if (netif_idx < 0) + return; + + SYS_ARCH_LOCK(&option121_lock); + option121_received[netif_idx] = 0; + SYS_ARCH_UNLOCK(&option121_lock); + + ip4_route_remove_dhcp(netif); +} + +#endif /* LWIP_DHCP_CLASSLESS_STATIC_ROUTES */ diff --git a/src/core/ipv4/ip4_route_table.c b/src/core/ipv4/ip4_route_table.c new file mode 100644 index 00000000..3565ad14 --- /dev/null +++ b/src/core/ipv4/ip4_route_table.c @@ -0,0 +1,326 @@ +/** + * @file + * IPv4 static route table implementation + * + * Provides a fixed-size routing table with longest-prefix-match lookup, + * primarily for DHCP Option 121 (RFC 3442) classless static routes. + */ + +/* + * Copyright (c) 2026 Timo Gatsonides + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT + * SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT + * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING + * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + * + * Author: Timo Gatsonides + */ + +#include "lwip/opt.h" + +#if LWIP_DHCP_CLASSLESS_STATIC_ROUTES + +#include "lwip/ip4_route_table.h" +#include "lwip/def.h" +#include "lwip/sys.h" + +#include + +/* Route table storage - sorted by prefix_len descending for LPM */ +static struct ip4_route_entry route_table[LWIP_IPV4_NUM_ROUTE_ENTRIES]; + +/* Lock for thread-safe access */ +static sys_lock_t route_lock; + +/* Number of active entries in the table */ +static int route_entry_count; + +/* Compute netmask from prefix length in network byte order */ +u32_t +ip4_prefix_to_mask(u8_t prefix_len) +{ + if (prefix_len == 0) + return 0; + if (prefix_len >= 32) + return 0xFFFFFFFF; + return PP_HTONL(~((1UL << (32 - prefix_len)) - 1)); +} + +/* Check if a destination address matches a route entry */ +static inline int +route_matches(const struct ip4_route_entry *entry, const ip4_addr_t *dest) +{ + u32_t mask = ip4_prefix_to_mask(entry->prefix_len); + return (dest->addr & mask) == (entry->dest.addr & mask); +} + +/* Find insertion point to maintain sorted order (descending prefix_len) */ +static int +find_insert_position(u8_t prefix_len) +{ + int i; + for (i = 0; i < route_entry_count; i++) { + if (prefix_len > route_table[i].prefix_len) + return i; + } + return route_entry_count; +} + +/* Find an existing route matching dest/prefix_len/netif, returns index or -1 */ +static int +find_existing_route(const ip4_addr_t *dest, u8_t prefix_len, struct netif *netif) +{ + u32_t mask = ip4_prefix_to_mask(prefix_len); + u32_t masked_dest = dest->addr & mask; + int i; + + for (i = 0; i < route_entry_count; i++) { + if (route_table[i].prefix_len == prefix_len && + (route_table[i].dest.addr & mask) == masked_dest && + (netif == NULL || route_table[i].netif == netif)) { + return i; + } + } + return -1; +} + +void +ip4_route_table_init(void) +{ + SYS_ARCH_LOCK_INIT(&route_lock); + memset(route_table, 0, sizeof(route_table)); + route_entry_count = 0; +} + +err_t +ip4_route_add(const ip4_addr_t *dest, u8_t prefix_len, + const ip4_addr_t *gateway, struct netif *netif, u8_t flags) +{ + err_t ret = ERR_OK; + int existing; + int pos; + int i; + u32_t mask; + + if (dest == NULL || gateway == NULL || netif == NULL) + return ERR_ARG; + if (prefix_len > IP4_MAX_PREFIX_LEN) + return ERR_ARG; + + SYS_ARCH_LOCK(&route_lock); + + /* check for existing route with same dest/prefix/netif and update it */ + existing = find_existing_route(dest, prefix_len, netif); + if (existing >= 0) { + ip4_addr_copy(route_table[existing].gateway, *gateway); + route_table[existing].flags = flags; + goto out; + } + + if (route_entry_count >= LWIP_IPV4_NUM_ROUTE_ENTRIES) { + ret = ERR_MEM; + goto out; + } + + /* find insertion point to maintain sorted order */ + pos = find_insert_position(prefix_len); + + /* shift entries down to make room */ + for (i = route_entry_count; i > pos; i--) { + memcpy(&route_table[i], &route_table[i - 1], + sizeof(struct ip4_route_entry)); + } + + /* insert new entry */ + mask = ip4_prefix_to_mask(prefix_len); + route_table[pos].dest.addr = dest->addr & mask; + ip4_addr_copy(route_table[pos].gateway, *gateway); + route_table[pos].prefix_len = prefix_len; + route_table[pos].flags = flags; + route_table[pos].netif = netif; + route_entry_count++; + +out: + SYS_ARCH_UNLOCK(&route_lock); + return ret; +} + +void +ip4_route_remove(const ip4_addr_t *dest, u8_t prefix_len, struct netif *netif) +{ + int idx; + int i; + + if (dest == NULL || prefix_len > IP4_MAX_PREFIX_LEN) + return; + + SYS_ARCH_LOCK(&route_lock); + + idx = find_existing_route(dest, prefix_len, netif); + if (idx >= 0) { + for (i = idx; i < route_entry_count - 1; i++) { + memcpy(&route_table[i], &route_table[i + 1], + sizeof(struct ip4_route_entry)); + } + memset(&route_table[route_entry_count - 1], 0, + sizeof(struct ip4_route_entry)); + route_entry_count--; + } + + SYS_ARCH_UNLOCK(&route_lock); +} + +void +ip4_route_remove_netif(struct netif *netif) +{ + int i; + int j; + + if (netif == NULL) + return; + + SYS_ARCH_LOCK(&route_lock); + + i = 0; + while (i < route_entry_count) { + if (route_table[i].netif == netif) { + for (j = i; j < route_entry_count - 1; j++) { + memcpy(&route_table[j], &route_table[j + 1], + sizeof(struct ip4_route_entry)); + } + memset(&route_table[route_entry_count - 1], 0, + sizeof(struct ip4_route_entry)); + route_entry_count--; + } else { + i++; + } + } + + SYS_ARCH_UNLOCK(&route_lock); +} + +void +ip4_route_remove_dhcp(struct netif *netif) +{ + int i; + int j; + + if (netif == NULL) + return; + + SYS_ARCH_LOCK(&route_lock); + + i = 0; + while (i < route_entry_count) { + if (route_table[i].netif == netif && + (route_table[i].flags & IP4_ROUTE_FLAG_DHCP)) { + for (j = i; j < route_entry_count - 1; j++) { + memcpy(&route_table[j], &route_table[j + 1], + sizeof(struct ip4_route_entry)); + } + memset(&route_table[route_entry_count - 1], 0, + sizeof(struct ip4_route_entry)); + route_entry_count--; + } else { + i++; + } + } + + SYS_ARCH_UNLOCK(&route_lock); +} + +u8_t +ip4_route_find(const ip4_addr_t *dest, struct ip4_route_entry *out_entry) +{ + u8_t found = 0; + int i; + + if (dest == NULL) + return 0; + + SYS_ARCH_LOCK(&route_lock); + + /* + * Table is sorted by prefix_len descending, so the first match + * is the longest prefix match. + */ + for (i = 0; i < route_entry_count; i++) { + if (route_table[i].netif != NULL && route_matches(&route_table[i], dest)) { + if (out_entry != NULL) + memcpy(out_entry, &route_table[i], sizeof(*out_entry)); + found = 1; + break; + } + } + + SYS_ARCH_UNLOCK(&route_lock); + return found; +} + +struct netif * +ip4_static_route(const ip4_addr_t *src, const ip4_addr_t *dest) +{ + struct ip4_route_entry entry; + LWIP_UNUSED_ARG(src); + + if (ip4_route_find(dest, &entry)) + return entry.netif; + return NULL; +} + +u8_t +ip4_get_gateway(const ip4_addr_t *dest, ip4_addr_t *out_gateway) +{ + struct ip4_route_entry entry; + + if (ip4_route_find(dest, &entry)) { + if (out_gateway != NULL) + ip4_addr_copy(*out_gateway, entry.gateway); + return 1; + } + return 0; +} + +/* + * Get read-only access to route table for debugging/netlink. + * Note: Caller must not hold route_lock. The returned pointer is valid + * but entries may change if routes are modified concurrently. + */ +const struct ip4_route_entry * +ip4_get_route_table(int *count) +{ + if (count != NULL) + *count = route_entry_count; + return route_table; +} + +int +ip4_route_count(void) +{ + int count; + SYS_ARCH_LOCK(&route_lock); + count = route_entry_count; + SYS_ARCH_UNLOCK(&route_lock); + return count; +} + +#endif /* LWIP_DHCP_CLASSLESS_STATIC_ROUTES */ diff --git a/src/include/lwip/dhcp_classless_route.h b/src/include/lwip/dhcp_classless_route.h new file mode 100644 index 00000000..ac3bd5d0 --- /dev/null +++ b/src/include/lwip/dhcp_classless_route.h @@ -0,0 +1,99 @@ +/** + * @file + * DHCP Option 121 (Classless Static Routes) Parser - RFC 3442 + * + * Parses DHCP Option 121 responses and populates the IPv4 static route + * table with classless static routes provided by the DHCP server. + */ + +/* + * Copyright (c) 2026 Timo Gatsonides + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT + * SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT + * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING + * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + * + * Author: Timo Gatsonides + */ + +#ifndef LWIP_HDR_DHCP_CLASSLESS_ROUTE_H +#define LWIP_HDR_DHCP_CLASSLESS_ROUTE_H + +#include "lwip/opt.h" + +#if LWIP_DHCP_CLASSLESS_STATIC_ROUTES + +#include "lwip/pbuf.h" +#include "lwip/netif.h" +#include "lwip/err.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** Initialize the Option 121 module - must be called before other functions */ +void dhcp_classless_route_init(void); + +/** + * Parse DHCP Option 121 data and add routes to the static route table. + * + * Parses variable-length encoding per RFC 3442: + * 1 byte: prefix length (0-32) + * N bytes: significant octets of destination (N = ceil(prefix_len/8)) + * 4 bytes: gateway IP address + * + * Routes are added with IP4_ROUTE_FLAG_DHCP flag. Existing DHCP routes + * for the interface are cleared first. + * + * @param netif network interface that received the DHCP response + * @param p pbuf containing the option data + * @param offset offset into pbuf where option data starts + * @param len length of option data + * @return number of routes successfully added (>= 0), or negative error + */ +int dhcp_parse_classless_routes(struct netif *netif, struct pbuf *p, + u16_t offset, u8_t len); + +/** + * Check if Option 121 was received for an interface. + * Per RFC 3442, if Option 121 is present, Option 3 (Router) must be ignored. + * + * @param netif network interface to check + * @return 1 if Option 121 was received, 0 otherwise + */ +u8_t dhcp_classless_route_received(struct netif *netif); + +/** + * Clear Option 121 state and routes for a network interface. + * Called when DHCP lease is released, interface is removed, etc. + * + * @param netif network interface to clear + */ +void dhcp_classless_route_clear(struct netif *netif); + +#ifdef __cplusplus +} +#endif + +#endif /* LWIP_DHCP_CLASSLESS_STATIC_ROUTES */ + +#endif /* LWIP_HDR_DHCP_CLASSLESS_ROUTE_H */ diff --git a/src/include/lwip/ip4_route_table.h b/src/include/lwip/ip4_route_table.h new file mode 100644 index 00000000..29daaf40 --- /dev/null +++ b/src/include/lwip/ip4_route_table.h @@ -0,0 +1,138 @@ +/** + * @file + * IPv4 static route table for DHCP Option 121 (Classless Static Routes) + * + * Provides a static routing table for IPv4 with longest-prefix-match lookups. + * Primarily used to store routes learned from DHCP Option 121 (RFC 3442). + */ + +/* + * Copyright (c) 2026 Timo Gatsonides + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT + * SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT + * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING + * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + * + * Author: Timo Gatsonides + */ + +#ifndef LWIP_HDR_IP4_ROUTE_TABLE_H +#define LWIP_HDR_IP4_ROUTE_TABLE_H + +#include "lwip/opt.h" + +#if LWIP_DHCP_CLASSLESS_STATIC_ROUTES + +#include "lwip/ip4_addr.h" +#include "lwip/err.h" +#include "lwip/netif.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define IP4_MAX_PREFIX_LEN 32 + +/* Route entry flags */ +#define IP4_ROUTE_FLAG_NONE 0x00 +#define IP4_ROUTE_FLAG_DHCP 0x01 /* Route learned from DHCP Option 121 */ +#define IP4_ROUTE_FLAG_STATIC 0x02 /* Manually configured static route */ + +struct ip4_route_entry { + ip4_addr_t dest; /**< Destination network address (masked) */ + ip4_addr_t gateway; /**< Next-hop gateway IP address */ + u8_t prefix_len; /**< CIDR prefix length (0-32) */ + u8_t flags; /**< Route flags (IP4_ROUTE_FLAG_*) */ + struct netif *netif; /**< Associated network interface (NULL = unused entry) */ +}; + +/** Compute netmask from prefix length in network byte order */ +u32_t ip4_prefix_to_mask(u8_t prefix_len); + +/** Initialize the route table - must be called before other functions */ +void ip4_route_table_init(void); + +/** + * Add a route to the static route table. + * Table is kept sorted by prefix length (longest first) for LPM lookups. + * + * @param dest destination network address + * @param prefix_len CIDR prefix length (0-32) + * @param gateway next-hop gateway IP address + * @param netif associated network interface + * @param flags route flags (IP4_ROUTE_FLAG_*) + * @return ERR_OK on success, ERR_MEM if table full, ERR_ARG if invalid args + */ +err_t ip4_route_add(const ip4_addr_t *dest, u8_t prefix_len, + const ip4_addr_t *gateway, struct netif *netif, u8_t flags); + +/** Remove a specific route from the table */ +void ip4_route_remove(const ip4_addr_t *dest, u8_t prefix_len, struct netif *netif); + +/** Remove all routes associated with a network interface */ +void ip4_route_remove_netif(struct netif *netif); + +/** Remove all DHCP-learned routes for a network interface */ +void ip4_route_remove_dhcp(struct netif *netif); + +/** + * Find the best matching route for a destination address (longest prefix match). + * Copies route entry to out_entry if found. + * + * @param dest destination IP address to look up + * @param out_entry pointer to route entry to fill (may be NULL) + * @return 1 if route found, 0 otherwise + */ +u8_t ip4_route_find(const ip4_addr_t *dest, struct ip4_route_entry *out_entry); + +/** + * Route lookup hook for lwIP integration (LWIP_HOOK_IP4_ROUTE_SRC). + * + * @param src source IPv4 address (may be NULL) + * @param dest destination IPv4 address + * @return network interface to use, or NULL if no static route matches + */ +struct netif *ip4_static_route(const ip4_addr_t *src, const ip4_addr_t *dest); + +/** + * Get the gateway address for a destination from the static route table. + * Copies gateway to out_gateway if found. + * + * @param dest destination IP address to look up + * @param out_gateway pointer to store the gateway address (may be NULL) + * @return 1 if route found, 0 otherwise + */ +u8_t ip4_get_gateway(const ip4_addr_t *dest, ip4_addr_t *out_gateway); + +/** Get read-only access to route table for debugging/netlink reporting */ +const struct ip4_route_entry *ip4_get_route_table(int *count); + +/** Get the number of active routes in the table */ +int ip4_route_count(void); + +#ifdef __cplusplus +} +#endif + +#endif /* LWIP_DHCP_CLASSLESS_STATIC_ROUTES */ + +#endif /* LWIP_HDR_IP4_ROUTE_TABLE_H */ diff --git a/src/include/lwip/opt.h b/src/include/lwip/opt.h index ce5d7876..e9a55aee 100644 --- a/src/include/lwip/opt.h +++ b/src/include/lwip/opt.h @@ -961,6 +961,33 @@ #if !defined LWIP_DHCP_MAX_DNS_SERVERS || defined __DOXYGEN__ #define LWIP_DHCP_MAX_DNS_SERVERS DNS_MAX_SERVERS #endif + +/** + * LWIP_DHCP_CLASSLESS_STATIC_ROUTES==1: Enable DHCP Option 121 (RFC 3442) + * Classless Static Routes support. When enabled, the DHCP client requests + * and processes Option 121, adding static routes to the IPv4 route table. + * Per RFC 3442, if Option 121 is present, Option 3 (Router) is ignored. + * Requires LWIP_DHCP to be enabled. + */ +#if !defined LWIP_DHCP_CLASSLESS_STATIC_ROUTES || defined __DOXYGEN__ +#define LWIP_DHCP_CLASSLESS_STATIC_ROUTES 0 +#endif + +/** + * LWIP_DHCP_CLASSLESS_ROUTE_MAX_NETIFS: Maximum number of network interfaces + * tracked for Option 121 state. Should match or exceed the system interface count. + */ +#if !defined LWIP_DHCP_CLASSLESS_ROUTE_MAX_NETIFS || defined __DOXYGEN__ +#define LWIP_DHCP_CLASSLESS_ROUTE_MAX_NETIFS 16 +#endif + +/** + * LWIP_IPV4_NUM_ROUTE_ENTRIES: Maximum number of entries in the IPv4 static + * route table. Used when LWIP_DHCP_CLASSLESS_STATIC_ROUTES is enabled. + */ +#if !defined LWIP_IPV4_NUM_ROUTE_ENTRIES || defined __DOXYGEN__ +#define LWIP_IPV4_NUM_ROUTE_ENTRIES 16 +#endif /** * @} */ diff --git a/src/include/lwip/prot/dhcp.h b/src/include/lwip/prot/dhcp.h index ab18dca3..1a573031 100644 --- a/src/include/lwip/prot/dhcp.h +++ b/src/include/lwip/prot/dhcp.h @@ -141,6 +141,7 @@ typedef enum { #define DHCP_OPTION_BROADCAST 28 #define DHCP_OPTION_TCP_TTL 37 #define DHCP_OPTION_NTP 42 +#define DHCP_OPTION_CLASSLESS_STATIC_ROUTE 121 /* RFC 3442 */ #define DHCP_OPTION_END 255 /* DHCP options */ From cf7b45112b80ccea9094273144a6c2803b67647b Mon Sep 17 00:00:00 2001 From: Timo Gatsonides Date: Fri, 20 Mar 2026 21:11:36 +0100 Subject: [PATCH 3/5] net: wire up static route table directly in ip4 routing instead of via hook --- src/core/ipv4/ip4.c | 25 ++++++++++++++++++++++--- src/include/lwip/ip4.h | 2 +- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/src/core/ipv4/ip4.c b/src/core/ipv4/ip4.c index 74bfddc4..5b182079 100644 --- a/src/core/ipv4/ip4.c +++ b/src/core/ipv4/ip4.c @@ -57,6 +57,10 @@ #include "lwip/stats.h" #include "lwip/prot/iana.h" +#if LWIP_DHCP_CLASSLESS_STATIC_ROUTES +#include "lwip/ip4_route_table.h" +#endif + #include #ifdef LWIP_HOOK_FILENAME @@ -130,7 +134,7 @@ ip4_set_default_multicast_netif(struct netif *default_multicast_netif) } #endif /* LWIP_MULTICAST_TX_OPTIONS */ -#ifdef LWIP_HOOK_IP4_ROUTE_SRC +#if defined(LWIP_HOOK_IP4_ROUTE_SRC) || LWIP_DHCP_CLASSLESS_STATIC_ROUTES /** * Source based IPv4 routing must be fully implemented in * LWIP_HOOK_IP4_ROUTE_SRC(). This function only provides the parameters. @@ -139,15 +143,24 @@ struct netif * ip4_route_src(const ip4_addr_t *src, const ip4_addr_t *dest) { if (src != NULL) { + struct netif *netif; /* when src==NULL, the hook is called from ip4_route(dest) */ - struct netif *netif = LWIP_HOOK_IP4_ROUTE_SRC(src, dest); +#if LWIP_DHCP_CLASSLESS_STATIC_ROUTES + netif = ip4_static_route(src, dest); + if (netif != NULL) { + return netif; + } +#endif /* LWIP_DHCP_CLASSLESS_STATIC_ROUTES */ +#ifdef LWIP_HOOK_IP4_ROUTE_SRC + netif = LWIP_HOOK_IP4_ROUTE_SRC(src, dest); if (netif != NULL) { return netif; } +#endif /* LWIP_HOOK_IP4_ROUTE_SRC */ } return ip4_route(dest); } -#endif /* LWIP_HOOK_IP4_ROUTE_SRC */ +#endif /* LWIP_HOOK_IP4_ROUTE_SRC || LWIP_DHCP_CLASSLESS_STATIC_ROUTES */ static u8_t ip4_route_netif(struct netif *n, void *priv) { @@ -240,6 +253,12 @@ ip4_route(const ip4_addr_t *dest) } #endif /* LWIP_NETIF_LOOPBACK && !LWIP_HAVE_LOOPIF */ +#if LWIP_DHCP_CLASSLESS_STATIC_ROUTES + netif = ip4_static_route(NULL, dest); + if (netif != NULL) { + return netif; + } +#endif /* LWIP_DHCP_CLASSLESS_STATIC_ROUTES */ #ifdef LWIP_HOOK_IP4_ROUTE_SRC netif = LWIP_HOOK_IP4_ROUTE_SRC(NULL, dest); if (netif != NULL) { diff --git a/src/include/lwip/ip4.h b/src/include/lwip/ip4.h index 397d8441..1d22e44e 100644 --- a/src/include/lwip/ip4.h +++ b/src/include/lwip/ip4.h @@ -52,7 +52,7 @@ extern "C" { #endif -#ifdef LWIP_HOOK_IP4_ROUTE_SRC +#if defined(LWIP_HOOK_IP4_ROUTE_SRC) || LWIP_DHCP_CLASSLESS_STATIC_ROUTES #define LWIP_IPV4_SRC_ROUTING 1 #else #define LWIP_IPV4_SRC_ROUTING 0 From 35f60a0f2f51fd18bc7caf013529e2a5a7a39a0f Mon Sep 17 00:00:00 2001 From: Timo Gatsonides Date: Tue, 31 Mar 2026 10:14:30 +0200 Subject: [PATCH 4/5] net: use static route gateway in etharp output for DHCP option 121 routes Also removes some references to nanos in comments. --- src/core/ipv4/dhcp.c | 10 +++++----- src/core/ipv4/etharp.c | 11 +++++++++++ 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/src/core/ipv4/dhcp.c b/src/core/ipv4/dhcp.c index 78a93627..07fd6966 100644 --- a/src/core/ipv4/dhcp.c +++ b/src/core/ipv4/dhcp.c @@ -175,7 +175,7 @@ static u8_t dhcp_discover_request_options[] = { , DHCP_OPTION_NTP #endif /* LWIP_DHCP_GET_NTP_SRV */ #if LWIP_DHCP_CLASSLESS_STATIC_ROUTES - /* nanos: request Option 121 (Classless Static Routes) per RFC 3442 */ + /* request Option 121 (Classless Static Routes) per RFC 3442 */ , DHCP_OPTION_CLASSLESS_STATIC_ROUTE #endif /* LWIP_DHCP_CLASSLESS_STATIC_ROUTES */ }; @@ -805,7 +805,7 @@ void dhcp_cleanup(struct netif *netif) LWIP_ASSERT("netif != NULL", netif != NULL); #if LWIP_DHCP_CLASSLESS_STATIC_ROUTES - /* nanos: clear Option 121 routes before cleaning up DHCP state */ + /* clear Option 121 routes before cleaning up DHCP state */ dhcp_classless_route_clear(netif); #endif /* LWIP_DHCP_CLASSLESS_STATIC_ROUTES */ @@ -1382,7 +1382,7 @@ dhcp_release_and_stop(struct netif *netif) } #if LWIP_DHCP_CLASSLESS_STATIC_ROUTES - /* nanos: clear Option 121 routes on release/stop */ + /* clear Option 121 routes on release/stop */ dhcp_classless_route_clear(netif); #endif /* LWIP_DHCP_CLASSLESS_STATIC_ROUTES */ @@ -1690,7 +1690,7 @@ dhcp_parse_reply(struct pbuf *p, struct dhcp *dhcp, struct netif *netif) break; #if LWIP_DHCP_CLASSLESS_STATIC_ROUTES case (DHCP_OPTION_CLASSLESS_STATIC_ROUTE): - /* nanos: parse classless static routes (RFC 3442), only from ACK */ + /* parse classless static routes (RFC 3442), only from ACK */ if (dhcp_option_given(dhcp, DHCP_OPTION_IDX_MSG_TYPE) && (dhcp_get_option_value(dhcp, DHCP_OPTION_IDX_MSG_TYPE) == DHCP_ACK)) { dhcp_parse_classless_routes(netif, q, val_offset, len); @@ -1808,7 +1808,7 @@ dhcp_parse_reply(struct pbuf *p, struct dhcp *dhcp, struct netif *netif) /* make sure the string is really NULL-terminated */ dhcp->boot_file_name[DHCP_FILE_LEN-1] = 0; } -#endif /* LWIP_DHCP_BOOTP_FILE */ +#endif /* LWIP_DHCP_BOOTP_FILE */ return ERR_OK; } diff --git a/src/core/ipv4/etharp.c b/src/core/ipv4/etharp.c index d1ac0135..58f4ad20 100644 --- a/src/core/ipv4/etharp.c +++ b/src/core/ipv4/etharp.c @@ -55,6 +55,10 @@ #include "lwip/prot/iana.h" #include "netif/ethernet.h" +#if LWIP_DHCP_CLASSLESS_STATIC_ROUTES +#include "lwip/ip4_route_table.h" +#endif + #include #ifdef LWIP_HOOK_FILENAME @@ -858,6 +862,13 @@ etharp_output(struct netif *netif, struct pbuf *q, const ip4_addr_t *ipaddr) if (!ip4_addr_islinklocal(&iphdr->src)) #endif /* LWIP_AUTOIP */ { +#if LWIP_DHCP_CLASSLESS_STATIC_ROUTES + /* check static route table for a per-route gateway */ + ip4_addr_t static_gw; + if (ip4_get_gateway(ipaddr, &static_gw)) { + dst_addr = &static_gw; + } else +#endif /* LWIP_DHCP_CLASSLESS_STATIC_ROUTES */ #ifdef LWIP_HOOK_ETHARP_GET_GW /* For advanced routing, a single default gateway might not be enough, so get the IP address of the gateway to handle the current destination address. */ From cd19a6bf50e4534e3868137fe4c75771839261af Mon Sep 17 00:00:00 2001 From: Timo Gatsonides Date: Tue, 12 May 2026 15:13:38 +0200 Subject: [PATCH 5/5] DHCP Option 121: address review feedback Remove dhcp_classless_route.c; move parser into dhcp.c as static function. Replace option121_received array with ip4_route_exists(). Use LWIP_ERROR, lwip_htonl where appropriate. --- src/core/init.c | 5 +- src/core/ipv4/dhcp.c | 147 +++++++++++++-- src/core/ipv4/dhcp_classless_route.c | 230 ------------------------ src/core/ipv4/etharp.c | 6 +- src/core/ipv4/ip4_route_table.c | 122 ++++++------- src/include/lwip/dhcp_classless_route.h | 99 ---------- src/include/lwip/ip4_route_table.h | 25 +-- src/include/lwip/opt.h | 10 +- 8 files changed, 207 insertions(+), 437 deletions(-) delete mode 100644 src/core/ipv4/dhcp_classless_route.c delete mode 100644 src/include/lwip/dhcp_classless_route.h diff --git a/src/core/init.c b/src/core/init.c index 81aea6bd..471c1ec2 100644 --- a/src/core/init.c +++ b/src/core/init.c @@ -60,7 +60,6 @@ #if LWIP_DHCP_CLASSLESS_STATIC_ROUTES #include "lwip/ip4_route_table.h" -#include "lwip/dhcp_classless_route.h" #endif /* LWIP_DHCP_CLASSLESS_STATIC_ROUTES */ #include "netif/ppp/ppp_opts.h" @@ -99,6 +98,9 @@ PACK_STRUCT_END #if (!LWIP_UDP && LWIP_DHCP) #error "If you want to use DHCP, you have to define LWIP_UDP=1 in your lwipopts.h" #endif +#if (LWIP_DHCP_CLASSLESS_STATIC_ROUTES && !LWIP_DHCP) +#error "If you want to use LWIP_DHCP_CLASSLESS_STATIC_ROUTES, you have to define LWIP_DHCP=1 in your lwipopts.h" +#endif #if (!LWIP_UDP && !LWIP_RAW && LWIP_MULTICAST_TX_OPTIONS) #error "If you want to use LWIP_MULTICAST_TX_OPTIONS, you have to define LWIP_UDP=1 and/or LWIP_RAW=1 in your lwipopts.h" #endif @@ -377,7 +379,6 @@ lwip_init(void) #endif /* LWIP_DNS */ #if LWIP_DHCP_CLASSLESS_STATIC_ROUTES ip4_route_table_init(); - dhcp_classless_route_init(); #endif /* LWIP_DHCP_CLASSLESS_STATIC_ROUTES */ #if PPP_SUPPORT ppp_init(); diff --git a/src/core/ipv4/dhcp.c b/src/core/ipv4/dhcp.c index 07fd6966..ee60f2c0 100644 --- a/src/core/ipv4/dhcp.c +++ b/src/core/ipv4/dhcp.c @@ -83,7 +83,7 @@ #include #if LWIP_DHCP_CLASSLESS_STATIC_ROUTES -#include "lwip/dhcp_classless_route.h" +#include "lwip/ip4_route_table.h" #endif #ifdef LWIP_HOOK_FILENAME @@ -197,6 +197,108 @@ static u8_t dhcp_pcb_refcount; static sys_lock_t dhcp_mutex; +#if LWIP_DHCP_CLASSLESS_STATIC_ROUTES +/* Calculate significant octets for a prefix length: ceil(prefix_len / 8) */ +static u8_t +prefix_to_octets(u8_t prefix_len) +{ + return (prefix_len + 7) / 8; +} + +/** + * Parse DHCP Option 121 data and add routes to the static route table. + * RFC 3442 variable-length encoding: + * 1 byte: prefix length (0-32) + * N bytes: significant octets of destination (N = ceil(prefix_len/8)) + * 4 bytes: gateway IP address + */ +static int +dhcp_parse_classless_routes(struct netif *netif, struct pbuf *opts_pbuf, + u16_t offset, u8_t len) +{ + int routes_parsed = 0; + u16_t pos = offset; + u16_t end; + + /* overflow check */ + if (offset > 0xFFFF - len) + return -1; + end = offset + len; + + /* empty option is valid but contains no routes */ + if (len == 0) + return 0; + + /* Clear existing DHCP routes for this interface first (lease renewal) */ + ip4_route_remove_netif(netif, IP4_ROUTE_FLAG_DHCP); + + while (pos < end) { + u8_t prefix_len; + u8_t significant_octets; + ip4_addr_t dest; + ip4_addr_t gateway; + u8_t dest_bytes[4] = {0, 0, 0, 0}; + + /* read prefix length */ + if (pbuf_copy_partial(opts_pbuf, &prefix_len, 1, pos) != 1) + break; + pos++; + + if (prefix_len > IP4_MAX_PREFIX_LEN) + break; + + significant_octets = prefix_to_octets(prefix_len); + + /* check we have enough data remaining */ + if (pos + significant_octets + sizeof(gateway.addr) > end) + break; + + /* read destination network (significant octets only) */ + if (significant_octets > 0) { + if (pbuf_copy_partial(opts_pbuf, dest_bytes, significant_octets, pos) + != significant_octets) + break; + } + pos += significant_octets; + + /* + * Reconstruct destination address. + * The significant octets are the high-order bytes of the address. + * dest_bytes is already zero-initialized for non-significant octets. + */ + dest.addr = (dest_bytes[0] << 24) | (dest_bytes[1] << 16) | + (dest_bytes[2] << 8) | dest_bytes[3]; + dest.addr = lwip_htonl(dest.addr); + + /* apply mask to ensure destination is properly masked (RFC 3442) */ + dest.addr &= ip4_prefix_to_mask(prefix_len); + + /* read gateway address - already in network byte order */ + if (pbuf_copy_partial(opts_pbuf, &gateway.addr, sizeof(gateway.addr), pos) + != sizeof(gateway.addr)) + break; + pos += sizeof(gateway.addr); + + /* + * Validate gateway address: + * - Reject multicast/broadcast (invalid as next-hop) + * - 0.0.0.0 is valid per RFC 3442: means destination is on-link + */ + if (ip4_addr_ismulticast(&gateway) || + ip4_addr_isbroadcast(&gateway, netif)) + continue; + + /* add route to table */ + if (ip4_route_add(&dest, prefix_len, &gateway, netif, + IP4_ROUTE_FLAG_DHCP) == ERR_OK) { + routes_parsed++; + } + } + + return routes_parsed; +} +#endif /* LWIP_DHCP_CLASSLESS_STATIC_ROUTES */ + /* DHCP client state machine functions */ static err_t dhcp_discover(struct netif *netif); static err_t dhcp_select(struct netif *netif); @@ -295,6 +397,9 @@ dhcp_handle_nak(struct netif *netif) dhcp_set_state(dhcp, DHCP_STATE_BACKING_OFF); /* remove IP address from interface (must no longer be used, as per RFC2131) */ netif_set_addr(netif, IP4_ADDR_ANY4, IP4_ADDR_ANY4, IP4_ADDR_ANY4); +#if LWIP_DHCP_CLASSLESS_STATIC_ROUTES + ip4_route_remove_netif(netif, IP4_ROUTE_FLAG_DHCP); +#endif /* LWIP_DHCP_CLASSLESS_STATIC_ROUTES */ /* We can immediately restart discovery */ dhcp_discover(netif); } @@ -744,7 +849,7 @@ dhcp_handle_ack(struct netif *netif, struct dhcp_msg *msg_in) if (dhcp_option_given(dhcp, DHCP_OPTION_IDX_ROUTER) #if LWIP_DHCP_CLASSLESS_STATIC_ROUTES /* RFC 3442: If Option 121 is present, MUST ignore Option 3 (Router) */ - && !dhcp_classless_route_received(netif) + && !ip4_route_exists(netif, IP4_ROUTE_FLAG_DHCP) #endif /* LWIP_DHCP_CLASSLESS_STATIC_ROUTES */ ) { ip4_addr_set_u32(&dhcp->offered_gw_addr, lwip_htonl(dhcp_get_option_value(dhcp, DHCP_OPTION_IDX_ROUTER))); @@ -805,8 +910,7 @@ void dhcp_cleanup(struct netif *netif) LWIP_ASSERT("netif != NULL", netif != NULL); #if LWIP_DHCP_CLASSLESS_STATIC_ROUTES - /* clear Option 121 routes before cleaning up DHCP state */ - dhcp_classless_route_clear(netif); + ip4_route_remove_netif(netif, IP4_ROUTE_FLAG_DHCP); #endif /* LWIP_DHCP_CLASSLESS_STATIC_ROUTES */ SYS_ARCH_LOCK(&dhcp_mutex); @@ -1167,10 +1271,24 @@ dhcp_bind(struct netif *netif) ip4_addr_copy(gw_addr, dhcp->offered_gw_addr); /* gateway address not given? */ if (ip4_addr_isany_val(gw_addr)) { - /* copy network address */ - ip4_addr_get_network(&gw_addr, &dhcp->offered_ip_addr, &sn_mask); - /* use first host address on network as gateway */ - ip4_addr_set_u32(&gw_addr, ip4_addr_get_u32(&gw_addr) | PP_HTONL(0x00000001UL)); +#if LWIP_DHCP_CLASSLESS_STATIC_ROUTES + /* + * RFC 3442: If Option 121 is present, use the gateway from the default + * route (0/0) if one was provided. Do not synthesize a gateway — if no + * default route was included in Option 121, there must be none. + */ + if (ip4_route_exists(netif, IP4_ROUTE_FLAG_DHCP)) { + ip4_addr_t default_dest; + ip4_addr_set_zero(&default_dest); + ip4_get_gateway(netif, &default_dest, &gw_addr); + } else +#endif /* LWIP_DHCP_CLASSLESS_STATIC_ROUTES */ + { + /* copy network address */ + ip4_addr_get_network(&gw_addr, &dhcp->offered_ip_addr, &sn_mask); + /* use first host address on network as gateway */ + ip4_addr_set_u32(&gw_addr, ip4_addr_get_u32(&gw_addr) | PP_HTONL(0x00000001UL)); + } } #if LWIP_DHCP_AUTOIP_COOP @@ -1382,8 +1500,7 @@ dhcp_release_and_stop(struct netif *netif) } #if LWIP_DHCP_CLASSLESS_STATIC_ROUTES - /* clear Option 121 routes on release/stop */ - dhcp_classless_route_clear(netif); + ip4_route_remove_netif(netif, IP4_ROUTE_FLAG_DHCP); #endif /* LWIP_DHCP_CLASSLESS_STATIC_ROUTES */ ip_addr_copy(server_ip_addr, dhcp->server_ip_addr); @@ -1690,9 +1807,13 @@ dhcp_parse_reply(struct pbuf *p, struct dhcp *dhcp, struct netif *netif) break; #if LWIP_DHCP_CLASSLESS_STATIC_ROUTES case (DHCP_OPTION_CLASSLESS_STATIC_ROUTE): - /* parse classless static routes (RFC 3442), only from ACK */ - if (dhcp_option_given(dhcp, DHCP_OPTION_IDX_MSG_TYPE) && - (dhcp_get_option_value(dhcp, DHCP_OPTION_IDX_MSG_TYPE) == DHCP_ACK)) { + /* Parse classless static routes (RFC 3442), only when expecting an ACK. + * Check DHCP state rather than MSG_TYPE to avoid depending on + * option ordering within the packet. */ + if (dhcp->state == DHCP_STATE_REQUESTING || + dhcp->state == DHCP_STATE_RENEWING || + dhcp->state == DHCP_STATE_REBINDING || + dhcp->state == DHCP_STATE_REBOOTING) { dhcp_parse_classless_routes(netif, q, val_offset, len); } decode_len = 0; diff --git a/src/core/ipv4/dhcp_classless_route.c b/src/core/ipv4/dhcp_classless_route.c deleted file mode 100644 index 6ea226b0..00000000 --- a/src/core/ipv4/dhcp_classless_route.c +++ /dev/null @@ -1,230 +0,0 @@ -/** - * @file - * DHCP Option 121 (Classless Static Routes) Parser - RFC 3442 - * - * Parses classless static routes from DHCP responses and populates - * the IPv4 static route table. - */ - -/* - * Copyright (c) 2026 Timo Gatsonides - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * 3. The name of the author may not be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT - * SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT - * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING - * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY - * OF SUCH DAMAGE. - * - * Author: Timo Gatsonides - */ - -#include "lwip/opt.h" - -#if LWIP_DHCP_CLASSLESS_STATIC_ROUTES - -#include "lwip/dhcp_classless_route.h" -#include "lwip/ip4_route_table.h" -#include "lwip/prot/dhcp.h" -#include "lwip/sys.h" -#include "lwip/pbuf.h" -#include "lwip/netif.h" -#include "lwip/def.h" - -#include - -/* Per-interface tracking of whether Option 121 was received */ -static u8_t option121_received[LWIP_DHCP_CLASSLESS_ROUTE_MAX_NETIFS]; - -/* Lock for thread-safe access to option121_received state */ -static sys_lock_t option121_lock; - -/* Get a safe index for the option121_received array, returns -1 if out of bounds */ -static inline int -get_netif_idx(struct netif *netif) -{ - u8_t idx; - if (netif == NULL) - return -1; - idx = netif_get_index(netif); - if (idx == 0 || idx > LWIP_DHCP_CLASSLESS_ROUTE_MAX_NETIFS) - return -1; - return idx - 1; -} - -/* Calculate significant octets for a prefix length: ceil(prefix_len / 8) */ -static inline u8_t -prefix_to_octets(u8_t prefix_len) -{ - return (prefix_len + 7) / 8; -} - -void -dhcp_classless_route_init(void) -{ - SYS_ARCH_LOCK_INIT(&option121_lock); - memset(option121_received, 0, sizeof(option121_received)); -} - -int -dhcp_parse_classless_routes(struct netif *netif, struct pbuf *p, - u16_t offset, u8_t len) -{ - int routes_parsed = 0; - u16_t pos = offset; - u16_t end; - int netif_idx; - - if (netif == NULL || p == NULL) - return ERR_ARG; /* ERR_ARG is already negative */ - - netif_idx = get_netif_idx(netif); - if (netif_idx < 0) - return ERR_ARG; - - /* overflow check */ - if (offset > 0xFFFF - len) - return ERR_ARG; - end = offset + len; - - /* empty option is valid but contains no routes */ - if (len == 0) - return 0; - - /* - * Clear any existing DHCP routes for this interface first. - * This ensures we replace old routes with new ones on lease renewal. - */ - ip4_route_remove_dhcp(netif); - - /* - * Parse each route entry in the option. - * Format per RFC 3442: - * 1 byte: prefix length (0-32) - * N bytes: significant octets of destination (N = ceil(prefix_len/8)) - * 4 bytes: gateway IP address - */ - while (pos < end) { - u8_t prefix_len; - u8_t significant_octets; - ip4_addr_t dest; - ip4_addr_t gateway; - u8_t dest_bytes[4] = {0, 0, 0, 0}; - - /* read prefix length */ - if (pbuf_copy_partial(p, &prefix_len, 1, pos) != 1) - break; - pos++; - - if (prefix_len > 32) - break; - - significant_octets = prefix_to_octets(prefix_len); - - /* check we have enough data remaining */ - if (pos + significant_octets + 4 > end) - break; - - /* read destination network (significant octets only) */ - if (significant_octets > 0) { - if (pbuf_copy_partial(p, dest_bytes, significant_octets, pos) - != significant_octets) - break; - } - pos += significant_octets; - - /* - * Reconstruct destination address. - * The significant octets are the high-order bytes of the address. - * dest_bytes is already zero-initialized for non-significant octets. - */ - dest.addr = (dest_bytes[0] << 24) | (dest_bytes[1] << 16) | - (dest_bytes[2] << 8) | dest_bytes[3]; - dest.addr = lwip_htonl(dest.addr); - - /* apply mask to ensure destination is properly masked (RFC 3442) */ - dest.addr &= ip4_prefix_to_mask(prefix_len); - - /* read gateway address - already in network byte order */ - if (pbuf_copy_partial(p, &gateway.addr, 4, pos) != 4) - break; - pos += 4; - - /* - * Validate gateway address: - * - Reject multicast/broadcast (invalid as next-hop) - * - 0.0.0.0 is valid per RFC 3442: means destination is on-link - * (directly reachable on this interface without a gateway) - */ - if (ip4_addr_ismulticast(&gateway) || - ip4_addr_isbroadcast(&gateway, netif)) - continue; - - /* add route to table */ - if (ip4_route_add(&dest, prefix_len, &gateway, netif, - IP4_ROUTE_FLAG_DHCP) == ERR_OK) { - routes_parsed++; - } - } - - /* - * Mark that this interface received Option 121. - * This is used to determine whether to ignore Option 3 (Router). - */ - if (routes_parsed > 0) { - SYS_ARCH_LOCK(&option121_lock); - option121_received[netif_idx] = 1; - SYS_ARCH_UNLOCK(&option121_lock); - } - - return routes_parsed; -} - -u8_t -dhcp_classless_route_received(struct netif *netif) -{ - u8_t result = 0; - int netif_idx = get_netif_idx(netif); - - if (netif_idx < 0) - return 0; - - SYS_ARCH_LOCK(&option121_lock); - result = (option121_received[netif_idx] != 0); - SYS_ARCH_UNLOCK(&option121_lock); - - return result; -} - -void -dhcp_classless_route_clear(struct netif *netif) -{ - int netif_idx = get_netif_idx(netif); - - if (netif_idx < 0) - return; - - SYS_ARCH_LOCK(&option121_lock); - option121_received[netif_idx] = 0; - SYS_ARCH_UNLOCK(&option121_lock); - - ip4_route_remove_dhcp(netif); -} - -#endif /* LWIP_DHCP_CLASSLESS_STATIC_ROUTES */ diff --git a/src/core/ipv4/etharp.c b/src/core/ipv4/etharp.c index 58f4ad20..d11ad854 100644 --- a/src/core/ipv4/etharp.c +++ b/src/core/ipv4/etharp.c @@ -849,6 +849,9 @@ etharp_output(struct netif *netif, struct pbuf *q, const ip4_addr_t *ipaddr) /* unicast destination IP address? */ } else { netif_addr_idx_t i; +#if LWIP_DHCP_CLASSLESS_STATIC_ROUTES + ip4_addr_t static_gw; +#endif /* LWIP_DHCP_CLASSLESS_STATIC_ROUTES */ /* outside local network? if so, this can neither be a global broadcast nor a subnet broadcast. */ if (!ip4_addr_netcmp(ipaddr, netif_ip4_addr(netif), netif_ip4_netmask(netif)) && @@ -864,8 +867,7 @@ etharp_output(struct netif *netif, struct pbuf *q, const ip4_addr_t *ipaddr) { #if LWIP_DHCP_CLASSLESS_STATIC_ROUTES /* check static route table for a per-route gateway */ - ip4_addr_t static_gw; - if (ip4_get_gateway(ipaddr, &static_gw)) { + if (ip4_get_gateway(netif, ipaddr, &static_gw)) { dst_addr = &static_gw; } else #endif /* LWIP_DHCP_CLASSLESS_STATIC_ROUTES */ diff --git a/src/core/ipv4/ip4_route_table.c b/src/core/ipv4/ip4_route_table.c index 3565ad14..fd921474 100644 --- a/src/core/ipv4/ip4_route_table.c +++ b/src/core/ipv4/ip4_route_table.c @@ -60,13 +60,13 @@ ip4_prefix_to_mask(u8_t prefix_len) { if (prefix_len == 0) return 0; - if (prefix_len >= 32) + if (prefix_len >= IP4_MAX_PREFIX_LEN) return 0xFFFFFFFF; - return PP_HTONL(~((1UL << (32 - prefix_len)) - 1)); + return lwip_htonl(~((1UL << (32 - prefix_len)) - 1)); } /* Check if a destination address matches a route entry */ -static inline int +static int route_matches(const struct ip4_route_entry *entry, const ip4_addr_t *dest) { u32_t mask = ip4_prefix_to_mask(entry->prefix_len); @@ -121,10 +121,11 @@ ip4_route_add(const ip4_addr_t *dest, u8_t prefix_len, int i; u32_t mask; - if (dest == NULL || gateway == NULL || netif == NULL) - return ERR_ARG; - if (prefix_len > IP4_MAX_PREFIX_LEN) - return ERR_ARG; + LWIP_ERROR("ip4_route_add: dest != NULL", dest != NULL, return ERR_ARG); + LWIP_ERROR("ip4_route_add: gateway != NULL", gateway != NULL, return ERR_ARG); + LWIP_ERROR("ip4_route_add: netif != NULL", netif != NULL, return ERR_ARG); + LWIP_ERROR("ip4_route_add: prefix_len <= IP4_MAX_PREFIX_LEN", + prefix_len <= IP4_MAX_PREFIX_LEN, return ERR_ARG); SYS_ARCH_LOCK(&route_lock); @@ -170,8 +171,9 @@ ip4_route_remove(const ip4_addr_t *dest, u8_t prefix_len, struct netif *netif) int idx; int i; - if (dest == NULL || prefix_len > IP4_MAX_PREFIX_LEN) - return; + LWIP_ERROR("ip4_route_remove: dest != NULL", dest != NULL, return); + LWIP_ERROR("ip4_route_remove: prefix_len <= IP4_MAX_PREFIX_LEN", + prefix_len <= IP4_MAX_PREFIX_LEN, return); SYS_ARCH_LOCK(&route_lock); @@ -190,49 +192,17 @@ ip4_route_remove(const ip4_addr_t *dest, u8_t prefix_len, struct netif *netif) } void -ip4_route_remove_netif(struct netif *netif) -{ - int i; - int j; - - if (netif == NULL) - return; - - SYS_ARCH_LOCK(&route_lock); - - i = 0; - while (i < route_entry_count) { - if (route_table[i].netif == netif) { - for (j = i; j < route_entry_count - 1; j++) { - memcpy(&route_table[j], &route_table[j + 1], - sizeof(struct ip4_route_entry)); - } - memset(&route_table[route_entry_count - 1], 0, - sizeof(struct ip4_route_entry)); - route_entry_count--; - } else { - i++; - } - } - - SYS_ARCH_UNLOCK(&route_lock); -} - -void -ip4_route_remove_dhcp(struct netif *netif) +ip4_route_remove_netif(struct netif *netif, u8_t flags) { int i; int j; - if (netif == NULL) - return; - SYS_ARCH_LOCK(&route_lock); i = 0; while (i < route_entry_count) { - if (route_table[i].netif == netif && - (route_table[i].flags & IP4_ROUTE_FLAG_DHCP)) { + if ((route_table[i].netif == netif) && + (route_table[i].flags & flags) == flags) { for (j = i; j < route_entry_count - 1; j++) { memcpy(&route_table[j], &route_table[j + 1], sizeof(struct ip4_route_entry)); @@ -254,8 +224,7 @@ ip4_route_find(const ip4_addr_t *dest, struct ip4_route_entry *out_entry) u8_t found = 0; int i; - if (dest == NULL) - return 0; + LWIP_ERROR("ip4_route_find: dest != NULL", dest != NULL, return 0); SYS_ARCH_LOCK(&route_lock); @@ -288,39 +257,52 @@ ip4_static_route(const ip4_addr_t *src, const ip4_addr_t *dest) } u8_t -ip4_get_gateway(const ip4_addr_t *dest, ip4_addr_t *out_gateway) +ip4_get_gateway(struct netif *netif, const ip4_addr_t *dest, ip4_addr_t *out_gateway) { - struct ip4_route_entry entry; + u8_t found = 0; + int i; + + LWIP_ERROR("ip4_get_gateway: netif != NULL", netif != NULL, return 0); + LWIP_ERROR("ip4_get_gateway: dest != NULL", dest != NULL, return 0); - if (ip4_route_find(dest, &entry)) { - if (out_gateway != NULL) - ip4_addr_copy(*out_gateway, entry.gateway); - return 1; + SYS_ARCH_LOCK(&route_lock); + + for (i = 0; i < route_entry_count; i++) { + if (route_table[i].netif == netif) { + u32_t mask = ip4_prefix_to_mask(route_table[i].prefix_len); + if ((dest->addr & mask) == (route_table[i].dest.addr & mask)) { + if (out_gateway != NULL) + ip4_addr_copy(*out_gateway, route_table[i].gateway); + found = 1; + break; + } + } } - return 0; -} -/* - * Get read-only access to route table for debugging/netlink. - * Note: Caller must not hold route_lock. The returned pointer is valid - * but entries may change if routes are modified concurrently. - */ -const struct ip4_route_entry * -ip4_get_route_table(int *count) -{ - if (count != NULL) - *count = route_entry_count; - return route_table; + SYS_ARCH_UNLOCK(&route_lock); + return found; } -int -ip4_route_count(void) +u8_t +ip4_route_exists(struct netif *netif, u8_t flags) { - int count; + u8_t found = 0; + int i; + + LWIP_ERROR("ip4_route_exists: netif != NULL", netif != NULL, return 0); + SYS_ARCH_LOCK(&route_lock); - count = route_entry_count; + + for (i = 0; i < route_entry_count; i++) { + if (route_table[i].netif == netif && + (route_table[i].flags & flags) == flags) { + found = 1; + break; + } + } + SYS_ARCH_UNLOCK(&route_lock); - return count; + return found; } #endif /* LWIP_DHCP_CLASSLESS_STATIC_ROUTES */ diff --git a/src/include/lwip/dhcp_classless_route.h b/src/include/lwip/dhcp_classless_route.h deleted file mode 100644 index ac3bd5d0..00000000 --- a/src/include/lwip/dhcp_classless_route.h +++ /dev/null @@ -1,99 +0,0 @@ -/** - * @file - * DHCP Option 121 (Classless Static Routes) Parser - RFC 3442 - * - * Parses DHCP Option 121 responses and populates the IPv4 static route - * table with classless static routes provided by the DHCP server. - */ - -/* - * Copyright (c) 2026 Timo Gatsonides - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * 3. The name of the author may not be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT - * SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT - * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING - * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY - * OF SUCH DAMAGE. - * - * Author: Timo Gatsonides - */ - -#ifndef LWIP_HDR_DHCP_CLASSLESS_ROUTE_H -#define LWIP_HDR_DHCP_CLASSLESS_ROUTE_H - -#include "lwip/opt.h" - -#if LWIP_DHCP_CLASSLESS_STATIC_ROUTES - -#include "lwip/pbuf.h" -#include "lwip/netif.h" -#include "lwip/err.h" - -#ifdef __cplusplus -extern "C" { -#endif - -/** Initialize the Option 121 module - must be called before other functions */ -void dhcp_classless_route_init(void); - -/** - * Parse DHCP Option 121 data and add routes to the static route table. - * - * Parses variable-length encoding per RFC 3442: - * 1 byte: prefix length (0-32) - * N bytes: significant octets of destination (N = ceil(prefix_len/8)) - * 4 bytes: gateway IP address - * - * Routes are added with IP4_ROUTE_FLAG_DHCP flag. Existing DHCP routes - * for the interface are cleared first. - * - * @param netif network interface that received the DHCP response - * @param p pbuf containing the option data - * @param offset offset into pbuf where option data starts - * @param len length of option data - * @return number of routes successfully added (>= 0), or negative error - */ -int dhcp_parse_classless_routes(struct netif *netif, struct pbuf *p, - u16_t offset, u8_t len); - -/** - * Check if Option 121 was received for an interface. - * Per RFC 3442, if Option 121 is present, Option 3 (Router) must be ignored. - * - * @param netif network interface to check - * @return 1 if Option 121 was received, 0 otherwise - */ -u8_t dhcp_classless_route_received(struct netif *netif); - -/** - * Clear Option 121 state and routes for a network interface. - * Called when DHCP lease is released, interface is removed, etc. - * - * @param netif network interface to clear - */ -void dhcp_classless_route_clear(struct netif *netif); - -#ifdef __cplusplus -} -#endif - -#endif /* LWIP_DHCP_CLASSLESS_STATIC_ROUTES */ - -#endif /* LWIP_HDR_DHCP_CLASSLESS_ROUTE_H */ diff --git a/src/include/lwip/ip4_route_table.h b/src/include/lwip/ip4_route_table.h index 29daaf40..c26b1f01 100644 --- a/src/include/lwip/ip4_route_table.h +++ b/src/include/lwip/ip4_route_table.h @@ -7,7 +7,7 @@ */ /* - * Copyright (c) 2026 Timo Gatsonides + * Copyright (c) 2025 Timo Gatsonides * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, @@ -88,11 +88,17 @@ err_t ip4_route_add(const ip4_addr_t *dest, u8_t prefix_len, /** Remove a specific route from the table */ void ip4_route_remove(const ip4_addr_t *dest, u8_t prefix_len, struct netif *netif); -/** Remove all routes associated with a network interface */ -void ip4_route_remove_netif(struct netif *netif); +/** Remove routes matching a network interface and flags */ +void ip4_route_remove_netif(struct netif *netif, u8_t flags); -/** Remove all DHCP-learned routes for a network interface */ -void ip4_route_remove_dhcp(struct netif *netif); +/** + * Check whether a route with the given netif and flags exists. + * + * @param netif network interface to check + * @param flags route flags to match + * @return 1 if such a route exists, 0 otherwise + */ +u8_t ip4_route_exists(struct netif *netif, u8_t flags); /** * Find the best matching route for a destination address (longest prefix match). @@ -117,17 +123,12 @@ struct netif *ip4_static_route(const ip4_addr_t *src, const ip4_addr_t *dest); * Get the gateway address for a destination from the static route table. * Copies gateway to out_gateway if found. * + * @param netif network interface to match * @param dest destination IP address to look up * @param out_gateway pointer to store the gateway address (may be NULL) * @return 1 if route found, 0 otherwise */ -u8_t ip4_get_gateway(const ip4_addr_t *dest, ip4_addr_t *out_gateway); - -/** Get read-only access to route table for debugging/netlink reporting */ -const struct ip4_route_entry *ip4_get_route_table(int *count); - -/** Get the number of active routes in the table */ -int ip4_route_count(void); +u8_t ip4_get_gateway(struct netif *netif, const ip4_addr_t *dest, ip4_addr_t *out_gateway); #ifdef __cplusplus } diff --git a/src/include/lwip/opt.h b/src/include/lwip/opt.h index e9a55aee..c78022bf 100644 --- a/src/include/lwip/opt.h +++ b/src/include/lwip/opt.h @@ -973,20 +973,12 @@ #define LWIP_DHCP_CLASSLESS_STATIC_ROUTES 0 #endif -/** - * LWIP_DHCP_CLASSLESS_ROUTE_MAX_NETIFS: Maximum number of network interfaces - * tracked for Option 121 state. Should match or exceed the system interface count. - */ -#if !defined LWIP_DHCP_CLASSLESS_ROUTE_MAX_NETIFS || defined __DOXYGEN__ -#define LWIP_DHCP_CLASSLESS_ROUTE_MAX_NETIFS 16 -#endif - /** * LWIP_IPV4_NUM_ROUTE_ENTRIES: Maximum number of entries in the IPv4 static * route table. Used when LWIP_DHCP_CLASSLESS_STATIC_ROUTES is enabled. */ #if !defined LWIP_IPV4_NUM_ROUTE_ENTRIES || defined __DOXYGEN__ -#define LWIP_IPV4_NUM_ROUTE_ENTRIES 16 +#define LWIP_IPV4_NUM_ROUTE_ENTRIES 8 #endif /** * @}