Accept uevent netlink sockets as silent

Merged
JO
2mo ago
+2164-37

[!NOTE]
First piece of the general USB translation layer discussed in #319; it stands alone as a bug fix and does not depend on the rest of that series.

Summary

socket(AF_NETLINK, SOCK_RAW, NETLINK_KOBJECT_UEVENT) now returns a working silent socket. libusb's non-udev build refuses to initialize at all without one (linux_usbfs.c:416-430 aborts libusb_init when the hotplug monitor cannot start), so every libusb program in a guest died at init, whether or not it ever touched a device.

What

  • NETLINK_KOBJECT_UEVENT sockets are accepted next to NETLINK_ROUTE: never readable, so poll times out and a non-blocking recv reports EAGAIN, which is what a real Linux kernel with no hotplug activity looks like. Synthesizing uevents on device attach is deliberately out of scope here.
  • sendto on a uevent socket reports EPERM, matching uevent_net_rcv_skb() for a sender without CAP_SYS_ADMIN.
  • SOL_SOCKET options work on netlink fds; they previously failed EBADF because the sockopt path accepted only FD_SOCKET. The set follows sk_setsockopt()/sk_getsockopt() rather than accepting everything: SO_RCVBUF/SO_SNDBUF clamp to rmem_max/wmem_max as an unsigned value, double, then floor at SOCK_MIN_RCVBUF (2304) or SOCK_MIN_SNDBUF (4608), which differ; SO_RCVTIMEO is honored by the blocking receive path (deadline taken once on entry, EAGAIN on expiry) rather than accepted and dropped; SO_LINGER and the timeout options require their real payload length (8 and 16) instead of four bytes; SO_DEBUG with a nonzero value needs CAP_NET_ADMIN (EACCES); SO_REUSEPORT with a nonzero value is EOPNOTSUPP on a non-inet socket; SO_SNDLOWAT has no setter at all (ENOPROTOOPT); and everything settable reads back through getsockopt with the right payload length. Unrecognized options report ENOPROTOOPT.
  • SOCK_NONBLOCK/SOCK_CLOEXEC are now published into the fd's linux_flags. That was dropped before, and it affected NETLINK_ROUTE sockets too.
  • New guest test tests/test-uevent-socket.c, wired into test-matrix.sh. Its assertions also hold on a real Linux kernel: a payload below NLMSG_HDRLEN is swallowed there and sendto returns its length, so the test accepts either outcome and pins EPERM whenever the send fails.

Evidence

A statically linked libusb test (current libusb git, --disable-udev, so the netlink monitor is the one in play), run under the same host and guest binary on both sides:

upstream main 628181b:   libusb_init = -99 (LIBUSB_ERROR_OTHER)
this branch:             libusb_init = 0 (LIBUSB_SUCCESS)
                         get_device_list = 0

get_device_list returns 0 because enumeration needs the synthetic /sys/bus/usb and /dev/bus/usb trees that come later in the series; init succeeding is what this change is for.

test-uevent-socket under elfuse: 40 passed, 0 failed
test-matrix elfuse-aarch64: 266 passed, 0 failed, 10 skipped

The test asserts relations rather than constants, because the floors derive from sizeof(struct sk_buff) and the caps are sysctls: the send floor is checked to be exactly twice the receive floor, -1 and INT_MAX are checked to read back equal, and the honored SO_RCVTIMEO is timed with a jiffy of slack. Those assertions hold on a real Linux kernel, which matters because this test runs in the qemu-aarch64 matrix lane as well.

make check exits 0 on a clean rebuild (host suite 27/27, guardrail PASS), and the NETLINK_ROUTE behavior is unchanged.