Summary
AF_NETLINK / NETLINK_ROUTE sockets only support bind/sendmsg/recvmsg/read. The flat-buffer socket calls sendto(2), recvfrom(2) and getsockname(2) are not dispatched to the netlink emulation, so they fall through to the real host socket calls on the netlink fd's pipe backing and fail with ENOTSOCK (errno 88, "Socket operation on non-socket"). This breaks the most common interface-enumeration path: glibc getifaddrs() issues sendto(2) on a NETLINK_ROUTE socket internally and therefore fails outright.
Separately, RTM_GETLINK ignores the request filter (ifi_index / IFLA_IFNAME): a single-link lookup returns every interface instead of the one requested.
Reproduction
Generic, no manual netlink message construction — glibc does it for us:
// repro.c
#define _GNU_SOURCE
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <ifaddrs.h>
int main(void) {
struct ifaddrs *ifa, *p;
if (getifaddrs(&ifa) < 0) {
printf("getifaddrs: FAILED errno=%d (%s)\n", errno, strerror(errno));
return 1;
}
int n = 0; for (p = ifa; p; p = p->ifa_next) n++;
printf("getifaddrs: OK, %d address entries\n", n);
freeifaddrs(ifa); return 0;
}
$ aarch64-linux-gnu-gcc -D_GNU_SOURCE -static -O2 -o repro repro.c
$ ./elfuse -- ./repro
getifaddrs: FAILED errno=88 (Socket operation on non-socket)
Per-call confirmation (direct syscalls on an AF_NETLINK/SOCK_RAW/NETLINK_ROUTE socket):
getsockname: ret=-1 errno=88 (Socket operation on non-socket)
sendto: ret=-1 errno=88 (Socket operation on non-socket)
recvfrom: ret=-1 errno=88 (Socket operation on non-socket)
Filter bug (observed via the sendmsg path, which is dispatched): a non-DUMP RTM_GETLINK with ifi_index=1 returns RTM_NEWLINK count=24 instead of 1.
Expected vs Actual
|
Expected |
Actual (this build) |
glibc getifaddrs() |
succeeds, lists addresses |
FAILED errno=88 (ENOTSOCK) |
getsockname on netlink fd |
returns bound nl_pid, family AF_NETLINK |
-1 ENOTSOCK |
sendto/recvfrom on netlink fd |
transfer rtnetlink messages |
-1 ENOTSOCK |
RTM_GETLINK with ifi_index=N (no DUMP) |
exactly the matching link |
every interface |
Root cause (file:line)
Netlink sockets are synthetic: socket(AF_NETLINK,...) allocates an FD_NETLINK descriptor whose host backing is the read end of a pipe — src/syscall/netlink.c:418 (fd_alloc(FD_NETLINK, pipefd[0], ...)). bind/sendmsg/recvmsg/read branch on fd_get_type(fd) == FD_NETLINK, but three handlers do not:
sys_getsockname — src/syscall/net.c:507 (no FD_NETLINK branch; calls host_fd_ref_open() then host getsockname() on the pipe fd)
sys_sendto — src/syscall/net.c:634 (same; host send()/sendto() on the pipe)
sys_recvfrom — src/syscall/net.c:701 (same; host recv()/recvfrom() on the pipe)
host_fd_ref_open() (src/syscall/internal.h:245) returns the pipe's host fd, and a socket op on a pipe fd is ENOTSOCK on macOS.
Filter bug: nl_build_getlink(ns) (src/syscall/netlink.c:192) takes no filter and emits one RTM_NEWLINK per host interface unconditionally; the RTM_GETLINK request's ifi_index/IFLA_IFNAME are never parsed.
Affected programs
Any program that enumerates or looks up network interfaces over rtnetlink:
- glibc
getifaddrs() / if_nameindex() (NETLINK_ROUTE-based, sendto-driven) — breaks every C/C++ interface enumeration.
- Go
net package and vishvananda/netlink (sendto/recvfrom + getsockname for the auto-assigned nl_pid).
- iproute2
ip/ss, busybox ip (rtnetlink link/addr/route dumps).
- systemd / systemd-networkd / udev (sd-netlink), NetworkManager, dhcpcd/udhcpc (rtnetlink monitoring).
- Rust std interface enumeration and the
nix/rtnetlink crates.
- Anything doing a
LinkByName/LinkByIndex-style single-interface lookup additionally gets the wrong (all-interfaces) result and may error with "more than one link found".
Fix
Addressed by adding netlink_send/netlink_recv/netlink_getsockname and FD_NETLINK dispatch in sys_sendto/sys_recvfrom/sys_getsockname, plus a shared nl_parse_link_filter so the sendmsg and sendto request paths both honor ifi_index/IFLA_IFNAME. After the fix, getifaddrs() returns 38 entries, the three calls succeed, and the filtered RTM_GETLINK returns exactly 1 link.
First observed while bringing up a Go-based control plane (k0s): its node-IP/SAN detection fell back to 127.0.0.1 because the netlink path returned ENOTSOCK. The bug itself is generic to the netlink emulation and independent of any particular workload.
Summary
AF_NETLINK / NETLINK_ROUTE sockets only support
bind/sendmsg/recvmsg/read. The flat-buffer socket callssendto(2),recvfrom(2)andgetsockname(2)are not dispatched to the netlink emulation, so they fall through to the real host socket calls on the netlink fd's pipe backing and fail with ENOTSOCK (errno 88, "Socket operation on non-socket"). This breaks the most common interface-enumeration path: glibcgetifaddrs()issuessendto(2)on a NETLINK_ROUTE socket internally and therefore fails outright.Separately,
RTM_GETLINKignores the request filter (ifi_index/IFLA_IFNAME): a single-link lookup returns every interface instead of the one requested.Reproduction
Generic, no manual netlink message construction — glibc does it for us:
Per-call confirmation (direct syscalls on an
AF_NETLINK/SOCK_RAW/NETLINK_ROUTEsocket):Filter bug (observed via the
sendmsgpath, which is dispatched): a non-DUMPRTM_GETLINKwithifi_index=1returnsRTM_NEWLINK count=24instead of1.Expected vs Actual
getifaddrs()FAILED errno=88 (ENOTSOCK)getsocknameon netlink fdnl_pid, familyAF_NETLINK-1 ENOTSOCKsendto/recvfromon netlink fd-1 ENOTSOCKRTM_GETLINKwithifi_index=N(no DUMP)Root cause (file:line)
Netlink sockets are synthetic:
socket(AF_NETLINK,...)allocates anFD_NETLINKdescriptor whose host backing is the read end of a pipe —src/syscall/netlink.c:418(fd_alloc(FD_NETLINK, pipefd[0], ...)).bind/sendmsg/recvmsg/readbranch onfd_get_type(fd) == FD_NETLINK, but three handlers do not:sys_getsockname—src/syscall/net.c:507(noFD_NETLINKbranch; callshost_fd_ref_open()then hostgetsockname()on the pipe fd)sys_sendto—src/syscall/net.c:634(same; hostsend()/sendto()on the pipe)sys_recvfrom—src/syscall/net.c:701(same; hostrecv()/recvfrom()on the pipe)host_fd_ref_open()(src/syscall/internal.h:245) returns the pipe's host fd, and a socket op on a pipe fd is ENOTSOCK on macOS.Filter bug:
nl_build_getlink(ns)(src/syscall/netlink.c:192) takes no filter and emits oneRTM_NEWLINKper host interface unconditionally; theRTM_GETLINKrequest'sifi_index/IFLA_IFNAMEare never parsed.Affected programs
Any program that enumerates or looks up network interfaces over rtnetlink:
getifaddrs()/if_nameindex()(NETLINK_ROUTE-based,sendto-driven) — breaks every C/C++ interface enumeration.netpackage andvishvananda/netlink(sendto/recvfrom+getsocknamefor the auto-assignednl_pid).ip/ss, busyboxip(rtnetlink link/addr/route dumps).nix/rtnetlinkcrates.LinkByName/LinkByIndex-style single-interface lookup additionally gets the wrong (all-interfaces) result and may error with "more than one link found".Fix
Addressed by adding
netlink_send/netlink_recv/netlink_getsocknameandFD_NETLINKdispatch insys_sendto/sys_recvfrom/sys_getsockname, plus a sharednl_parse_link_filterso thesendmsgandsendtorequest paths both honorifi_index/IFLA_IFNAME. After the fix,getifaddrs()returns 38 entries, the three calls succeed, and the filteredRTM_GETLINKreturns exactly 1 link.First observed while bringing up a Go-based control plane (k0s): its node-IP/SAN detection fell back to 127.0.0.1 because the netlink path returned ENOTSOCK. The bug itself is generic to the netlink emulation and independent of any particular workload.