From b98c5f2e219c142ab89c09445b6c014120f0760f Mon Sep 17 00:00:00 2001 From: Ophestra Date: Wed, 25 Mar 2026 14:06:59 +0900 Subject: [PATCH] internal/netlink: nonblocking socket I/O This enables use with blocking calls like when used with NETLINK_KOBJECT_UEVENT. Signed-off-by: Ophestra --- internal/netlink/netlink.go | 59 ++++++++++++++++++++++++++++++++----- 1 file changed, 51 insertions(+), 8 deletions(-) diff --git a/internal/netlink/netlink.go b/internal/netlink/netlink.go index 5df7b390..df6af840 100644 --- a/internal/netlink/netlink.go +++ b/internal/netlink/netlink.go @@ -24,7 +24,9 @@ func getpid() uint32 { // A conn represents resources associated to a netlink socket. type conn struct { // AF_NETLINK socket. - fd int + f *os.File + // For using runtime polling via f. + raw syscall.RawConn // Kernel module or netlink group to communicate with. family int // Message sequence number. @@ -42,7 +44,7 @@ func dial(family int) (*conn, error) { var c conn if fd, err := syscall.Socket( syscall.AF_NETLINK, - syscall.SOCK_RAW|syscall.SOCK_CLOEXEC, + syscall.SOCK_RAW|syscall.SOCK_NONBLOCK|syscall.SOCK_CLOEXEC, family, ); err != nil { return nil, os.NewSyscallError("socket", err) @@ -53,8 +55,14 @@ func dial(family int) (*conn, error) { _ = syscall.Close(fd) return nil, os.NewSyscallError("bind", err) } else { - c.fd, c.family = fd, family + c.family = family + c.f = os.NewFile(uintptr(fd), "netlink") + if c.raw, err = c.f.SyscallConn(); err != nil { + _ = c.f.Close() + return nil, err + } } + c.pos = syscall.NLMSG_HDRLEN c.buf = make([]byte, os.Getpagesize()) return &c, nil @@ -66,7 +74,42 @@ func (c *conn) Close() error { return syscall.EINVAL } c.buf = nil - return syscall.Close(c.fd) + return c.f.Close() +} + +// recvfrom wraps recv(2) with nonblocking behaviour via the runtime network poller. +func (c *conn) recvfrom( + p []byte, + flags int, +) (n int, from syscall.Sockaddr, err error) { + rcErr := c.raw.Read(func(fd uintptr) (done bool) { + n, from, err = syscall.Recvfrom(int(fd), p, flags) + return err != syscall.EWOULDBLOCK + }) + if err != nil { + err = os.NewSyscallError("recvfrom", err) + } else { + err = rcErr + } + return +} + +// sendto wraps send(2) with nonblocking behaviour via the runtime network poller. +func (c *conn) sendto( + p []byte, + flags int, + to syscall.Sockaddr, +) (err error) { + rcErr := c.raw.Write(func(fd uintptr) (done bool) { + err = syscall.Sendto(int(fd), p, flags, to) + return err != syscall.EWOULDBLOCK + }) + if err != nil { + err = os.NewSyscallError("sendto", err) + } else { + err = rcErr + } + return } // Msg is type constraint for types sent over the wire via netlink. @@ -152,8 +195,8 @@ type HandlerFunc func(resp []syscall.NetlinkMessage) error func (c *conn) receive(f HandlerFunc, flags int) error { for { buf := c.buf - if n, _, err := syscall.Recvfrom(c.fd, buf, flags); err != nil { - return os.NewSyscallError("recvfrom", err) + if n, _, err := c.recvfrom(buf, flags); err != nil { + return err } else if n < syscall.NLMSG_HDRLEN { return syscall.EBADE } else { @@ -187,10 +230,10 @@ func (c *conn) Roundtrip(f HandlerFunc) error { } defer func() { c.seq++ }() - if err := syscall.Sendto(c.fd, c.pending(), 0, &syscall.SockaddrNetlink{ + if err := c.sendto(c.pending(), 0, &syscall.SockaddrNetlink{ Family: syscall.AF_NETLINK, }); err != nil { - return os.NewSyscallError("sendto", err) + return err } return c.receive(f, 0)