Merge remote-tracking branch 'origin/main' into benvin/v4-only-family
# Conflicts: # internal/nftables/engine.go
This commit is contained in:
@@ -5,6 +5,8 @@ import (
|
||||
|
||||
"github.com/google/nftables"
|
||||
"github.com/google/nftables/expr"
|
||||
"github.com/mdlayher/netlink"
|
||||
"golang.org/x/sys/unix"
|
||||
|
||||
"git.unkin.net/unkin/tomswall/internal/config"
|
||||
)
|
||||
@@ -15,7 +17,7 @@ type Engine struct {
|
||||
}
|
||||
|
||||
func NewEngine(cfg *config.Config) (*Engine, error) {
|
||||
conn, err := nftables.New()
|
||||
conn, err := nftables.New(nftables.WithSockOptions(largeBuffers))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("connecting to nftables: %w", err)
|
||||
}
|
||||
@@ -77,6 +79,36 @@ func (e *Engine) staleTables() ([]*nftables.Table, error) {
|
||||
return stale, nil
|
||||
}
|
||||
|
||||
// batchBufSize bounds one batch: the kernel rejects a batch larger than the
|
||||
// send buffer (EMSGSIZE) and drops ACKs beyond the receive buffer (ENOBUFS)
|
||||
// after committing it.
|
||||
// ponytail: fixed cap of tens of thousands of rules; size per batch if exceeded.
|
||||
const batchBufSize = 64 << 20
|
||||
|
||||
// largeBuffers raises both socket buffers, ignoring rmem_max/wmem_max when
|
||||
// CAP_NET_ADMIN allows it and falling back to the capped sizes otherwise.
|
||||
func largeBuffers(c *netlink.Conn) error {
|
||||
rc, err := c.SyscallConn()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var serr error
|
||||
err = rc.Control(func(fd uintptr) {
|
||||
for _, o := range [][2]int{{unix.SO_SNDBUFFORCE, unix.SO_SNDBUF}, {unix.SO_RCVBUFFORCE, unix.SO_RCVBUF}} {
|
||||
if unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, o[0], batchBufSize) == nil {
|
||||
continue
|
||||
}
|
||||
if serr = unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, o[1], batchBufSize); serr != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return serr
|
||||
}
|
||||
|
||||
func (e *Engine) ensureTable() *nftables.Table {
|
||||
return e.conn.AddTable(&nftables.Table{
|
||||
Family: e.family(),
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
package nftables
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/mdlayher/netlink"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func TestLargeBuffersRaisesSocketBuffers(t *testing.T) {
|
||||
c, err := netlink.Dial(unix.NETLINK_NETFILTER, nil)
|
||||
if err != nil {
|
||||
t.Skipf("netlink unavailable: %v", err)
|
||||
}
|
||||
defer c.Close()
|
||||
if err := largeBuffers(c); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Without CAP_NET_ADMIN the kernel caps at the sysctl max; it doubles either way.
|
||||
for opt, sysctl := range map[int]string{unix.SO_RCVBUF: "rmem_max", unix.SO_SNDBUF: "wmem_max"} {
|
||||
want := 2 * min(batchBufSize, procInt(t, "/proc/sys/net/core/"+sysctl))
|
||||
if got := sockBuf(t, c, opt); got < want {
|
||||
t.Errorf("%s-bounded buffer = %d, want >= %d", sysctl, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func procInt(t *testing.T, path string) int {
|
||||
t.Helper()
|
||||
b, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
t.Skipf("reading %s: %v", path, err)
|
||||
}
|
||||
v, err := strconv.Atoi(strings.TrimSpace(string(b)))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
func sockBuf(t *testing.T, c *netlink.Conn, opt int) int {
|
||||
t.Helper()
|
||||
rc, err := c.SyscallConn()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var v int
|
||||
var serr error
|
||||
if err := rc.Control(func(fd uintptr) { v, serr = unix.GetsockoptInt(int(fd), unix.SOL_SOCKET, opt) }); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if serr != nil {
|
||||
t.Fatal(serr)
|
||||
}
|
||||
return v
|
||||
}
|
||||
Reference in New Issue
Block a user