aboutsummaryrefslogtreecommitdiffhomepage
path: root/internal/netlink
diff options
context:
space:
mode:
authorOphestra <cat@gensokyo.uk>2026-03-25 14:06:59 +0900
committerOphestra <cat@gensokyo.uk>2026-03-25 14:06:59 +0900
commitb98c5f2e219c142ab89c09445b6c014120f0760f (patch)
treec002a0213e567c60a9d97a874056ce3e6283be2a /internal/netlink
parentd972cffe5a221cc585e2edfdd1d3514eab51271c (diff)
internal/netlink: nonblocking socket I/O
This enables use with blocking calls like when used with NETLINK_KOBJECT_UEVENT. Signed-off-by: Ophestra <cat@gensokyo.uk>
Diffstat (limited to 'internal/netlink')
-rw-r--r--internal/netlink/netlink.go59
1 files 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)