From 4a0b5ee47a12154a28fcf047d7682e69745934a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Frauenschl=C3=A4ger?= Date: Mon, 28 Sep 2026 10:55:13 +0200 Subject: [PATCH 1/2] Allow compiling out the DHCP client The DHCP client was compiled into every build, and the poll loop calls into it unconditionally, so --gc-sections could not drop it either. A static-address target paid for a protocol it never runs. Gate the implementation on WOLFIP_ENABLE_DHCP, defaulting to 1, so every existing build is unchanged: with the default, the preprocessed wolfip.c is identical to before. It is the macro the ports' config.h already use to decide whether to define DHCP, so setting it to 0 there now also drops the client from the library. DHCP keeps its meaning as the application-level switch for calling dhcp_client_init(). Guarded are the client itself, the DAD probe, the two DAD conflict checks in arp_recv, and the poll loop's dhcp_timer_recover() call. The dhcp_* fields stay in struct wolfIP: a few dozen bytes, and keeping them lets the DHCP_IS_RUNNING() checks stay plain runtime tests that are always false. The declarations in wolfip.h stay ungated: an unresolved call is a clearer error than an undeclared one. Measured on a TC4Dx TLS demo with WOLFIP_ENABLE_DHCP 0: 3012 bytes of flash, and no dhcp symbols in the image. --- docs/API.md | 2 ++ docs/dhcp_dns_howto.md | 2 ++ src/wolfip.c | 14 ++++++++++++++ 3 files changed, 18 insertions(+) diff --git a/docs/API.md b/docs/API.md index c7179d21..26249fa7 100644 --- a/docs/API.md +++ b/docs/API.md @@ -246,6 +246,8 @@ For `non_ethernet` devices this value remains the internal frame budget; the max ## DHCP Client Functions +Compiled unless `WOLFIP_ENABLE_DHCP` is set to 0. + ```c int dhcp_client_init(struct wolfIP *s); ``` diff --git a/docs/dhcp_dns_howto.md b/docs/dhcp_dns_howto.md index 3ef577a2..95e47d55 100644 --- a/docs/dhcp_dns_howto.md +++ b/docs/dhcp_dns_howto.md @@ -77,6 +77,8 @@ retransmit and lease timers never fire. ## 3. The API +The DHCP client is compiled by default. A static-address build can drop it by setting `WOLFIP_ENABLE_DHCP` to 0; the functions below are then still declared, but not defined, so a call fails at link time. + All declarations are in `wolfip.h`: ```c diff --git a/src/wolfip.c b/src/wolfip.c index 03692fa7..9ffd5b59 100644 --- a/src/wolfip.c +++ b/src/wolfip.c @@ -40,6 +40,10 @@ #define WOLFIP_MAX_ROUTES 16U #endif +#ifndef WOLFIP_ENABLE_DHCP +#define WOLFIP_ENABLE_DHCP 1 +#endif + #define WOLFIP_LOOPBACK_IP 0x7F000001U #define WOLFIP_LOOPBACK_MASK 0xFF000000U #if WOLFIP_ENABLE_LOOPBACK @@ -9159,6 +9163,7 @@ static void icmp_input(struct wolfIP *s, unsigned int if_idx, struct wolfIP_ip_p icmp_try_recv(s, if_idx, icmp, len); } +#if WOLFIP_ENABLE_DHCP static int dhcp_send_discover(struct wolfIP *s); static int dhcp_send_request(struct wolfIP *s); #ifdef ETHERNET @@ -10337,6 +10342,7 @@ static void dhcp_dad_conflict(struct wolfIP *s) dhcp_schedule_timer_at(s, s->last_tick + DHCP_DECLINE_WAIT_MS); } #endif +#endif /* WOLFIP_ENABLE_DHCP */ /* ARP */ #ifdef ETHERNET @@ -10585,6 +10591,7 @@ static void arp_request(struct wolfIP *s, unsigned int if_idx, ip4 tip) * reply is detected in arp_recv (dhcp_dad_conflict). Deliberately bypasses * the 1 req/s rate limit: DAD is at most 3 probes per acquisition, one * per second, and must not starve behind ordinary traffic. */ +#if WOLFIP_ENABLE_DHCP static int dhcp_send_dad_probe(struct wolfIP *s) { struct arp_packet arp; @@ -10613,6 +10620,7 @@ static int dhcp_send_dad_probe(struct wolfIP *s) return wolfIP_ll_send_frame(s, WOLFIP_PRIMARY_IF_IDX, &arp, sizeof(struct arp_packet)); } +#endif /* WOLFIP_ENABLE_DHCP */ static void arp_recv(struct wolfIP *s, unsigned int if_idx, void *buf, int len) { @@ -10640,6 +10648,7 @@ static void arp_recv(struct wolfIP *s, unsigned int if_idx, void *buf, int len) if (arp->sma[0] & 0x01) return; +#if WOLFIP_ENABLE_DHCP /* RFC 4331/5227 DAD: on the probing interface, a request from a * foreign MAC that claims the candidate (sender IP, including a * gratuitous announcement with sip==tip) or probes for it is a @@ -10654,6 +10663,7 @@ static void arp_recv(struct wolfIP *s, unsigned int if_idx, void *buf, int len) return; } } +#endif /* WOLFIP_ENABLE_DHCP */ /* An unconfigured interface (no assigned address) must not answer * ARP requests: matching tip against a zero conf->ip would let a @@ -10690,6 +10700,7 @@ static void arp_recv(struct wolfIP *s, unsigned int if_idx, void *buf, int len) else if (arp->opcode == ee16(ARP_REPLY)) { ip4 sip = ee32(arp->sip); int pending; +#if WOLFIP_ENABLE_DHCP /* RFC 4331 DAD: a reply on the probing interface claiming the * candidate is a conflict, unless it is our own MAC (looped probe). * Bound to the DAD interface + recorded candidate so a reply on @@ -10700,6 +10711,7 @@ static void arp_recv(struct wolfIP *s, unsigned int if_idx, void *buf, int len) dhcp_dad_conflict(s); return; } +#endif /* WOLFIP_ENABLE_DHCP */ /* Validate sender IP: reject broadcast, multicast, zero, and * our own address -- same checks as the ARP request handler. */ if (sip == IPADDR_ANY || sip == conf->ip || @@ -12876,7 +12888,9 @@ int wolfIP_poll(struct wolfIP *s, uint64_t now) /* Handle timers */ handle_timers(s, now); +#if WOLFIP_ENABLE_DHCP dhcp_timer_recover(s); +#endif #ifdef IP_MULTICAST igmp_timer_recover(s); #endif From 36b89c1fccb2c88ee9932632ccd5c9a2a911e4e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Frauenschl=C3=A4ger?= Date: Mon, 28 Sep 2026 10:56:39 +0200 Subject: [PATCH 2/2] Support MAX_UDPSOCKETS 0 for builds that need no UDP With WOLFIP_ENABLE_DHCP 0, MAX_UDPSOCKETS 0 already compiles and behaves: every udpsockets[] access is either a loop bounded by MAX_UDPSOCKETS or preceded by its own ">= MAX_UDPSOCKETS" check, so at zero each UDP path becomes unreachable rather than unsafe, and wolfIP_sock_socket() finds no slot to hand out. DNS goes with it: it cannot open its socket, and the linker drops it. The one unchecked access was in the DHCP client, which that setting compiles out. An #error rejects MAX_UDPSOCKETS 0 while DHCP is still enabled, since the client needs a UDP socket. Document the configuration so it is supported rather than accidental, and build it in CI (with the check that no DHCP symbols are left) so it stays that way. The zero-length array is the same GNU extension struct dhcp_option::data already uses. Measured on a TC4Dx TLS demo, on top of gating DHCP: 2028 more bytes of flash and 18372 bytes of RAM, the per-socket UDP buffers that no longer exist. --- .github/workflows/linux.yml | 12 ++++++++++++ docs/dhcp_dns_howto.md | 2 ++ src/wolfip.c | 4 ++++ 3 files changed, 18 insertions(+) diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 7547ae06..bf34d250 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -122,6 +122,18 @@ jobs: set -euo pipefail timeout --preserve-status 2m sudo ./build/packet_ping wtcp0 10.10.10.1 + - name: Build without DHCP and with no UDP sockets + run: | + set -euo pipefail + mkdir -p build/noudp + sed 's/^#define MAX_UDPSOCKETS .*/#define MAX_UDPSOCKETS 0/' config.h > build/noudp/config.h + gcc -Ibuild/noudp -I. -D_GNU_SOURCE -DWOLFIP_ENABLE_DHCP=0 -Wall -Werror -Wextra -c src/wolfip.c -o build/noudp/wolfip.o + nm build/noudp/wolfip.o > build/noudp/syms.txt + if grep -qi dhcp build/noudp/syms.txt; then + echo "DHCP symbols left in a WOLFIP_ENABLE_DHCP=0 build" + exit 1 + fi + - name: Install check run: | sudo apt-get install -y check diff --git a/docs/dhcp_dns_howto.md b/docs/dhcp_dns_howto.md index 95e47d55..a1c6aee4 100644 --- a/docs/dhcp_dns_howto.md +++ b/docs/dhcp_dns_howto.md @@ -353,6 +353,8 @@ if your application needs UDP sockets of its own. If the pool is exhausted, `dhcp_client_init()` returns negative and `nslookup()` fails to allocate its socket. +A target that needs no UDP at all can set `MAX_UDPSOCKETS 0` together with `WOLFIP_ENABLE_DHCP 0`. Every UDP path then finds no socket to use, DNS lookups fail to allocate one, and the per-socket UDP buffers disappear from RAM. Leaving DHCP enabled with no UDP sockets stops the build with an `#error`. + ## 10. Troubleshooting **DHCP never gets bound.** Confirm `now_ms` advances between `wolfIP_poll()` diff --git a/src/wolfip.c b/src/wolfip.c index 9ffd5b59..3c2866c9 100644 --- a/src/wolfip.c +++ b/src/wolfip.c @@ -43,6 +43,9 @@ #ifndef WOLFIP_ENABLE_DHCP #define WOLFIP_ENABLE_DHCP 1 #endif +#if WOLFIP_ENABLE_DHCP && defined(MAX_UDPSOCKETS) && (MAX_UDPSOCKETS < 1) +#error "WOLFIP_ENABLE_DHCP requires MAX_UDPSOCKETS >= 1" +#endif #define WOLFIP_LOOPBACK_IP 0x7F000001U #define WOLFIP_LOOPBACK_MASK 0xFF000000U @@ -1517,6 +1520,7 @@ struct wolfIP { char dns_ptr_name[256]; struct timers_binheap timers; struct tsocket tcpsockets[MAX_TCPSOCKETS]; + /* Zero-length (GNU extension) when MAX_UDPSOCKETS is 0. */ struct tsocket udpsockets[MAX_UDPSOCKETS]; struct tsocket icmpsockets[MAX_ICMPSOCKETS]; #if WOLFIP_RAWSOCKETS