diff --git a/src/core/init.c b/src/core/init.c index b3737a35..471c1ec2 100644 --- a/src/core/init.c +++ b/src/core/init.c @@ -58,6 +58,10 @@ #include "lwip/mld6.h" #include "lwip/api.h" +#if LWIP_DHCP_CLASSLESS_STATIC_ROUTES +#include "lwip/ip4_route_table.h" +#endif /* LWIP_DHCP_CLASSLESS_STATIC_ROUTES */ + #include "netif/ppp/ppp_opts.h" #include "netif/ppp/ppp_impl.h" @@ -94,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 @@ -370,6 +377,9 @@ lwip_init(void) #if LWIP_DNS dns_init(); #endif /* LWIP_DNS */ +#if LWIP_DHCP_CLASSLESS_STATIC_ROUTES + ip4_route_table_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 6698031a..ee60f2c0 100644 --- a/src/core/ipv4/dhcp.c +++ b/src/core/ipv4/dhcp.c @@ -82,6 +82,10 @@ #include +#if LWIP_DHCP_CLASSLESS_STATIC_ROUTES +#include "lwip/ip4_route_table.h" +#endif + #ifdef LWIP_HOOK_FILENAME #include LWIP_HOOK_FILENAME #endif @@ -170,6 +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 + /* request Option 121 (Classless Static Routes) per RFC 3442 */ + , DHCP_OPTION_CLASSLESS_STATIC_ROUTE +#endif /* LWIP_DHCP_CLASSLESS_STATIC_ROUTES */ }; #ifdef DHCP_GLOBAL_XID @@ -189,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); @@ -287,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); } @@ -733,7 +846,12 @@ dhcp_handle_ack(struct netif *netif, struct dhcp_msg *msg_in) } /* gateway router */ - if (dhcp_option_given(dhcp, DHCP_OPTION_IDX_ROUTER)) { + 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) */ + && !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))); } @@ -791,6 +909,10 @@ void dhcp_cleanup(struct netif *netif) LWIP_ASSERT_CORE_LOCKED(); LWIP_ASSERT("netif != NULL", netif != NULL); +#if LWIP_DHCP_CLASSLESS_STATIC_ROUTES + ip4_route_remove_netif(netif, IP4_ROUTE_FLAG_DHCP); +#endif /* LWIP_DHCP_CLASSLESS_STATIC_ROUTES */ + SYS_ARCH_LOCK(&dhcp_mutex); if (netif_dhcp_data(netif) != NULL) { mem_free(netif_dhcp_data(netif)); @@ -1149,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 @@ -1363,6 +1499,10 @@ dhcp_release_and_stop(struct netif *netif) return; } +#if LWIP_DHCP_CLASSLESS_STATIC_ROUTES + 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); /* clean old DHCP offer */ @@ -1536,7 +1676,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; @@ -1665,10 +1805,24 @@ 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): + /* 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; + 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; @@ -1775,7 +1929,7 @@ dhcp_parse_reply(struct pbuf *p, struct dhcp *dhcp) /* 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; } @@ -1836,7 +1990,7 @@ 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? */ - if (dhcp_parse_reply(p, dhcp) != ERR_OK) { + 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; diff --git a/src/core/ipv4/etharp.c b/src/core/ipv4/etharp.c index d1ac0135..d11ad854 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 @@ -845,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)) && @@ -858,6 +865,12 @@ 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 */ + if (ip4_get_gateway(netif, 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. */ 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/core/ipv4/ip4_route_table.c b/src/core/ipv4/ip4_route_table.c new file mode 100644 index 00000000..fd921474 --- /dev/null +++ b/src/core/ipv4/ip4_route_table.c @@ -0,0 +1,308 @@ +/** + * @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 >= IP4_MAX_PREFIX_LEN) + return 0xFFFFFFFF; + return lwip_htonl(~((1UL << (32 - prefix_len)) - 1)); +} + +/* Check if a destination address matches a route entry */ +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); + 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; + + 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); + + /* 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; + + 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); + + 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, u8_t flags) +{ + int i; + int j; + + SYS_ARCH_LOCK(&route_lock); + + i = 0; + while (i < route_entry_count) { + 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)); + } + 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; + + LWIP_ERROR("ip4_route_find: dest != NULL", 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(struct netif *netif, const ip4_addr_t *dest, ip4_addr_t *out_gateway) +{ + 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); + + 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; + } + } + } + + SYS_ARCH_UNLOCK(&route_lock); + return found; +} + +u8_t +ip4_route_exists(struct netif *netif, u8_t flags) +{ + u8_t found = 0; + int i; + + LWIP_ERROR("ip4_route_exists: netif != NULL", netif != NULL, return 0); + + SYS_ARCH_LOCK(&route_lock); + + 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 found; +} + +#endif /* LWIP_DHCP_CLASSLESS_STATIC_ROUTES */ 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 diff --git a/src/include/lwip/ip4_route_table.h b/src/include/lwip/ip4_route_table.h new file mode 100644 index 00000000..c26b1f01 --- /dev/null +++ b/src/include/lwip/ip4_route_table.h @@ -0,0 +1,139 @@ +/** + * @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) 2025 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 routes matching a network interface and flags */ +void ip4_route_remove_netif(struct netif *netif, u8_t flags); + +/** + * 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). + * 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 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(struct netif *netif, const ip4_addr_t *dest, ip4_addr_t *out_gateway); + +#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..c78022bf 100644 --- a/src/include/lwip/opt.h +++ b/src/include/lwip/opt.h @@ -961,6 +961,25 @@ #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_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 8 +#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 */