Skip to content

Commit f2704fa

Browse files
committed
Add netlink getsockname/sendto/recvfrom regression test
make check did not exercise the getsockname/sendto/recvfrom paths that the netlink emulation now handles, so a regression reverting them to the host socket fd (ENOTSOCK) would pass unnoticed. Add tests/test-netlink.c and register it in the manifest. The test drives each dispatched syscall directly against a NETLINK_ROUTE socket: getsockname must report an AF_NETLINK address and a non-zero port id, a flat RTM_GETLINK dump request must be accepted by sendto, and recvfrom must drain at least one RTM_NEWLINK message with an AF_NETLINK source. It then runs glibc getifaddrs(), the end-to-end call from issue #53 that originally failed with ENOTSOCK. Only implementation-independent netlink semantics are asserted, so the same static binary passes under a real kernel as well as the emulation. Validated with make check on Apple Silicon (9/9 checks pass); the same binary run against an elfuse built from main reproduces the bug with 7 ENOTSOCK failures, confirming the test catches the regression.
1 parent 47fc927 commit f2704fa

2 files changed

Lines changed: 137 additions & 0 deletions

File tree

tests/manifest.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ test-sysfs-cpu
7171
[section] Network tests
7272
test-net
7373
test-netstat
74+
test-netlink
7475

7576
[section] Threading tests
7677
test-thread # diff=skip

tests/test-netlink.c

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
/* Exercise the AF_NETLINK getsockname/sendto/recvfrom dispatch paths.
2+
*
3+
* Copyright 2026 elfuse contributors
4+
* Copyright 2025 Moritz Angermann, zw3rk pte. ltd.
5+
* SPDX-License-Identifier: Apache-2.0
6+
*
7+
* Regression guard for the netlink socket emulation. Before getsockname,
8+
* sendto, and recvfrom were routed to the netlink handlers, these calls fell
9+
* through to the host socket syscalls on the underlying pipe fd and failed
10+
* with ENOTSOCK (errno 88), which in turn broke glibc getifaddrs(). The test
11+
* drives each of the three syscalls directly against a NETLINK_ROUTE socket
12+
* and then validates the end-to-end getifaddrs() path that originally
13+
* regressed.
14+
*
15+
* The assertions hold for both the elfuse emulation and a real Linux kernel
16+
* (the test matrix runs the same binary under qemu-aarch64), so only
17+
* implementation-independent netlink semantics are checked.
18+
*/
19+
20+
#include <errno.h>
21+
#include <ifaddrs.h>
22+
#include <stdio.h>
23+
#include <string.h>
24+
#include <unistd.h>
25+
26+
#include <sys/socket.h>
27+
#include <net/if.h>
28+
#include <linux/netlink.h>
29+
#include <linux/rtnetlink.h>
30+
31+
static int pass, fail;
32+
33+
#define CHECK(cond, msg) \
34+
do { \
35+
if (cond) { \
36+
printf("PASS: %s\n", (msg)); \
37+
pass++; \
38+
} else { \
39+
printf("FAIL: %s (errno=%d %s)\n", (msg), errno, strerror(errno)); \
40+
fail++; \
41+
} \
42+
} while (0)
43+
44+
/* RTM_GETLINK dump request: nlmsghdr immediately followed by ifinfomsg. */
45+
struct getlink_req {
46+
struct nlmsghdr nlh;
47+
struct ifinfomsg ifi;
48+
};
49+
50+
int main(void)
51+
{
52+
int fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
53+
if (fd < 0) {
54+
printf("FAIL: socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE): %s\n",
55+
strerror(errno));
56+
/* Without a socket none of the dispatch paths can be reached. */
57+
printf("\n%d passed, %d failed\n", pass, fail + 1);
58+
return 1;
59+
}
60+
printf("PASS: socket(AF_NETLINK, NETLINK_ROUTE) = %d\n", fd);
61+
pass++;
62+
63+
/* bind() with nl_pid=0 lets the kernel/emulation assign a port id. */
64+
struct sockaddr_nl local = {.nl_family = AF_NETLINK};
65+
CHECK(bind(fd, (struct sockaddr *) &local, sizeof(local)) == 0,
66+
"bind(AF_NETLINK)");
67+
68+
/* 1. getsockname(): previously ENOTSOCK on the pipe fd. */
69+
struct sockaddr_nl got = {0};
70+
socklen_t gotlen = sizeof(got);
71+
int rc = getsockname(fd, (struct sockaddr *) &got, &gotlen);
72+
CHECK(rc == 0 && gotlen >= sizeof(struct sockaddr_nl) &&
73+
got.nl_family == AF_NETLINK,
74+
"getsockname() returns an AF_NETLINK address");
75+
CHECK(rc == 0 && got.nl_pid != 0,
76+
"getsockname() reports a non-zero port id");
77+
78+
/* 2. sendto(): flat request buffer, no msghdr. */
79+
struct getlink_req req = {0};
80+
req.nlh.nlmsg_len = sizeof(req);
81+
req.nlh.nlmsg_type = RTM_GETLINK;
82+
req.nlh.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP;
83+
req.nlh.nlmsg_seq = 1;
84+
req.ifi.ifi_family = AF_UNSPEC;
85+
86+
struct sockaddr_nl kernel = {.nl_family = AF_NETLINK};
87+
ssize_t sent = sendto(fd, &req, req.nlh.nlmsg_len, 0,
88+
(struct sockaddr *) &kernel, sizeof(kernel));
89+
CHECK(sent == (ssize_t) req.nlh.nlmsg_len,
90+
"sendto(RTM_GETLINK) accepts the request");
91+
92+
/* 3. recvfrom(): drain the dump, expecting RTM_NEWLINK then NLMSG_DONE. */
93+
int saw_newlink = 0, saw_done = 0, src_ok = 0;
94+
for (int iter = 0; iter < 64 && !saw_done; iter++) {
95+
char buf[8192];
96+
struct sockaddr_nl src = {0};
97+
socklen_t srclen = sizeof(src);
98+
ssize_t n = recvfrom(fd, buf, sizeof(buf), 0, (struct sockaddr *) &src,
99+
&srclen);
100+
if (n <= 0)
101+
break;
102+
if (srclen >= sizeof(struct sockaddr_nl) && src.nl_family == AF_NETLINK)
103+
src_ok = 1;
104+
for (struct nlmsghdr *nlh = (struct nlmsghdr *) buf;
105+
NLMSG_OK(nlh, (unsigned) n); nlh = NLMSG_NEXT(nlh, n)) {
106+
if (nlh->nlmsg_type == RTM_NEWLINK)
107+
saw_newlink = 1;
108+
else if (nlh->nlmsg_type == NLMSG_DONE)
109+
saw_done = 1;
110+
else if (nlh->nlmsg_type == NLMSG_ERROR)
111+
saw_done = 1; /* stop draining on error terminator */
112+
}
113+
}
114+
CHECK(saw_newlink, "recvfrom() returns at least one RTM_NEWLINK");
115+
CHECK(src_ok, "recvfrom() fills an AF_NETLINK source address");
116+
117+
close(fd);
118+
119+
/* 4. End-to-end: glibc getifaddrs() drives getsockname + sendto + recv
120+
* internally. This is the exact call that regressed with ENOTSOCK.
121+
*/
122+
struct ifaddrs *ifa = NULL;
123+
rc = getifaddrs(&ifa);
124+
CHECK(rc == 0, "getifaddrs() succeeds");
125+
int n_ifaces = 0;
126+
for (struct ifaddrs *p = ifa; p; p = p->ifa_next)
127+
if (p->ifa_name && p->ifa_name[0])
128+
n_ifaces++;
129+
CHECK(rc == 0 && n_ifaces > 0,
130+
"getifaddrs() enumerates at least one interface");
131+
if (ifa)
132+
freeifaddrs(ifa);
133+
134+
printf("\n%d passed, %d failed\n", pass, fail);
135+
return fail ? 1 : 0;
136+
}

0 commit comments

Comments
 (0)