Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ endif
THIRD_PARTY= $(ACPICA_DIR)/.vendored $(LWIPDIR)/.vendored $(MBEDTLS_DIR)/.vendored

$(ACPICA_DIR)/.vendored: GITFLAGS= --depth 1 https://github.com/acpica/acpica.git -b R09_30_21
$(LWIPDIR)/.vendored: GITFLAGS= --depth 1 https://github.com/nanovms/lwip.git -b STABLE-2_1_x
$(LWIPDIR)/.vendored: GITFLAGS= --depth 1 https://github.com/tpjg/lwip.git -b STABLE-2_1_x
$(MBEDTLS_DIR)/.vendored: GITFLAGS= --depth 1 https://github.com/nanovms/mbedtls.git

kernel: $(THIRD_PARTY) contgen
Expand Down
2 changes: 2 additions & 0 deletions platform/pc/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ SRCS-kernel.elf= \
$(SRCDIR)/kernel/vdso-now.c \
$(SRCDIR)/kernel/vmem_heap.c \
$(SRCDIR)/net/direct.c \
$(SRCDIR)/net/dhcp_option121.c \
$(SRCDIR)/net/ip4_route_table.c \
$(SRCDIR)/net/net.c \
$(SRCDIR)/net/netsyscall.c \
$(RUNTIME) \
Expand Down
2 changes: 2 additions & 0 deletions platform/riscv-virt/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ SRCS-kernel.elf= \
$(SRCDIR)/kernel/vdso-now.c \
$(SRCDIR)/kernel/vmem_heap.c \
$(SRCDIR)/net/direct.c \
$(SRCDIR)/net/dhcp_option121.c \
$(SRCDIR)/net/ip4_route_table.c \
$(SRCDIR)/net/net.c \
$(SRCDIR)/net/netsyscall.c \
$(RUNTIME) \
Expand Down
2 changes: 2 additions & 0 deletions platform/virt/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ SRCS-kernel.elf= \
$(SRCDIR)/kernel/vdso-now.c \
$(SRCDIR)/kernel/vmem_heap.c \
$(SRCDIR)/net/direct.c \
$(SRCDIR)/net/dhcp_option121.c \
$(SRCDIR)/net/ip4_route_table.c \
$(SRCDIR)/net/net.c \
$(SRCDIR)/net/netsyscall.c \
$(RUNTIME) \
Expand Down
213 changes: 213 additions & 0 deletions src/net/dhcp_option121.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
/*
* DHCP Option 121 (Classless Static Routes) Parser - RFC 3442
*
* Parses classless static routes from DHCP responses and populates
* the IPv4 static route table.
*/

#include <kernel.h>
#include <lwip.h>
#include <lwip/prot/dhcp.h>
#include <dhcp_option121.h>
#include <ip4_route_table.h>

/*
* Global to track the current netif during DHCP receive processing.
* Set by dhcp_recv() before calling dhcp_parse_reply() so that
* ip_current_netif() can return the correct value for hooks.
*
* This is needed because nanos's lwIP modifications pass ip_data
* as a parameter rather than using a global, but the DHCP option
* hook still uses ip_current_netif().
*
* Safe because lwIP runs in single-threaded context (NO_SYS=1) and
* DHCP packets are processed sequentially in the network timer context.
*/
struct netif *nanos_dhcp_current_netif;

/* Per-interface tracking of whether Option 121 was received */
static u8_t option121_received[DHCP_OPTION121_MAX_NETIFS];

/* Spinlock for thread-safe access to option121_received state */
static struct spinlock 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)
{
if (netif == NULL)
return -1;
u8_t idx = netif_get_index(netif);
if (idx == 0 || idx > DHCP_OPTION121_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_option121_init(void)
{
spin_lock_init(&option121_lock);
runtime_memset(option121_received, 0, sizeof(option121_received));
}

int dhcp_parse_option121(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) {
spin_lock(&option121_lock);
option121_received[netif_idx] = 1;
spin_unlock(&option121_lock);
}

return routes_parsed;
}

boolean dhcp_option121_received(struct netif *netif)
{
boolean result = false;
int netif_idx = get_netif_idx(netif);

if (netif_idx < 0)
return false;

spin_lock(&option121_lock);
result = (option121_received[netif_idx] != 0);
spin_unlock(&option121_lock);

return result;
}

void dhcp_option121_clear(struct netif *netif)
{
int netif_idx = get_netif_idx(netif);

if (netif_idx < 0)
return;

spin_lock(&option121_lock);
option121_received[netif_idx] = 0;
spin_unlock(&option121_lock);

ip4_route_remove_dhcp(netif);
}

void nanos_dhcp_parse_option_hook(struct netif *netif, struct dhcp *dhcp,
u8_t state, struct dhcp_msg *msg,
u8_t msg_type, u8_t option, u8_t len,
struct pbuf *p, u16_t offset)
{
LWIP_UNUSED_ARG(dhcp);
LWIP_UNUSED_ARG(state);
LWIP_UNUSED_ARG(msg);

/* only process Option 121 from DHCP ACK messages */
if (option == DHCP_OPTION_CLASSLESS_STATIC_ROUTE &&
msg_type == DHCP_ACK) {
dhcp_parse_option121(netif, p, offset, len);
}
}
69 changes: 69 additions & 0 deletions src/net/dhcp_option121.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*
* 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.
*/

#ifndef __DHCP_OPTION121_H__
#define __DHCP_OPTION121_H__

#include <lwip/pbuf.h>
#include <lwip/netif.h>
#include <lwip/err.h>

/* DHCP Option 121 - Classless Static Route (RFC 3442) */
#define DHCP_OPTION_CLASSLESS_STATIC_ROUTE 121

/*
* Maximum number of network interfaces we track for Option 121 state.
* Should match or exceed the number of interfaces in the system.
*/
#ifndef DHCP_OPTION121_MAX_NETIFS
#define DHCP_OPTION121_MAX_NETIFS 16
#endif

/* Initialize the Option 121 module - must be called before other functions */
void dhcp_option121_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.
*
* Returns number of routes successfully added (>= 0), or negative error.
*/
int dhcp_parse_option121(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.
*/
boolean dhcp_option121_received(struct netif *netif);

/*
* Clear Option 121 state and routes for a network interface.
* Called when DHCP lease is released, interface is removed, etc.
*/
void dhcp_option121_clear(struct netif *netif);

/*
* lwIP DHCP option parse hook (LWIP_HOOK_DHCP_PARSE_OPTION).
* Called by lwIP for each unknown DHCP option, parses Option 121.
*/
struct dhcp;
struct dhcp_msg;

void nanos_dhcp_parse_option_hook(struct netif *netif, struct dhcp *dhcp,
u8_t state, struct dhcp_msg *msg,
u8_t msg_type, u8_t option, u8_t len,
struct pbuf *p, u16_t offset);

#endif /* __DHCP_OPTION121_H__ */
Loading