Skip to content

AF_NETLINK sockets reject sendto/recvfrom/getsockname (ENOTSOCK), and RTM_GETLINK ignores the ifi_index/IFLA_IFNAME filter #53

@chenhunghan

Description

@chenhunghan

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 pipesrc/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_getsocknamesrc/syscall/net.c:507 (no FD_NETLINK branch; calls host_fd_ref_open() then host getsockname() on the pipe fd)
  • sys_sendtosrc/syscall/net.c:634 (same; host send()/sendto() on the pipe)
  • sys_recvfromsrc/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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions