Skip to content

Commit f5d2d94

Browse files
committed
Use poll instead of pselect6
pselect6 is limited to FD_SETSIZE, which is 1024 in most cases. When a application holds many fds, this can be reached easily, resulting in a panic when the fd is added to the fd set. Instead of pselect6 use poll, which doesn't have such a limitation.
1 parent 1db35da commit f5d2d94

2 files changed

Lines changed: 80 additions & 8 deletions

File tree

socket.go

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import (
88
)
99

1010
// isReadReady reports whether the netlink connection is ready for reading.
11-
// It uses pselect6 with a zero timeout on the underlying raw connection.
11+
// It uses poll(2) with a zero timeout on the underlying raw connection.
1212
// This allows for an efficient check of socket readiness without blocking.
1313
// If the Conn was created with a TestDial function, it assumes readiness.
1414
func (cc *Conn) isReadReady(conn *netlink.Conn) (bool, error) {
@@ -24,13 +24,12 @@ func (cc *Conn) isReadReady(conn *netlink.Conn) (bool, error) {
2424
var n int
2525
var opErr error
2626
err = rawConn.Control(func(fd uintptr) {
27-
var readfds unix.FdSet
28-
readfds.Zero()
29-
readfds.Set(int(fd))
30-
31-
ts := &unix.Timespec{} // zero timeout: immediate return
27+
fds := []unix.PollFd{{
28+
Fd: int32(fd),
29+
Events: unix.POLLIN,
30+
}}
3231
for {
33-
n, opErr = unix.Pselect(int(fd)+1, &readfds, nil, nil, ts, nil)
32+
n, opErr = unix.Poll(fds, 0) // 0 timeout: immediate return
3433
if opErr != unix.EINTR {
3534
break
3635
}
@@ -41,7 +40,7 @@ func (cc *Conn) isReadReady(conn *netlink.Conn) (bool, error) {
4140
}
4241

4342
if opErr != nil {
44-
return false, fmt.Errorf("pselect6: %w", opErr)
43+
return false, fmt.Errorf("poll: %w", opErr)
4544
}
4645

4746
return n > 0, nil

socket_test.go

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
package nftables_test
2+
3+
import (
4+
"os"
5+
"runtime"
6+
"testing"
7+
8+
"github.com/google/nftables"
9+
"github.com/vishvananda/netns"
10+
"golang.org/x/sys/unix"
11+
)
12+
13+
// TestIsReadReadyHighFD verifies that the nftables library works correctly when
14+
// the underlying netlink socket gets an fd >= FD_SETSIZE (1024). The old
15+
// pselect-based implementation would panic in this scenario because
16+
// unix.FdSet.Set panics for fd >= FD_SETSIZE. The current poll-based
17+
// implementation has no such limit.
18+
func TestIsReadReadyHighFD(t *testing.T) {
19+
if !*enableSysTests {
20+
t.SkipNow()
21+
}
22+
23+
runtime.LockOSThread()
24+
defer runtime.UnlockOSThread()
25+
26+
ns, err := netns.New()
27+
if err != nil {
28+
t.Fatalf("netns.New() failed: %v", err)
29+
}
30+
defer ns.Close()
31+
32+
// Exhaust low file descriptors so the next socket allocation gets fd >= FD_SETSIZE.
33+
var fillers []*os.File
34+
defer func() {
35+
for _, f := range fillers {
36+
f.Close()
37+
}
38+
}()
39+
40+
for {
41+
f, err := os.Open("/dev/null")
42+
if err != nil {
43+
t.Fatalf("os.Open(/dev/null): %v", err)
44+
}
45+
fillers = append(fillers, f)
46+
if int(f.Fd()) >= unix.FD_SETSIZE {
47+
break
48+
}
49+
}
50+
t.Logf("exhausted fds up to %d", fillers[len(fillers)-1].Fd())
51+
52+
// Now use the actual library. The netlink socket it opens will get an fd
53+
// >= FD_SETSIZE. With the old pselect code this would panic; with poll it
54+
// must work.
55+
c, err := nftables.New(nftables.WithNetNSFd(int(ns)))
56+
if err != nil {
57+
t.Fatalf("New() failed: %v", err)
58+
}
59+
60+
// Add a command and flush it to trigger the isReadReady code path.
61+
c.AddTable(&nftables.Table{Name: "test_high_fd", Family: nftables.TableFamilyIPv4})
62+
func() {
63+
// turn the potential panic into a test failure.
64+
defer func() {
65+
if r := recover(); r != nil {
66+
t.Fatalf("isReadReady panicked for fd >= %d: %v", unix.FD_SETSIZE, r)
67+
}
68+
}()
69+
if err := c.Flush(); err != nil {
70+
t.Fatalf("Flush() failed: %v", err)
71+
}
72+
}()
73+
}

0 commit comments

Comments
 (0)