Skip to content

Commit 21c0df2

Browse files
authored
Merge pull request #70 from danielinux/l3_dev
Added support for L3, non-ethernet interfaces
2 parents b0f6ee9 + 3e65311 commit 21c0df2

10 files changed

Lines changed: 1181 additions & 73 deletions

File tree

.github/workflows/linux.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,13 @@ jobs:
3434
timeout --preserve-status 5m sudo ./build/test-evloop
3535
sudo killall tcpdump || true
3636
37+
- name: Run standalone "event loop" TUN test
38+
timeout-minutes: 5
39+
run: |
40+
set -euo pipefail
41+
timeout --preserve-status 5m sudo ./build/test-evloop-tun
42+
sudo killall tcpdump || true
43+
3744
- name: Run standalone "IPsec esp" test
3845
timeout-minutes: 7
3946
run: |

CMakeLists.txt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,15 @@ add_executable(test-evloop ${EXCLUDE_TEST_BINARY}
9393
)
9494
add_test(NAME evloop COMMAND test-evloop)
9595

96+
if (CMAKE_SYSTEM_NAME STREQUAL "Linux")
97+
add_executable(test-evloop-tun ${EXCLUDE_TEST_BINARY}
98+
src/test/test_eventloop_tun.c
99+
src/port/posix/linux_tun.c
100+
${WOLFIP_SRCS}
101+
)
102+
add_test(NAME evloop-tun COMMAND test-evloop-tun)
103+
endif()
104+
96105
add_executable(test-dns ${EXCLUDE_TEST_BINARY}
97106
src/test/test_dhcp_dns.c
98107
${WOLFIP_SRCS}

Makefile

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,9 @@ EXE=build/tcpecho build/tcp_netcat_poll build/tcp_netcat_select \
156156
build/test-evloop build/test-dns build/test-wolfssl-forwarding \
157157
build/test-ttl-expired build/test-wolfssl build/test-httpd \
158158
build/ipfilter-logger build/test-esp build/esp-server
159+
ifeq ($(UNAME_S),Linux)
160+
EXE+= build/test-evloop-tun
161+
endif
159162
LIB=libwolfip.so
160163

161164
PREFIX=/usr/local
@@ -217,6 +220,10 @@ build/test-evloop: $(OBJ) build/test/test_eventloop.o
217220
@echo "[LD] $@"
218221
@$(CC) $(CFLAGS) -o $@ $(BEGIN_GROUP) $(^) $(LDFLAGS) $(END_GROUP)
219222

223+
build/test-evloop-tun: $(OBJ) build/test/test_eventloop_tun.o build/port/posix/linux_tun.o
224+
@echo "[LD] $@"
225+
@$(CC) $(CFLAGS) -o $@ $(BEGIN_GROUP) $(^) $(LDFLAGS) $(END_GROUP)
226+
220227
build/test-dns: $(OBJ) build/test/test_dhcp_dns.o
221228
@echo "[LD] $@"
222229
@$(CC) $(CFLAGS) -o $@ $(BEGIN_GROUP) $(^) $(LDFLAGS) $(END_GROUP)

docs/API.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,15 @@ wolfIP is a minimal TCP/IP stack designed for resource-constrained embedded syst
2626
struct wolfIP_ll_dev {
2727
uint8_t mac[6]; // Device MAC address
2828
char ifname[16]; // Interface name
29+
uint8_t non_ethernet; // L3-only link (no Ethernet header/ARP when set)
2930
int (*poll)(struct wolfIP_ll_dev *ll, void *buf, uint32_t len); // Receive function
3031
int (*send)(struct wolfIP_ll_dev *ll, void *buf, uint32_t len); // Transmit function
3132
};
3233
```
3334
wolfIP maintains an array of these descriptors sized by `WOLFIP_MAX_INTERFACES` (default `1`). Call `wolfIP_getdev_ex()` to access a specific slot; the legacy `wolfIP_getdev()` helper targets the first hardware slot (index `0` normally, or `1` when the optional loopback interface is enabled).
3435

36+
When `non_ethernet` is set, the interface is treated as L3-only point-to-point: the stack skips ARP/neighbor resolution, omits Ethernet headers on transmit, and expects receive buffers to begin at the IP header.
37+
3538
### IP Configuration
3639
```c
3740
struct ipconf {

src/port/posix/linux_tun.c

Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
1+
/* linux_tun.c
2+
*
3+
* Linux TUN (L3) interface for wolfIP.
4+
*
5+
* Copyright (C) 2026 wolfSSL Inc.
6+
*
7+
* This file is part of wolfIP TCP/IP stack.
8+
*
9+
* wolfIP is free software; you can redistribute it and/or modify
10+
* it under the terms of the GNU General Public License as published by
11+
* the Free Software Foundation; either version 3 of the License, or
12+
* (at your option) any later version.
13+
*
14+
* wolfIP is distributed in the hope that it will be useful,
15+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
16+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17+
* GNU General Public License for more details.
18+
*
19+
* You should have received a copy of the GNU General Public License
20+
* along with this program; if not, write to the Free Software
21+
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA
22+
*/
23+
#include <arpa/inet.h>
24+
#include <fcntl.h>
25+
#include <linux/if_tun.h>
26+
#include <net/if.h>
27+
#include <netinet/in.h>
28+
#include <stdint.h>
29+
#include <stdio.h>
30+
#include <string.h>
31+
#include <sys/ioctl.h>
32+
#include <sys/poll.h>
33+
#include <sys/types.h>
34+
#include <sys/socket.h>
35+
#include <unistd.h>
36+
#include <linux/netlink.h>
37+
#include <linux/rtnetlink.h>
38+
39+
#define WOLF_POSIX
40+
#include "config.h"
41+
#include "wolfip.h"
42+
#undef WOLF_POSIX
43+
44+
static int tun_fd = -1;
45+
46+
static int tun_poll(struct wolfIP_ll_dev *ll, void *buf, uint32_t len)
47+
{
48+
struct pollfd pfd;
49+
int ret;
50+
(void)ll;
51+
if (tun_fd < 0)
52+
return -1;
53+
pfd.fd = tun_fd;
54+
pfd.events = POLLIN;
55+
ret = poll(&pfd, 1, 2);
56+
if (ret < 0) {
57+
perror("poll");
58+
return -1;
59+
}
60+
if (ret == 0)
61+
return 0;
62+
return read(tun_fd, buf, len);
63+
}
64+
65+
static int tun_send(struct wolfIP_ll_dev *ll, void *buf, uint32_t len)
66+
{
67+
(void)ll;
68+
if (tun_fd < 0)
69+
return -1;
70+
return write(tun_fd, buf, len);
71+
}
72+
73+
static int nl_addattr(struct nlmsghdr *nlh, size_t maxlen, int type,
74+
const void *data, size_t alen)
75+
{
76+
size_t len = RTA_LENGTH(alen);
77+
struct rtattr *rta;
78+
size_t newlen = NLMSG_ALIGN(nlh->nlmsg_len) + RTA_ALIGN(len);
79+
80+
if (newlen > maxlen)
81+
return -1;
82+
rta = (struct rtattr *)((char *)nlh + NLMSG_ALIGN(nlh->nlmsg_len));
83+
rta->rta_type = type;
84+
rta->rta_len = (unsigned short)len;
85+
if (alen > 0 && data)
86+
memcpy(RTA_DATA(rta), data, alen);
87+
nlh->nlmsg_len = (unsigned int)newlen;
88+
return 0;
89+
}
90+
91+
static int tun_add_host_route(const char *ifname, uint32_t peer_ip)
92+
{
93+
int fd;
94+
int ifindex;
95+
struct {
96+
struct nlmsghdr nlh;
97+
struct rtmsg rtm;
98+
char buf[128];
99+
} req;
100+
struct sockaddr_nl nladdr;
101+
102+
ifindex = if_nametoindex(ifname);
103+
if (ifindex == 0)
104+
return -1;
105+
106+
memset(&req, 0, sizeof(req));
107+
req.nlh.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg));
108+
req.nlh.nlmsg_type = RTM_NEWROUTE;
109+
req.nlh.nlmsg_flags = NLM_F_REQUEST | NLM_F_CREATE | NLM_F_REPLACE;
110+
req.nlh.nlmsg_seq = 1;
111+
req.rtm.rtm_family = AF_INET;
112+
req.rtm.rtm_dst_len = 32;
113+
req.rtm.rtm_table = RT_TABLE_MAIN;
114+
req.rtm.rtm_protocol = RTPROT_BOOT;
115+
req.rtm.rtm_scope = RT_SCOPE_LINK;
116+
req.rtm.rtm_type = RTN_UNICAST;
117+
118+
if (nl_addattr(&req.nlh, sizeof(req), RTA_DST, &peer_ip, sizeof(peer_ip)) < 0)
119+
return -1;
120+
if (nl_addattr(&req.nlh, sizeof(req), RTA_OIF, &ifindex, sizeof(ifindex)) < 0)
121+
return -1;
122+
123+
fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
124+
if (fd < 0)
125+
return -1;
126+
memset(&nladdr, 0, sizeof(nladdr));
127+
nladdr.nl_family = AF_NETLINK;
128+
if (sendto(fd, &req, req.nlh.nlmsg_len, 0,
129+
(struct sockaddr *)&nladdr, sizeof(nladdr)) < 0) {
130+
close(fd);
131+
return -1;
132+
}
133+
close(fd);
134+
return 0;
135+
}
136+
137+
int tun_init(struct wolfIP_ll_dev *ll, const char *ifname,
138+
uint32_t host_ip, uint32_t peer_ip)
139+
{
140+
struct ifreq ifr;
141+
struct sockaddr_in *addr;
142+
int sock_fd;
143+
144+
memset(&ifr, 0, sizeof(ifr));
145+
ifr.ifr_flags = IFF_TUN | IFF_NO_PI;
146+
strncpy(ifr.ifr_name, ifname, IFNAMSIZ);
147+
ifr.ifr_name[IFNAMSIZ - 1] = '\0';
148+
149+
tun_fd = open("/dev/net/tun", O_RDWR);
150+
if (tun_fd < 0 || ioctl(tun_fd, TUNSETIFF, (void *)&ifr) != 0) {
151+
perror("ioctl TUNSETIFF");
152+
if (tun_fd >= 0) {
153+
close(tun_fd);
154+
tun_fd = -1;
155+
}
156+
return -1;
157+
}
158+
{
159+
int flags = fcntl(tun_fd, F_GETFL, 0);
160+
if (flags >= 0)
161+
(void)fcntl(tun_fd, F_SETFL, flags | O_NONBLOCK);
162+
}
163+
164+
if (ll) {
165+
memset(ll->mac, 0, sizeof(ll->mac));
166+
strncpy(ll->ifname, ifname, sizeof(ll->ifname) - 1);
167+
ll->ifname[sizeof(ll->ifname) - 1] = '\0';
168+
ll->non_ethernet = 1;
169+
ll->poll = tun_poll;
170+
ll->send = tun_send;
171+
}
172+
173+
sock_fd = socket(AF_INET, SOCK_DGRAM, 0);
174+
if (sock_fd < 0) {
175+
perror("socket");
176+
close(tun_fd);
177+
tun_fd = -1;
178+
return -1;
179+
}
180+
181+
memset(&ifr, 0, sizeof(ifr));
182+
strncpy(ifr.ifr_name, ifname, IFNAMSIZ);
183+
ifr.ifr_name[IFNAMSIZ - 1] = '\0';
184+
if (ioctl(sock_fd, SIOCGIFFLAGS, &ifr) < 0) {
185+
perror("ioctl SIOCGIFFLAGS");
186+
close(sock_fd);
187+
close(tun_fd);
188+
tun_fd = -1;
189+
return -1;
190+
}
191+
ifr.ifr_flags |= (IFF_UP | IFF_RUNNING | IFF_POINTOPOINT);
192+
if (ioctl(sock_fd, SIOCSIFFLAGS, &ifr) < 0) {
193+
perror("ioctl SIOCSIFFLAGS");
194+
close(sock_fd);
195+
close(tun_fd);
196+
tun_fd = -1;
197+
return -1;
198+
}
199+
200+
addr = (struct sockaddr_in *)&ifr.ifr_addr;
201+
addr->sin_family = AF_INET;
202+
addr->sin_addr.s_addr = host_ip;
203+
if (ioctl(sock_fd, SIOCSIFADDR, &ifr) < 0) {
204+
perror("ioctl SIOCSIFADDR");
205+
close(sock_fd);
206+
close(tun_fd);
207+
tun_fd = -1;
208+
return -1;
209+
}
210+
211+
addr = (struct sockaddr_in *)&ifr.ifr_dstaddr;
212+
addr->sin_family = AF_INET;
213+
addr->sin_addr.s_addr = peer_ip;
214+
if (ioctl(sock_fd, SIOCSIFDSTADDR, &ifr) < 0) {
215+
perror("ioctl SIOCSIFDSTADDR");
216+
close(sock_fd);
217+
close(tun_fd);
218+
tun_fd = -1;
219+
return -1;
220+
}
221+
222+
addr = (struct sockaddr_in *)&ifr.ifr_netmask;
223+
addr->sin_family = AF_INET;
224+
addr->sin_addr.s_addr = htonl(0xFFFFFFFFU);
225+
if (ioctl(sock_fd, SIOCSIFNETMASK, &ifr) < 0) {
226+
perror("ioctl SIOCSIFNETMASK");
227+
close(sock_fd);
228+
close(tun_fd);
229+
tun_fd = -1;
230+
return -1;
231+
}
232+
233+
(void)tun_add_host_route(ifname, peer_ip);
234+
printf("Successfully initialized tun device %s\n", ifname);
235+
close(sock_fd);
236+
return 0;
237+
}

0 commit comments

Comments
 (0)