Skip to content

Commit 1164e65

Browse files
authored
Merge pull request #111 from danielinux/tftp
Added TFTP Client / server
2 parents 1781511 + c5be23e commit 1164e65

16 files changed

Lines changed: 4937 additions & 24 deletions

File tree

.github/workflows/linux.yml

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,15 @@ jobs:
1919
- name: Install dependencies
2020
run: |
2121
sudo apt-get update
22-
sudo apt-get install -y build-essential autoconf automake libtool pkg-config
22+
sudo apt-get install -y build-essential autoconf automake libtool pkg-config \
23+
tftpd-hpa tftp-hpa
2324
sudo modprobe tun
25+
# The tftpd-hpa package installs a systemd unit that binds the
26+
# standard TFTP port. The interop test starts its own in.tftpd
27+
# bound to the test tap link, so stop the system instance to
28+
# keep ports / file ownership predictable.
29+
sudo systemctl stop tftpd-hpa || true
30+
sudo systemctl disable tftpd-hpa || true
2431
2532
- name: Clone and build wolfSSL from nightly-snapshot
2633
run: |
@@ -129,6 +136,28 @@ jobs:
129136
set -euo pipefail
130137
timeout --preserve-status 5m build/test/unit
131138
139+
- name: Build TFTP interop test
140+
run: |
141+
make build/test-tftp-interop
142+
143+
- name: Run TFTP interop test (wolfIP client vs tftpd-hpa, wolfIP server vs tftp-hpa)
144+
timeout-minutes: 3
145+
run: |
146+
set -euo pipefail
147+
timeout --preserve-status 3m sudo ./build/test-tftp-interop all
148+
sudo killall tcpdump || true
149+
150+
- name: Upload TFTP interop diagnostics on failure
151+
if: failure()
152+
uses: actions/upload-artifact@v4
153+
with:
154+
name: tftp-interop-diagnostics
155+
path: |
156+
/tmp/wolfip-tftp.pcap
157+
/tmp/wolfip-tftpd-hpa.log
158+
/tmp/wolfip-tftp-client.log
159+
if-no-files-found: ignore
160+
132161
- name: Build ESP unit tests
133162
run: |
134163
make unit-esp

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
*.bin
88
*.swp
99
*.elf
10+
*.gcov
1011
CMakeCache.txt
1112
CMakeFiles
1213
CMakeScripts

CMakeLists.txt

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,11 +52,25 @@ string(TOLOWER "${CMAKE_SYSTEM_NAME}" CMAKE_SYSTEM_NAME_LC)
5252

5353
set(WOLFIP_TAP_SRC "${CMAKE_CURRENT_SOURCE_DIR}/src/port/posix/tap_${CMAKE_SYSTEM_NAME_LC}.c")
5454

55+
# Optional TFTP client/server module. Default off to match config.h
56+
# (WOLFIP_ENABLE_TFTP == 0); turn on with -DWOLFIP_ENABLE_TFTP=ON.
57+
option(WOLFIP_ENABLE_TFTP "Build and link the wolfIP TFTP client/server" OFF)
58+
if (WOLFIP_ENABLE_TFTP)
59+
file(GLOB WOLFIP_TFTP_SRCS CONFIGURE_DEPENDS
60+
"${CMAKE_CURRENT_SOURCE_DIR}/src/tftp/*.c")
61+
add_compile_definitions(WOLFIP_ENABLE_TFTP=1)
62+
else()
63+
set(WOLFIP_TFTP_SRCS )
64+
endif()
65+
5566
if (NOT EXISTS "${WOLFIP_TAP_SRC}")
5667
message(FATAL_ERROR "Unsupported platform: ${CMAKE_SYSTEM_NAME}")
5768
endif()
5869

59-
set(WOLFIP_SRCS src/wolfip.c ${WOLFIP_TAP_SRC})
70+
set(WOLFIP_SRCS
71+
src/wolfip.c
72+
${WOLFIP_TFTP_SRCS}
73+
${WOLFIP_TAP_SRC})
6074

6175
set(CERT_SRCS
6276
${CMAKE_BINARY_DIR}/certs/server_cert.c
@@ -187,7 +201,7 @@ add_executable(test-ttl-expired ${EXCLUDE_TEST_BINARY}
187201
target_compile_definitions(test-ttl-expired PRIVATE -DWOLFIP_MAX_INTERFACES=2 -DWOLFIP_ENABLE_FORWARDING=1)
188202
add_test(NAME ttl-expired COMMAND test-ttl-expired)
189203

190-
if (NOT Check_FOUND)
204+
if (Check_FOUND)
191205
add_executable(unit ${EXCLUDE_TEST_BINARY}
192206
src/test/unit/unit.c
193207
)

Makefile

Lines changed: 72 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,21 @@ endif
8989
TAP_OBJ:=$(NETDEV_OBJ)
9090
TAP_PIE_OBJ:=$(NETDEV_PIE_OBJ)
9191

92+
# Optional TFTP module. Default to off to match config.h
93+
# (WOLFIP_ENABLE_TFTP == 0); set WOLFIP_ENABLE_TFTP=1 on the command
94+
# line to compile and link the TFTP client/server objects.
95+
WOLFIP_ENABLE_TFTP ?= 0
96+
ifeq ($(WOLFIP_ENABLE_TFTP),1)
97+
WOLFIP_TFTP_SRC:=$(wildcard src/tftp/*.c)
98+
WOLFIP_TFTP_OBJ:=$(patsubst src/%.c,build/%.o,$(WOLFIP_TFTP_SRC))
99+
WOLFIP_TFTP_PIE_OBJ:=$(patsubst src/%.c,build/pie/%.o,$(WOLFIP_TFTP_SRC))
100+
CFLAGS+=-DWOLFIP_ENABLE_TFTP=1
101+
else
102+
WOLFIP_TFTP_SRC:=
103+
WOLFIP_TFTP_OBJ:=
104+
WOLFIP_TFTP_PIE_OBJ:=
105+
endif
106+
92107
ifeq ($(UNAME_S),Darwin)
93108
BEGIN_GROUP:=
94109
END_GROUP:=
@@ -135,12 +150,15 @@ CPPCHECK_FLAGS=--enable=warning,performance,portability,missingInclude \
135150
--error-exitcode=1 --xml --xml-version=2
136151

137152
OBJ=build/wolfip.o \
153+
$(WOLFIP_TFTP_OBJ) \
138154
$(TAP_OBJ)
139155

140156
IPFILTER_OBJ=build/ipfilter/wolfip.o \
157+
$(WOLFIP_TFTP_OBJ) \
141158
$(TAP_OBJ)
142159

143160
ESP_OBJ=build/esp/wolfip.o \
161+
$(WOLFIP_TFTP_OBJ) \
144162
$(TAP_OBJ)
145163

146164
HAVE_WOLFSSL:=$(shell printf "#include <wolfssl/options.h>\nint main(void){return 0;}\n" | $(CC) $(CFLAGS) -x c - -c -o /dev/null 2>/dev/null && echo 1)
@@ -185,6 +203,7 @@ libtcpip.a: $(OBJ)
185203

186204
libwolfip.so:CFLAGS+=-fPIC
187205
libwolfip.so: build/pie/port/posix/bsd_socket.o build/pie/wolfip.o \
206+
$(WOLFIP_TFTP_PIE_OBJ) \
188207
$(TAP_PIE_OBJ)
189208
@mkdir -p `dirname $@` || true
190209
@echo "[LD] $@"
@@ -257,6 +276,39 @@ build/test-dns: $(OBJ) build/test/test_dhcp_dns.o
257276
@echo "[LD] $@"
258277
@$(CC) $(CFLAGS) -o $@ $(BEGIN_GROUP) $(^) $(LDFLAGS) $(END_GROUP)
259278

279+
# Bidirectional TFTP interop test against tftpd-hpa / tftp-hpa.
280+
# Forces WOLFIP_ENABLE_TFTP=1 and uses a single-session server so the
281+
# default UDP socket pool can hold both the listen and the transfer
282+
# socket without raising MAX_UDPSOCKETS.
283+
build/tftp-interop/wolfip.o: src/wolfip.c
284+
@mkdir -p `dirname $@` || true
285+
@echo "[CC] $< (tftp-interop)"
286+
@$(CC) $(CFLAGS) -DWOLFIP_ENABLE_TFTP=1 -c $< -o $@
287+
288+
build/tftp-interop/wolftftp.o: src/tftp/wolftftp.c
289+
@mkdir -p `dirname $@` || true
290+
@echo "[CC] $< (tftp-interop)"
291+
@$(CC) $(CFLAGS) -DWOLFIP_ENABLE_TFTP=1 -DWOLFTFTP_SERVER_MAX_SESSIONS=1 \
292+
-c $< -o $@
293+
294+
build/test/test_tftp_interop.o: src/test/test_tftp_interop.c
295+
@mkdir -p `dirname $@` || true
296+
@echo "[CC] $<"
297+
@$(CC) $(CFLAGS) -DWOLFIP_ENABLE_TFTP=1 -DWOLFTFTP_SERVER_MAX_SESSIONS=1 \
298+
-c $< -o $@
299+
300+
build/test-tftp-interop: build/tftp-interop/wolfip.o \
301+
build/tftp-interop/wolftftp.o $(TAP_OBJ) \
302+
build/test/test_tftp_interop.o
303+
@echo "[LD] $@"
304+
@$(CC) $(CFLAGS) -o $@ $(BEGIN_GROUP) $(^) $(LDFLAGS) $(END_GROUP)
305+
306+
.PHONY: tftp-interop-test
307+
tftp-interop-test: build/test-tftp-interop
308+
@echo "[RUN] $< (requires root, tftpd-hpa and tftp-hpa)"
309+
@sudo -n true >/dev/null 2>&1 || { echo "tftp-interop-test needs to run as root (sudo)"; exit 1; }
310+
@sudo ./build/test-tftp-interop all
311+
260312
build/tcpecho: $(OBJ) build/port/posix/bsd_socket.o build/test/tcp_echo.o
261313
@echo "[LD] $@"
262314
@$(CC) $(CFLAGS) -o $@ $(BEGIN_GROUP) $(^) $(LDFLAGS) $(END_GROUP)
@@ -321,7 +373,7 @@ build/esp-server: $(ESP_OBJ) build/port/posix/bsd_socket.o build/test/esp_server
321373
@echo "[LD] $@"
322374
@$(CC) $(CFLAGS) $(ESP_CFLAGS) $(LDFLAGS) -o $@ $(BEGIN_GROUP) $(^) -lwolfssl $(END_GROUP)
323375

324-
build/test-wolfssl-forwarding: build/test/test_wolfssl_forwarding.o build/test/wolfip_forwarding.o $(TAP_OBJ) build/port/wolfssl_io.o build/certs/server_key.o build/certs/ca_cert.o build/certs/server_cert.o
376+
build/test-wolfssl-forwarding: build/test/test_wolfssl_forwarding.o build/test/wolfip_forwarding.o $(WOLFIP_TFTP_OBJ) $(TAP_OBJ) build/port/wolfssl_io.o build/certs/server_key.o build/certs/ca_cert.o build/certs/server_cert.o
325377
@echo "[LD] $@"
326378
@$(CC) $(CFLAGS) -o $@ $(BEGIN_GROUP) $(^) $(LDFLAGS) -lwolfssl $(END_GROUP)
327379

@@ -333,7 +385,7 @@ build/test/wolfip_forwarding.o: src/wolfip.c
333385
@$(CC) $(CFLAGS) -DWOLFIP_MAX_INTERFACES=2 -DWOLFIP_ENABLE_FORWARDING=1 -c $< -o $@
334386

335387
build/test/test_ttl_expired.o: CFLAGS+=-DWOLFIP_MAX_INTERFACES=2 -DWOLFIP_ENABLE_FORWARDING=1
336-
build/test-ttl-expired: build/test/test_ttl_expired.o build/test/wolfip_forwarding.o
388+
build/test-ttl-expired: build/test/test_ttl_expired.o build/test/wolfip_forwarding.o $(WOLFIP_TFTP_OBJ)
337389
@echo "[LD] $@"
338390
@$(CC) $(CFLAGS) -o $@ $(BEGIN_GROUP) $(^) $(LDFLAGS) $(END_GROUP)
339391

@@ -386,7 +438,8 @@ UNIT_TEST_SRCS:=src/test/unit/unit.c \
386438
src/test/unit/unit_tests_tcp_ack.c \
387439
src/test/unit/unit_tests_tcp_flow.c \
388440
src/test/unit/unit_tests_proto.c \
389-
src/test/unit/unit_tests_multicast.c
441+
src/test/unit/unit_tests_multicast.c \
442+
src/test/unit/unit_tests_tftp.c
390443

391444
unit: build/test/unit
392445

@@ -486,27 +539,40 @@ $(COV_MCAST_UNIT): $(COV_MCAST_UNIT_O)
486539
cov: unit $(COV_UNIT)
487540
@echo "[RUN] unit (coverage)"
488541
@rm -f $(COV_DIR)/*.gcda
542+
@rm -f $(COV_DIR)/unit-multicast $(COV_DIR)/unit-multicast.o \
543+
$(COV_DIR)/unit-multicast.gcno $(COV_DIR)/unit-multicast.gcda
489544
@$(COV_UNIT)
490545
@echo "[COV] gcovr html"
491546
@mkdir -p build/coverage
492-
@gcovr -r . --exclude "src/test/unit/.*" --html-details -o build/coverage/index.html
547+
@gcovr -r . --exclude "src/test/unit/.*" \
548+
--gcov-ignore-errors=no_working_dir_found \
549+
--merge-mode-functions=merge-use-line-min \
550+
--html-details -o build/coverage/index.html
493551
@$(OPEN_CMD) build/coverage/index.html
494552

495553
autocov: unit $(COV_UNIT)
496554
@echo "[RUN] unit (coverage)"
497555
@rm -f $(COV_DIR)/*.gcda
556+
@rm -f $(COV_DIR)/unit-multicast $(COV_DIR)/unit-multicast.o \
557+
$(COV_DIR)/unit-multicast.gcno $(COV_DIR)/unit-multicast.gcda
498558
@$(COV_UNIT)
499559
@echo "[COV] gcovr html"
500560
@mkdir -p build/coverage
501-
@gcovr -r . --exclude "src/test/unit/.*" --html-details -o build/coverage/index.html
561+
@gcovr -r . --exclude "src/test/unit/.*" \
562+
--gcov-ignore-errors=no_working_dir_found \
563+
--merge-mode-functions=merge-use-line-min \
564+
--html-details -o build/coverage/index.html
502565

503566
autocov-multicast: unit-multicast $(COV_MCAST_UNIT)
504567
@echo "[RUN] unit multicast (coverage)"
505568
@rm -f $(COV_DIR)/*.gcda
506569
@$(COV_MCAST_UNIT)
507570
@echo "[COV] gcovr multicast html"
508571
@mkdir -p build/coverage
509-
@gcovr -r . --exclude "src/test/unit/.*" --html-details -o build/coverage/multicast.html
572+
@gcovr -r . --exclude "src/test/unit/.*" \
573+
--gcov-ignore-errors=no_working_dir_found \
574+
--merge-mode-functions=merge-use-line-min \
575+
--html-details -o build/coverage/multicast.html
510576

511577
# Install dynamic library to re-link linux applications
512578
#

README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ configured to forward traffic between multiple network interfaces.
1818
- Multi-interface support
1919
- Optional IPv4-forwarding
2020
- Optional IPv4 UDP multicast with IGMPv3 ASM membership reports
21+
- Reusable allocation-free TFTP module under `src/tftp/`
2122

2223
## Supported socket types
2324

@@ -53,6 +54,7 @@ wolfIP exposes a BSD-like `socket(2)` API for IPv4 sockets:
5354
| **Application** | DHCP | Client only (DORA) | [RFC 2131](https://datatracker.ietf.org/doc/html/rfc2131) |
5455
| **Application** | DNS | A and PTR record queries (client) | [RFC 1035](https://datatracker.ietf.org/doc/html/rfc1035) |
5556
| **Application** | HTTP/HTTPS | Server with wolfSSL TLS support | [RFC 9110](https://datatracker.ietf.org/doc/html/rfc9110) |
57+
| **Application** | TFTP | Client/server octet-mode transfers with callback-driven storage and verification | [RFC 1350](https://datatracker.ietf.org/doc/html/rfc1350), [RFC 2347](https://datatracker.ietf.org/doc/html/rfc2347), [RFC 2348](https://datatracker.ietf.org/doc/html/rfc2348), [RFC 2349](https://datatracker.ietf.org/doc/html/rfc2349), [RFC 7440](https://datatracker.ietf.org/doc/html/rfc7440) |
5658
| **VPN** | wolfGuard | FIPS-compliant WireGuard (P-256, AES-256-GCM, SHA-256) | [Wolfguard](https://www.github.com/wolfssl/wireguard) |
5759

5860
## wolfGuard (FIPS WireGuard)
@@ -180,6 +182,14 @@ This port follows the same model as the POSIX wrapper:
180182
- Socket wrappers serialize stack access with a mutex
181183
- Blocking operations wait on callback-driven wakeups (instead of busy polling)
182184

185+
## Source Layout
186+
187+
- `src/wolfip.c`: core TCP/IP stack
188+
- `src/http/`: optional HTTP/HTTPS server pieces
189+
- `src/tftp/`: reusable TFTP module sources, auto-registered by the top-level `Makefile` and `CMakeLists.txt` when present
190+
- `src/port/`: platform and OS adaptation layers
191+
- `src/test/`: integration and unit tests
192+
183193
## Copyright and License
184194

185195
wolfIP is licensed under the GPLv3 license. See the LICENSE file for details.

config.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,10 @@
7070
#define WOLFIP_ENABLE_HTTP
7171
#endif
7272

73+
#ifndef WOLFIP_ENABLE_TFTP
74+
#define WOLFIP_ENABLE_TFTP 0
75+
#endif
76+
7377
#if WOLFIP_ENABLE_LOOPBACK && WOLFIP_MAX_INTERFACES < 2
7478
#error "WOLFIP_ENABLE_LOOPBACK requires WOLFIP_MAX_INTERFACES > 1"
7579
#endif

docs/API.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,25 @@ wolfIP is a minimal TCP/IP stack designed for resource-constrained embedded syst
1616
- ICMP (RFC 792) - ping replies only
1717
- DHCP (RFC 2131) - client only
1818
- DNS (RFC 1035) - client only
19+
- TFTP (RFC 1350, RFC 2347, RFC 2348, RFC 2349, RFC 7440) via the reusable `src/tftp/` module
1920
- UDP (RFC 768) - unicast, optional IPv4 multicast with `IP_MULTICAST`
2021
- TCP (RFC 793) with options (Timestamps, MSS)
2122

23+
## Build Integration
24+
25+
The top-level build systems register reusable module sources from `src/tftp/`
26+
automatically:
27+
28+
- `Makefile` adds any `src/tftp/*.c` files to the shared library, static library,
29+
and top-level executable link sets.
30+
- `CMakeLists.txt` globs `src/tftp/*.c` with `CONFIGURE_DEPENDS` so the same
31+
sources are compiled into the main `wolfip` and `tcpip` targets.
32+
33+
The TFTP module is callback-driven and allocation-free. Callers provide the UDP
34+
send hook plus open/read/write/close callbacks for storage, and may additionally
35+
provide streaming hash-update and final verification callbacks for firmware
36+
download flows.
37+
2238
## Core Data Structures
2339

2440
### Device Driver Interface

0 commit comments

Comments
 (0)