util/linuxfw: add nftables support
This commit adds nftable rule injection for tailscaled. If tailscaled is started with envknob TS_DEBUG_USE_NETLINK_NFTABLES = true, the router will use nftables to manage firewall rules. Updates: #391 Signed-off-by: KevinLiang10 <kevinliang@tailscale.com>kevinliang10/Iptables_and_nftables_code_refactoring
parent
92fb80d55f
commit
80b912ace0
|
@ -8,6 +8,7 @@ package linuxfw
|
|||
import (
|
||||
"fmt"
|
||||
"net/netip"
|
||||
"os/exec"
|
||||
"strings"
|
||||
|
||||
"github.com/coreos/go-iptables/iptables"
|
||||
|
@ -36,6 +37,14 @@ type iptablesRunner struct {
|
|||
v6NATAvailable bool
|
||||
}
|
||||
|
||||
func checkIP6TableExists() error {
|
||||
// Some distros ship ip6tables separately from iptables.
|
||||
if _, err := exec.LookPath("ip6tables"); err != nil {
|
||||
return fmt.Errorf("path not found: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewIPTablesRunner constructs a NetfilterRunner that programs iptables rules.
|
||||
// If the underlying iptables library fails to initialize, that error is
|
||||
// returned. The runner probes for IPv6 support once at initialization time and
|
||||
|
@ -48,8 +57,11 @@ func NewIPTablesRunner(logf logger.Logf) (*iptablesRunner, error) {
|
|||
|
||||
supportsV6, supportsV6NAT := false, false
|
||||
v6err := checkIPv6(logf)
|
||||
ip6terr := checkIP6TableExists()
|
||||
if v6err != nil {
|
||||
logf("disabling tunneled IPv6 due to system IPv6 config: %v", v6err)
|
||||
} else if ip6terr != nil {
|
||||
logf("disabling tunneled IPv6 due to missing ip6tables: %v", ip6terr)
|
||||
} else {
|
||||
supportsV6 = true
|
||||
supportsV6NAT = supportsV6 && checkSupportsV6NAT()
|
||||
|
|
|
@ -20,6 +20,13 @@ import (
|
|||
"tailscale.com/types/logger"
|
||||
)
|
||||
|
||||
type MatchDecision int
|
||||
|
||||
const (
|
||||
Accept MatchDecision = iota
|
||||
Masq
|
||||
)
|
||||
|
||||
// The following bits are added to packet marks for Tailscale use.
|
||||
//
|
||||
// We tried to pick bits sufficiently out of the way that it's
|
||||
|
@ -122,11 +129,6 @@ func checkIPv6(logf logger.Logf) error {
|
|||
return fmt.Errorf("kernel doesn't support IPv6 policy routing: %w", err)
|
||||
}
|
||||
|
||||
// Some distros ship ip6tables separately from iptables.
|
||||
if _, err := exec.LookPath("ip6tables"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,733 @@
|
|||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
//go:build linux
|
||||
|
||||
package linuxfw
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net/netip"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/google/nftables"
|
||||
"github.com/google/nftables/expr"
|
||||
"github.com/mdlayher/netlink"
|
||||
"github.com/vishvananda/netns"
|
||||
)
|
||||
|
||||
// nfdump returns a hexdump of 4 bytes per line (like nft --debug=all), allowing
|
||||
// users to make sense of large byte literals more easily.
|
||||
func nfdump(b []byte) string {
|
||||
var buf bytes.Buffer
|
||||
i := 0
|
||||
for ; i < len(b); i += 4 {
|
||||
// TODO: show printable characters as ASCII
|
||||
fmt.Fprintf(&buf, "%02x %02x %02x %02x\n",
|
||||
b[i],
|
||||
b[i+1],
|
||||
b[i+2],
|
||||
b[i+3])
|
||||
}
|
||||
for ; i < len(b); i++ {
|
||||
fmt.Fprintf(&buf, "%02x ", b[i])
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// linediff returns a side-by-side diff of two nfdump() return values, flagging
|
||||
// lines which are not equal with an exclamation point prefix.
|
||||
func linediff(a, b string) string {
|
||||
var buf bytes.Buffer
|
||||
fmt.Fprintf(&buf, "got -- want\n")
|
||||
linesA := strings.Split(a, "\n")
|
||||
linesB := strings.Split(b, "\n")
|
||||
for idx, lineA := range linesA {
|
||||
if idx >= len(linesB) {
|
||||
break
|
||||
}
|
||||
lineB := linesB[idx]
|
||||
prefix := "! "
|
||||
if lineA == lineB {
|
||||
prefix = " "
|
||||
}
|
||||
fmt.Fprintf(&buf, "%s%s -- %s\n", prefix, lineA, lineB)
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func newTestConn(t *testing.T, want [][]byte) *nftables.Conn {
|
||||
conn, err := nftables.New(nftables.WithTestDial(
|
||||
func(req []netlink.Message) ([]netlink.Message, error) {
|
||||
for idx, msg := range req {
|
||||
b, err := msg.MarshalBinary()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(b) < 16 {
|
||||
continue
|
||||
}
|
||||
b = b[16:]
|
||||
if len(want) == 0 {
|
||||
t.Errorf("no want entry for message %d: %x", idx, b)
|
||||
continue
|
||||
}
|
||||
if got, want := b, want[0]; !bytes.Equal(got, want) {
|
||||
t.Errorf("message %d: %s", idx, linediff(nfdump(got), nfdump(want)))
|
||||
}
|
||||
want = want[1:]
|
||||
}
|
||||
return req, nil
|
||||
}))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return conn
|
||||
}
|
||||
|
||||
func TestInsertLoopbackRule(t *testing.T) {
|
||||
proto := nftables.TableFamilyIPv4
|
||||
want := [][]byte{
|
||||
[]byte("\x00\x00\x00\x0a"),
|
||||
[]byte("\x02\x00\x00\x00\x13\x00\x01\x00\x74\x73\x2d\x66\x69\x6c\x74\x65\x72\x2d\x74\x65\x73\x74\x00\x00\x08\x00\x02\x00\x00\x00\x00\x00"),
|
||||
[]byte("\x02\x00\x00\x00\x13\x00\x01\x00\x74\x73\x2d\x66\x69\x6c\x74\x65\x72\x2d\x74\x65\x73\x74\x00\x00\x12\x00\x03\x00\x74\x73\x2d\x69\x6e\x70\x75\x74\x2d\x74\x65\x73\x74\x00\x00\x00\x14\x00\x04\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x00\x0b\x00\x07\x00\x66\x69\x6c\x74\x65\x72\x00\x00"),
|
||||
[]byte("\x02\x00\x00\x00\x13\x00\x01\x00\x74\x73\x2d\x66\x69\x6c\x74\x65\x72\x2d\x74\x65\x73\x74\x00\x00\x12\x00\x02\x00\x74\x73\x2d\x69\x6e\x70\x75\x74\x2d\x74\x65\x73\x74\x00\x00\x00\x10\x01\x04\x80\x24\x00\x01\x80\x09\x00\x01\x00\x6d\x65\x74\x61\x00\x00\x00\x00\x14\x00\x02\x80\x08\x00\x02\x00\x00\x00\x00\x06\x08\x00\x01\x00\x00\x00\x00\x01\x2c\x00\x01\x80\x08\x00\x01\x00\x63\x6d\x70\x00\x20\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x00\x0c\x00\x03\x80\x06\x00\x01\x00\x6c\x6f\x00\x00\x34\x00\x01\x80\x0c\x00\x01\x00\x70\x61\x79\x6c\x6f\x61\x64\x00\x24\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x01\x08\x00\x03\x00\x00\x00\x00\x0c\x08\x00\x04\x00\x00\x00\x00\x04\x2c\x00\x01\x80\x08\x00\x01\x00\x63\x6d\x70\x00\x20\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x00\x0c\x00\x03\x80\x08\x00\x01\x00\xc0\xa8\x00\x02\x2c\x00\x01\x80\x0c\x00\x01\x00\x63\x6f\x75\x6e\x74\x65\x72\x00\x1c\x00\x02\x80\x0c\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0c\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x30\x00\x01\x80\x0e\x00\x01\x00\x69\x6d\x6d\x65\x64\x69\x61\x74\x65\x00\x00\x00\x1c\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x00\x10\x00\x02\x80\x0c\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01"),
|
||||
[]byte("\x00\x00\x00\x0a"),
|
||||
}
|
||||
testConn := newTestConn(t, want)
|
||||
table := testConn.AddTable(&nftables.Table{
|
||||
Family: proto,
|
||||
Name: "ts-filter-test",
|
||||
})
|
||||
|
||||
chain := testConn.AddChain(&nftables.Chain{
|
||||
Name: "ts-input-test",
|
||||
Table: table,
|
||||
Type: nftables.ChainTypeFilter,
|
||||
Hooknum: nftables.ChainHookInput,
|
||||
Priority: nftables.ChainPriorityFilter,
|
||||
})
|
||||
|
||||
addr := netip.MustParseAddr("192.168.0.2")
|
||||
|
||||
err := insertLoopbackRule(testConn, proto, table, chain, addr)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInsertLoopbackRuleV6(t *testing.T) {
|
||||
protoV6 := nftables.TableFamilyIPv6
|
||||
want := [][]byte{
|
||||
[]byte("\x00\x00\x00\x0a"),
|
||||
[]byte("\x0a\x00\x00\x00\x13\x00\x01\x00\x74\x73\x2d\x66\x69\x6c\x74\x65\x72\x2d\x74\x65\x73\x74\x00\x00\x08\x00\x02\x00\x00\x00\x00\x00"),
|
||||
[]byte("\x0a\x00\x00\x00\x13\x00\x01\x00\x74\x73\x2d\x66\x69\x6c\x74\x65\x72\x2d\x74\x65\x73\x74\x00\x00\x12\x00\x03\x00\x74\x73\x2d\x69\x6e\x70\x75\x74\x2d\x74\x65\x73\x74\x00\x00\x00\x14\x00\x04\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x00\x0b\x00\x07\x00\x66\x69\x6c\x74\x65\x72\x00\x00"),
|
||||
[]byte("\x0a\x00\x00\x00\x13\x00\x01\x00\x74\x73\x2d\x66\x69\x6c\x74\x65\x72\x2d\x74\x65\x73\x74\x00\x00\x12\x00\x02\x00\x74\x73\x2d\x69\x6e\x70\x75\x74\x2d\x74\x65\x73\x74\x00\x00\x00\x1c\x01\x04\x80\x24\x00\x01\x80\x09\x00\x01\x00\x6d\x65\x74\x61\x00\x00\x00\x00\x14\x00\x02\x80\x08\x00\x02\x00\x00\x00\x00\x06\x08\x00\x01\x00\x00\x00\x00\x01\x2c\x00\x01\x80\x08\x00\x01\x00\x63\x6d\x70\x00\x20\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x00\x0c\x00\x03\x80\x06\x00\x01\x00\x6c\x6f\x00\x00\x34\x00\x01\x80\x0c\x00\x01\x00\x70\x61\x79\x6c\x6f\x61\x64\x00\x24\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x01\x08\x00\x03\x00\x00\x00\x00\x08\x08\x00\x04\x00\x00\x00\x00\x10\x38\x00\x01\x80\x08\x00\x01\x00\x63\x6d\x70\x00\x2c\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x00\x18\x00\x03\x80\x14\x00\x01\x00\x20\x01\x0d\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x2c\x00\x01\x80\x0c\x00\x01\x00\x63\x6f\x75\x6e\x74\x65\x72\x00\x1c\x00\x02\x80\x0c\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0c\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x30\x00\x01\x80\x0e\x00\x01\x00\x69\x6d\x6d\x65\x64\x69\x61\x74\x65\x00\x00\x00\x1c\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x00\x10\x00\x02\x80\x0c\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01"),
|
||||
[]byte("\x00\x00\x00\x0a"),
|
||||
}
|
||||
testConn := newTestConn(t, want)
|
||||
tableV6 := testConn.AddTable(&nftables.Table{
|
||||
Family: protoV6,
|
||||
Name: "ts-filter-test",
|
||||
})
|
||||
|
||||
chainV6 := testConn.AddChain(&nftables.Chain{
|
||||
Name: "ts-input-test",
|
||||
Table: tableV6,
|
||||
Type: nftables.ChainTypeFilter,
|
||||
Hooknum: nftables.ChainHookInput,
|
||||
Priority: nftables.ChainPriorityFilter,
|
||||
})
|
||||
|
||||
addrV6 := netip.MustParseAddr("2001:db8::1")
|
||||
|
||||
err := insertLoopbackRule(testConn, protoV6, tableV6, chainV6, addrV6)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddReturnChromeOSVMRangeRule(t *testing.T) {
|
||||
proto := nftables.TableFamilyIPv4
|
||||
want := [][]byte{
|
||||
[]byte("\x00\x00\x00\x0a"),
|
||||
[]byte("\x02\x00\x00\x00\x13\x00\x01\x00\x74\x73\x2d\x66\x69\x6c\x74\x65\x72\x2d\x74\x65\x73\x74\x00\x00\x08\x00\x02\x00\x00\x00\x00\x00"),
|
||||
[]byte("\x02\x00\x00\x00\x13\x00\x01\x00\x74\x73\x2d\x66\x69\x6c\x74\x65\x72\x2d\x74\x65\x73\x74\x00\x00\x12\x00\x03\x00\x74\x73\x2d\x69\x6e\x70\x75\x74\x2d\x74\x65\x73\x74\x00\x00\x00\x14\x00\x04\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x00\x0b\x00\x07\x00\x66\x69\x6c\x74\x65\x72\x00\x00"),
|
||||
[]byte("\x02\x00\x00\x00\x13\x00\x01\x00\x74\x73\x2d\x66\x69\x6c\x74\x65\x72\x2d\x74\x65\x73\x74\x00\x00\x12\x00\x02\x00\x74\x73\x2d\x69\x6e\x70\x75\x74\x2d\x74\x65\x73\x74\x00\x00\x00\x58\x01\x04\x80\x24\x00\x01\x80\x09\x00\x01\x00\x6d\x65\x74\x61\x00\x00\x00\x00\x14\x00\x02\x80\x08\x00\x02\x00\x00\x00\x00\x06\x08\x00\x01\x00\x00\x00\x00\x01\x30\x00\x01\x80\x08\x00\x01\x00\x63\x6d\x70\x00\x24\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x01\x10\x00\x03\x80\x0c\x00\x01\x00\x74\x65\x73\x74\x54\x75\x6e\x6e\x34\x00\x01\x80\x0c\x00\x01\x00\x70\x61\x79\x6c\x6f\x61\x64\x00\x24\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x01\x08\x00\x03\x00\x00\x00\x00\x0c\x08\x00\x04\x00\x00\x00\x00\x04\x44\x00\x01\x80\x0c\x00\x01\x00\x62\x69\x74\x77\x69\x73\x65\x00\x34\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x01\x08\x00\x03\x00\x00\x00\x00\x04\x0c\x00\x04\x80\x08\x00\x01\x00\xff\xff\xfe\x00\x0c\x00\x05\x80\x08\x00\x01\x00\x00\x00\x00\x00\x2c\x00\x01\x80\x08\x00\x01\x00\x63\x6d\x70\x00\x20\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x00\x0c\x00\x03\x80\x08\x00\x01\x00\x64\x73\x5c\x00\x2c\x00\x01\x80\x0c\x00\x01\x00\x63\x6f\x75\x6e\x74\x65\x72\x00\x1c\x00\x02\x80\x0c\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0c\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x30\x00\x01\x80\x0e\x00\x01\x00\x69\x6d\x6d\x65\x64\x69\x61\x74\x65\x00\x00\x00\x1c\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x00\x10\x00\x02\x80\x0c\x00\x02\x80\x08\x00\x01\x00\xff\xff\xff\xfb"),
|
||||
[]byte("\x00\x00\x00\x0a"),
|
||||
}
|
||||
testConn := newTestConn(t, want)
|
||||
table := testConn.AddTable(&nftables.Table{
|
||||
Family: proto,
|
||||
Name: "ts-filter-test",
|
||||
})
|
||||
chain := testConn.AddChain(&nftables.Chain{
|
||||
Name: "ts-input-test",
|
||||
Table: table,
|
||||
Type: nftables.ChainTypeFilter,
|
||||
Hooknum: nftables.ChainHookInput,
|
||||
Priority: nftables.ChainPriorityFilter,
|
||||
})
|
||||
err := addReturnChromeOSVMRangeRule(testConn, table, chain, "testTunn")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddDropCGNATRangeRule(t *testing.T) {
|
||||
proto := nftables.TableFamilyIPv4
|
||||
want := [][]byte{
|
||||
[]byte("\x00\x00\x00\x0a"),
|
||||
[]byte("\x02\x00\x00\x00\x13\x00\x01\x00\x74\x73\x2d\x66\x69\x6c\x74\x65\x72\x2d\x74\x65\x73\x74\x00\x00\x08\x00\x02\x00\x00\x00\x00\x00"),
|
||||
[]byte("\x02\x00\x00\x00\x13\x00\x01\x00\x74\x73\x2d\x66\x69\x6c\x74\x65\x72\x2d\x74\x65\x73\x74\x00\x00\x12\x00\x03\x00\x74\x73\x2d\x69\x6e\x70\x75\x74\x2d\x74\x65\x73\x74\x00\x00\x00\x14\x00\x04\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x00\x0b\x00\x07\x00\x66\x69\x6c\x74\x65\x72\x00\x00"),
|
||||
[]byte("\x02\x00\x00\x00\x13\x00\x01\x00\x74\x73\x2d\x66\x69\x6c\x74\x65\x72\x2d\x74\x65\x73\x74\x00\x00\x12\x00\x02\x00\x74\x73\x2d\x69\x6e\x70\x75\x74\x2d\x74\x65\x73\x74\x00\x00\x00\x58\x01\x04\x80\x24\x00\x01\x80\x09\x00\x01\x00\x6d\x65\x74\x61\x00\x00\x00\x00\x14\x00\x02\x80\x08\x00\x02\x00\x00\x00\x00\x06\x08\x00\x01\x00\x00\x00\x00\x01\x30\x00\x01\x80\x08\x00\x01\x00\x63\x6d\x70\x00\x24\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x01\x10\x00\x03\x80\x0c\x00\x01\x00\x74\x65\x73\x74\x54\x75\x6e\x6e\x34\x00\x01\x80\x0c\x00\x01\x00\x70\x61\x79\x6c\x6f\x61\x64\x00\x24\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x01\x08\x00\x03\x00\x00\x00\x00\x0c\x08\x00\x04\x00\x00\x00\x00\x04\x44\x00\x01\x80\x0c\x00\x01\x00\x62\x69\x74\x77\x69\x73\x65\x00\x34\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x01\x08\x00\x03\x00\x00\x00\x00\x04\x0c\x00\x04\x80\x08\x00\x01\x00\xff\xc0\x00\x00\x0c\x00\x05\x80\x08\x00\x01\x00\x00\x00\x00\x00\x2c\x00\x01\x80\x08\x00\x01\x00\x63\x6d\x70\x00\x20\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x00\x0c\x00\x03\x80\x08\x00\x01\x00\x64\x40\x00\x00\x2c\x00\x01\x80\x0c\x00\x01\x00\x63\x6f\x75\x6e\x74\x65\x72\x00\x1c\x00\x02\x80\x0c\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0c\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x30\x00\x01\x80\x0e\x00\x01\x00\x69\x6d\x6d\x65\x64\x69\x61\x74\x65\x00\x00\x00\x1c\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x00\x10\x00\x02\x80\x0c\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x00"),
|
||||
[]byte("\x00\x00\x00\x0a"),
|
||||
}
|
||||
testConn := newTestConn(t, want)
|
||||
table := testConn.AddTable(&nftables.Table{
|
||||
Family: proto,
|
||||
Name: "ts-filter-test",
|
||||
})
|
||||
chain := testConn.AddChain(&nftables.Chain{
|
||||
Name: "ts-input-test",
|
||||
Table: table,
|
||||
Type: nftables.ChainTypeFilter,
|
||||
Hooknum: nftables.ChainHookInput,
|
||||
Priority: nftables.ChainPriorityFilter,
|
||||
})
|
||||
err := addDropCGNATRangeRule(testConn, table, chain, "testTunn")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddSetSubnetRouteMarkRule(t *testing.T) {
|
||||
proto := nftables.TableFamilyIPv4
|
||||
want := [][]byte{
|
||||
[]byte("\x00\x00\x00\x0a"),
|
||||
[]byte("\x02\x00\x00\x00\x13\x00\x01\x00\x74\x73\x2d\x66\x69\x6c\x74\x65\x72\x2d\x74\x65\x73\x74\x00\x00\x08\x00\x02\x00\x00\x00\x00\x00"),
|
||||
[]byte("\x02\x00\x00\x00\x13\x00\x01\x00\x74\x73\x2d\x66\x69\x6c\x74\x65\x72\x2d\x74\x65\x73\x74\x00\x00\x14\x00\x03\x00\x74\x73\x2d\x66\x6f\x72\x77\x61\x72\x64\x2d\x74\x65\x73\x74\x00\x14\x00\x04\x80\x08\x00\x01\x00\x00\x00\x00\x02\x08\x00\x02\x00\x00\x00\x00\x00\x0b\x00\x07\x00\x66\x69\x6c\x74\x65\x72\x00\x00"),
|
||||
[]byte("\x02\x00\x00\x00\x13\x00\x01\x00\x74\x73\x2d\x66\x69\x6c\x74\x65\x72\x2d\x74\x65\x73\x74\x00\x00\x14\x00\x02\x00\x74\x73\x2d\x66\x6f\x72\x77\x61\x72\x64\x2d\x74\x65\x73\x74\x00\x3c\x01\x04\x80\x24\x00\x01\x80\x09\x00\x01\x00\x6d\x65\x74\x61\x00\x00\x00\x00\x14\x00\x02\x80\x08\x00\x02\x00\x00\x00\x00\x06\x08\x00\x01\x00\x00\x00\x00\x01\x30\x00\x01\x80\x08\x00\x01\x00\x63\x6d\x70\x00\x24\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x00\x10\x00\x03\x80\x0c\x00\x01\x00\x74\x65\x73\x74\x54\x75\x6e\x6e\x2c\x00\x01\x80\x0c\x00\x01\x00\x63\x6f\x75\x6e\x74\x65\x72\x00\x1c\x00\x02\x80\x0c\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0c\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x24\x00\x01\x80\x09\x00\x01\x00\x6d\x65\x74\x61\x00\x00\x00\x00\x14\x00\x02\x80\x08\x00\x02\x00\x00\x00\x00\x03\x08\x00\x01\x00\x00\x00\x00\x01\x44\x00\x01\x80\x0c\x00\x01\x00\x62\x69\x74\x77\x69\x73\x65\x00\x34\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x01\x08\x00\x03\x00\x00\x00\x00\x04\x0c\x00\x04\x80\x08\x00\x01\x00\xff\x00\xff\xff\x0c\x00\x05\x80\x08\x00\x01\x00\x00\x04\x00\x00\x24\x00\x01\x80\x09\x00\x01\x00\x6d\x65\x74\x61\x00\x00\x00\x00\x14\x00\x02\x80\x08\x00\x02\x00\x00\x00\x00\x03\x08\x00\x01\x00\x00\x00\x00\x01\x2c\x00\x01\x80\x0c\x00\x01\x00\x63\x6f\x75\x6e\x74\x65\x72\x00\x1c\x00\x02\x80\x0c\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0c\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00"),
|
||||
[]byte("\x00\x00\x00\x0a"),
|
||||
}
|
||||
testConn := newTestConn(t, want)
|
||||
table := testConn.AddTable(&nftables.Table{
|
||||
Family: proto,
|
||||
Name: "ts-filter-test",
|
||||
})
|
||||
chain := testConn.AddChain(&nftables.Chain{
|
||||
Name: "ts-forward-test",
|
||||
Table: table,
|
||||
Type: nftables.ChainTypeFilter,
|
||||
Hooknum: nftables.ChainHookForward,
|
||||
Priority: nftables.ChainPriorityFilter,
|
||||
})
|
||||
err := addSetSubnetRouteMarkRule(testConn, table, chain, "testTunn")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddDropOutgoingPacketFromCGNATRangeRuleWithTunname(t *testing.T) {
|
||||
proto := nftables.TableFamilyIPv4
|
||||
want := [][]byte{
|
||||
[]byte("\x00\x00\x00\x0a"),
|
||||
[]byte("\x02\x00\x00\x00\x13\x00\x01\x00\x74\x73\x2d\x66\x69\x6c\x74\x65\x72\x2d\x74\x65\x73\x74\x00\x00\x08\x00\x02\x00\x00\x00\x00\x00"),
|
||||
[]byte("\x02\x00\x00\x00\x13\x00\x01\x00\x74\x73\x2d\x66\x69\x6c\x74\x65\x72\x2d\x74\x65\x73\x74\x00\x00\x14\x00\x03\x00\x74\x73\x2d\x66\x6f\x72\x77\x61\x72\x64\x2d\x74\x65\x73\x74\x00\x14\x00\x04\x80\x08\x00\x01\x00\x00\x00\x00\x02\x08\x00\x02\x00\x00\x00\x00\x00\x0b\x00\x07\x00\x66\x69\x6c\x74\x65\x72\x00\x00"),
|
||||
[]byte("\x02\x00\x00\x00\x13\x00\x01\x00\x74\x73\x2d\x66\x69\x6c\x74\x65\x72\x2d\x74\x65\x73\x74\x00\x00\x14\x00\x02\x00\x74\x73\x2d\x66\x6f\x72\x77\x61\x72\x64\x2d\x74\x65\x73\x74\x00\x58\x01\x04\x80\x24\x00\x01\x80\x09\x00\x01\x00\x6d\x65\x74\x61\x00\x00\x00\x00\x14\x00\x02\x80\x08\x00\x02\x00\x00\x00\x00\x07\x08\x00\x01\x00\x00\x00\x00\x01\x30\x00\x01\x80\x08\x00\x01\x00\x63\x6d\x70\x00\x24\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x00\x10\x00\x03\x80\x0c\x00\x01\x00\x74\x65\x73\x74\x54\x75\x6e\x6e\x34\x00\x01\x80\x0c\x00\x01\x00\x70\x61\x79\x6c\x6f\x61\x64\x00\x24\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x01\x08\x00\x03\x00\x00\x00\x00\x0c\x08\x00\x04\x00\x00\x00\x00\x04\x44\x00\x01\x80\x0c\x00\x01\x00\x62\x69\x74\x77\x69\x73\x65\x00\x34\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x01\x08\x00\x03\x00\x00\x00\x00\x04\x0c\x00\x04\x80\x08\x00\x01\x00\xff\xc0\x00\x00\x0c\x00\x05\x80\x08\x00\x01\x00\x00\x00\x00\x00\x2c\x00\x01\x80\x08\x00\x01\x00\x63\x6d\x70\x00\x20\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x00\x0c\x00\x03\x80\x08\x00\x01\x00\x64\x40\x00\x00\x2c\x00\x01\x80\x0c\x00\x01\x00\x63\x6f\x75\x6e\x74\x65\x72\x00\x1c\x00\x02\x80\x0c\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0c\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x30\x00\x01\x80\x0e\x00\x01\x00\x69\x6d\x6d\x65\x64\x69\x61\x74\x65\x00\x00\x00\x1c\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x00\x10\x00\x02\x80\x0c\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x00"),
|
||||
[]byte("\x00\x00\x00\x0a"),
|
||||
}
|
||||
testConn := newTestConn(t, want)
|
||||
table := testConn.AddTable(&nftables.Table{
|
||||
Family: proto,
|
||||
Name: "ts-filter-test",
|
||||
})
|
||||
chain := testConn.AddChain(&nftables.Chain{
|
||||
Name: "ts-forward-test",
|
||||
Table: table,
|
||||
Type: nftables.ChainTypeFilter,
|
||||
Hooknum: nftables.ChainHookForward,
|
||||
Priority: nftables.ChainPriorityFilter,
|
||||
})
|
||||
err := addDropOutgoingPacketFromCGNATRangeRuleWithTunname(testConn, table, chain, "testTunn")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddAcceptOutgoingPacketRule(t *testing.T) {
|
||||
proto := nftables.TableFamilyIPv4
|
||||
want := [][]byte{
|
||||
[]byte("\x00\x00\x00\x0a"),
|
||||
[]byte("\x02\x00\x00\x00\x13\x00\x01\x00\x74\x73\x2d\x66\x69\x6c\x74\x65\x72\x2d\x74\x65\x73\x74\x00\x00\x08\x00\x02\x00\x00\x00\x00\x00"),
|
||||
[]byte("\x02\x00\x00\x00\x13\x00\x01\x00\x74\x73\x2d\x66\x69\x6c\x74\x65\x72\x2d\x74\x65\x73\x74\x00\x00\x14\x00\x03\x00\x74\x73\x2d\x66\x6f\x72\x77\x61\x72\x64\x2d\x74\x65\x73\x74\x00\x14\x00\x04\x80\x08\x00\x01\x00\x00\x00\x00\x02\x08\x00\x02\x00\x00\x00\x00\x00\x0b\x00\x07\x00\x66\x69\x6c\x74\x65\x72\x00\x00"),
|
||||
[]byte("\x02\x00\x00\x00\x13\x00\x01\x00\x74\x73\x2d\x66\x69\x6c\x74\x65\x72\x2d\x74\x65\x73\x74\x00\x00\x14\x00\x02\x00\x74\x73\x2d\x66\x6f\x72\x77\x61\x72\x64\x2d\x74\x65\x73\x74\x00\xb4\x00\x04\x80\x24\x00\x01\x80\x09\x00\x01\x00\x6d\x65\x74\x61\x00\x00\x00\x00\x14\x00\x02\x80\x08\x00\x02\x00\x00\x00\x00\x07\x08\x00\x01\x00\x00\x00\x00\x01\x30\x00\x01\x80\x08\x00\x01\x00\x63\x6d\x70\x00\x24\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x00\x10\x00\x03\x80\x0c\x00\x01\x00\x74\x65\x73\x74\x54\x75\x6e\x6e\x2c\x00\x01\x80\x0c\x00\x01\x00\x63\x6f\x75\x6e\x74\x65\x72\x00\x1c\x00\x02\x80\x0c\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0c\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x30\x00\x01\x80\x0e\x00\x01\x00\x69\x6d\x6d\x65\x64\x69\x61\x74\x65\x00\x00\x00\x1c\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x00\x10\x00\x02\x80\x0c\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01"),
|
||||
[]byte("\x00\x00\x00\x0a"),
|
||||
}
|
||||
testConn := newTestConn(t, want)
|
||||
table := testConn.AddTable(&nftables.Table{
|
||||
Family: proto,
|
||||
Name: "ts-filter-test",
|
||||
})
|
||||
chain := testConn.AddChain(&nftables.Chain{
|
||||
Name: "ts-forward-test",
|
||||
Table: table,
|
||||
Type: nftables.ChainTypeFilter,
|
||||
Hooknum: nftables.ChainHookForward,
|
||||
Priority: nftables.ChainPriorityFilter,
|
||||
})
|
||||
err := addAcceptOutgoingPacketRule(testConn, table, chain, "testTunn")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddMatchSubnetRouteMarkRuleMasq(t *testing.T) {
|
||||
proto := nftables.TableFamilyIPv4
|
||||
want := [][]byte{
|
||||
[]byte("\x00\x00\x00\x0a"),
|
||||
[]byte("\x02\x00\x00\x00\x10\x00\x01\x00\x74\x73\x2d\x6e\x61\x74\x2d\x74\x65\x73\x74\x00\x08\x00\x02\x00\x00\x00\x00\x00"),
|
||||
[]byte("\x02\x00\x00\x00\x10\x00\x01\x00\x74\x73\x2d\x6e\x61\x74\x2d\x74\x65\x73\x74\x00\x18\x00\x03\x00\x74\x73\x2d\x70\x6f\x73\x74\x72\x6f\x75\x74\x69\x6e\x67\x2d\x74\x65\x73\x74\x00\x14\x00\x04\x80\x08\x00\x01\x00\x00\x00\x00\x04\x08\x00\x02\x00\x00\x00\x00\x64\x08\x00\x07\x00\x6e\x61\x74\x00"),
|
||||
[]byte("\x02\x00\x00\x00\x10\x00\x01\x00\x74\x73\x2d\x6e\x61\x74\x2d\x74\x65\x73\x74\x00\x18\x00\x02\x00\x74\x73\x2d\x70\x6f\x73\x74\x72\x6f\x75\x74\x69\x6e\x67\x2d\x74\x65\x73\x74\x00\xf4\x00\x04\x80\x24\x00\x01\x80\x09\x00\x01\x00\x6d\x65\x74\x61\x00\x00\x00\x00\x14\x00\x02\x80\x08\x00\x02\x00\x00\x00\x00\x03\x08\x00\x01\x00\x00\x00\x00\x01\x44\x00\x01\x80\x0c\x00\x01\x00\x62\x69\x74\x77\x69\x73\x65\x00\x34\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x01\x08\x00\x03\x00\x00\x00\x00\x04\x0c\x00\x04\x80\x08\x00\x01\x00\x00\xff\x00\x00\x0c\x00\x05\x80\x08\x00\x01\x00\x00\x00\x00\x00\x2c\x00\x01\x80\x08\x00\x01\x00\x63\x6d\x70\x00\x20\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x00\x0c\x00\x03\x80\x08\x00\x01\x00\x00\x04\x00\x00\x2c\x00\x01\x80\x0c\x00\x01\x00\x63\x6f\x75\x6e\x74\x65\x72\x00\x1c\x00\x02\x80\x0c\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0c\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x30\x00\x01\x80\x0e\x00\x01\x00\x69\x6d\x6d\x65\x64\x69\x61\x74\x65\x00\x00\x00\x1c\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x00\x10\x00\x02\x80\x0c\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01"),
|
||||
[]byte("\x00\x00\x00\x0a"),
|
||||
}
|
||||
testConn := newTestConn(t, want)
|
||||
table := testConn.AddTable(&nftables.Table{
|
||||
Family: proto,
|
||||
Name: "ts-nat-test",
|
||||
})
|
||||
chain := testConn.AddChain(&nftables.Chain{
|
||||
Name: "ts-postrouting-test",
|
||||
Table: table,
|
||||
Type: nftables.ChainTypeNAT,
|
||||
Hooknum: nftables.ChainHookPostrouting,
|
||||
Priority: nftables.ChainPriorityNATSource,
|
||||
})
|
||||
err := addMatchSubnetRouteMarkRule(testConn, table, chain, Accept)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddMatchSubnetRouteMarkRuleAccept(t *testing.T) {
|
||||
proto := nftables.TableFamilyIPv4
|
||||
want := [][]byte{
|
||||
[]byte("\x00\x00\x00\x0a"),
|
||||
[]byte("\x02\x00\x00\x00\x13\x00\x01\x00\x74\x73\x2d\x66\x69\x6c\x74\x65\x72\x2d\x74\x65\x73\x74\x00\x00\x08\x00\x02\x00\x00\x00\x00\x00"),
|
||||
[]byte("\x02\x00\x00\x00\x13\x00\x01\x00\x74\x73\x2d\x66\x69\x6c\x74\x65\x72\x2d\x74\x65\x73\x74\x00\x00\x14\x00\x03\x00\x74\x73\x2d\x66\x6f\x72\x77\x61\x72\x64\x2d\x74\x65\x73\x74\x00\x14\x00\x04\x80\x08\x00\x01\x00\x00\x00\x00\x02\x08\x00\x02\x00\x00\x00\x00\x00\x0b\x00\x07\x00\x66\x69\x6c\x74\x65\x72\x00\x00"),
|
||||
[]byte("\x02\x00\x00\x00\x13\x00\x01\x00\x74\x73\x2d\x66\x69\x6c\x74\x65\x72\x2d\x74\x65\x73\x74\x00\x00\x14\x00\x02\x00\x74\x73\x2d\x66\x6f\x72\x77\x61\x72\x64\x2d\x74\x65\x73\x74\x00\xf4\x00\x04\x80\x24\x00\x01\x80\x09\x00\x01\x00\x6d\x65\x74\x61\x00\x00\x00\x00\x14\x00\x02\x80\x08\x00\x02\x00\x00\x00\x00\x03\x08\x00\x01\x00\x00\x00\x00\x01\x44\x00\x01\x80\x0c\x00\x01\x00\x62\x69\x74\x77\x69\x73\x65\x00\x34\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x01\x08\x00\x03\x00\x00\x00\x00\x04\x0c\x00\x04\x80\x08\x00\x01\x00\x00\xff\x00\x00\x0c\x00\x05\x80\x08\x00\x01\x00\x00\x00\x00\x00\x2c\x00\x01\x80\x08\x00\x01\x00\x63\x6d\x70\x00\x20\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x00\x0c\x00\x03\x80\x08\x00\x01\x00\x00\x04\x00\x00\x2c\x00\x01\x80\x0c\x00\x01\x00\x63\x6f\x75\x6e\x74\x65\x72\x00\x1c\x00\x02\x80\x0c\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0c\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x30\x00\x01\x80\x0e\x00\x01\x00\x69\x6d\x6d\x65\x64\x69\x61\x74\x65\x00\x00\x00\x1c\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x00\x10\x00\x02\x80\x0c\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01"),
|
||||
[]byte("\x00\x00\x00\x0a"),
|
||||
}
|
||||
testConn := newTestConn(t, want)
|
||||
table := testConn.AddTable(&nftables.Table{
|
||||
Family: proto,
|
||||
Name: "ts-filter-test",
|
||||
})
|
||||
chain := testConn.AddChain(&nftables.Chain{
|
||||
Name: "ts-forward-test",
|
||||
Table: table,
|
||||
Type: nftables.ChainTypeFilter,
|
||||
Hooknum: nftables.ChainHookForward,
|
||||
Priority: nftables.ChainPriorityFilter,
|
||||
})
|
||||
err := addMatchSubnetRouteMarkRule(testConn, table, chain, Accept)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func newSysConn(t *testing.T) (*nftables.Conn, netns.NsHandle) {
|
||||
t.Helper()
|
||||
|
||||
runtime.LockOSThread()
|
||||
|
||||
ns, err := netns.New()
|
||||
if err != nil {
|
||||
t.Fatalf("netns.New() failed: %v", err)
|
||||
}
|
||||
c, err := nftables.New(nftables.WithNetNSFd(int(ns)))
|
||||
if err != nil {
|
||||
t.Fatalf("nftables.New() failed: %v", err)
|
||||
}
|
||||
return c, ns
|
||||
}
|
||||
|
||||
func cleanupSysConn(t *testing.T, ns netns.NsHandle) {
|
||||
defer runtime.UnlockOSThread()
|
||||
|
||||
if err := ns.Close(); err != nil {
|
||||
t.Fatalf("newNS.Close() failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func newFakeNftablesRunner(t *testing.T, conn *nftables.Conn) *nftablesRunner {
|
||||
nft4 := newNfTable(nftables.TableFamilyIPv4)
|
||||
nft6 := newNfTable(nftables.TableFamilyIPv6)
|
||||
|
||||
return &nftablesRunner{
|
||||
conn: conn,
|
||||
nft4: nft4,
|
||||
nft6: nft6,
|
||||
v6Available: true,
|
||||
v6NATAvailable: true,
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddAndDelNetfilterChains(t *testing.T) {
|
||||
if os.Geteuid() != 0 {
|
||||
t.Skip(t.Name(), " requires priviliges to create a namespace in order to run")
|
||||
return
|
||||
}
|
||||
conn, ns := newSysConn(t)
|
||||
defer cleanupSysConn(t, ns)
|
||||
|
||||
runner := newFakeNftablesRunner(t, conn)
|
||||
runner.AddChains()
|
||||
|
||||
tables, err := conn.ListTables()
|
||||
if err != nil {
|
||||
t.Fatalf("conn.ListTables() failed: %v", err)
|
||||
}
|
||||
|
||||
if len(tables) != 4 {
|
||||
t.Fatalf("len(tables) = %d, want 4", len(tables))
|
||||
}
|
||||
|
||||
chainsV4, err := conn.ListChainsOfTableFamily(nftables.TableFamilyIPv4)
|
||||
if err != nil {
|
||||
t.Fatalf("list chains failed: %v", err)
|
||||
}
|
||||
|
||||
if len(chainsV4) != 3 {
|
||||
t.Fatalf("len(chainsV4) = %d, want 3", len(chainsV4))
|
||||
}
|
||||
|
||||
chainsV6, err := conn.ListChainsOfTableFamily(nftables.TableFamilyIPv6)
|
||||
if err != nil {
|
||||
t.Fatalf("list chains failed: %v", err)
|
||||
}
|
||||
|
||||
if len(chainsV6) != 3 {
|
||||
t.Fatalf("len(chainsV6) = %d, want 3", len(chainsV6))
|
||||
}
|
||||
|
||||
runner.DelChains()
|
||||
|
||||
tables, err = conn.ListTables()
|
||||
if err != nil {
|
||||
t.Fatalf("conn.ListTables() failed: %v", err)
|
||||
}
|
||||
|
||||
if len(tables) != 0 {
|
||||
t.Fatalf("len(tables) = %d, want 0", len(tables))
|
||||
}
|
||||
}
|
||||
|
||||
func getTsChains(
|
||||
conn *nftables.Conn,
|
||||
proto nftables.TableFamily) (*nftables.Chain, *nftables.Chain, *nftables.Chain, error) {
|
||||
chains, err := conn.ListChainsOfTableFamily(nftables.TableFamilyIPv4)
|
||||
if err != nil {
|
||||
return nil, nil, nil, fmt.Errorf("list chains failed: %w", err)
|
||||
}
|
||||
var chainInput, chainForward, chainPostrouting *nftables.Chain
|
||||
for _, chain := range chains {
|
||||
switch chain.Name {
|
||||
case "ts-input":
|
||||
chainInput = chain
|
||||
case "ts-forward":
|
||||
chainForward = chain
|
||||
case "ts-postrouting":
|
||||
chainPostrouting = chain
|
||||
}
|
||||
}
|
||||
return chainInput, chainForward, chainPostrouting, nil
|
||||
}
|
||||
|
||||
// findV4BaseRules verifies that the base rules are present in the input and forward chains.
|
||||
func findV4BaseRules(
|
||||
conn *nftables.Conn,
|
||||
inpChain *nftables.Chain,
|
||||
forwChain *nftables.Chain,
|
||||
tunname string) ([]*nftables.Rule, error) {
|
||||
want := []*nftables.Rule{}
|
||||
rule, err := createReturnChromeOSVMRangeRule(inpChain.Table, inpChain, tunname)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("create rule: %w", err)
|
||||
}
|
||||
want = append(want, rule)
|
||||
rule, err = createReturnChromeOSVMRangeRule(inpChain.Table, inpChain, tunname)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("create rule: %w", err)
|
||||
}
|
||||
want = append(want, rule)
|
||||
rule, err = createDropOutgoingPacketFromCGNATRangeRuleWithTunname(forwChain.Table, forwChain, tunname)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("create rule: %w", err)
|
||||
}
|
||||
want = append(want, rule)
|
||||
|
||||
get := []*nftables.Rule{}
|
||||
for _, rule := range want {
|
||||
getRule, err := findRule(conn, rule)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("find rule: %w", err)
|
||||
}
|
||||
get = append(get, getRule)
|
||||
}
|
||||
return get, nil
|
||||
}
|
||||
|
||||
func findCommonBaseRules(
|
||||
conn *nftables.Conn,
|
||||
forwChain *nftables.Chain,
|
||||
tunname string) ([]*nftables.Rule, error) {
|
||||
want := []*nftables.Rule{}
|
||||
rule, err := createSetSubnetRouteMarkRule(forwChain.Table, forwChain, tunname)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("create rule: %w", err)
|
||||
}
|
||||
want = append(want, rule)
|
||||
rule, err = createMatchSubnetRouteMarkRule(forwChain.Table, forwChain, Accept)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("create rule: %w", err)
|
||||
}
|
||||
want = append(want, rule)
|
||||
rule = createAcceptOutgoingPacketRule(forwChain.Table, forwChain, tunname)
|
||||
want = append(want, rule)
|
||||
|
||||
get := []*nftables.Rule{}
|
||||
for _, rule := range want {
|
||||
getRule, err := findRule(conn, rule)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("find rule: %w", err)
|
||||
}
|
||||
get = append(get, getRule)
|
||||
}
|
||||
|
||||
return get, nil
|
||||
}
|
||||
|
||||
func TestNFTAddAndDelNetfilterBase(t *testing.T) {
|
||||
if os.Geteuid() != 0 {
|
||||
t.Skip(t.Name(), " requires priviliges to create a namespace in order to run")
|
||||
return
|
||||
}
|
||||
|
||||
conn, ns := newSysConn(t)
|
||||
defer cleanupSysConn(t, ns)
|
||||
|
||||
runner := newFakeNftablesRunner(t, conn)
|
||||
runner.AddChains()
|
||||
defer runner.DelChains()
|
||||
runner.AddBase("testTunn")
|
||||
|
||||
// check number of rules in each IPv4 TS chain
|
||||
inputV4, forwardV4, postroutingV4, err := getTsChains(conn, nftables.TableFamilyIPv4)
|
||||
if err != nil {
|
||||
t.Fatalf("getTsChains() failed: %v", err)
|
||||
}
|
||||
|
||||
inputV4Rules, err := conn.GetRules(runner.nft4.Filter, inputV4)
|
||||
if err != nil {
|
||||
t.Fatalf("conn.GetRules() failed: %v", err)
|
||||
}
|
||||
if len(inputV4Rules) != 2 {
|
||||
t.Fatalf("len(inputV4Rules) = %d, want 2", len(inputV4Rules))
|
||||
}
|
||||
|
||||
forwardV4Rules, err := conn.GetRules(runner.nft4.Filter, forwardV4)
|
||||
if err != nil {
|
||||
t.Fatalf("conn.GetRules() failed: %v", err)
|
||||
}
|
||||
if len(forwardV4Rules) != 4 {
|
||||
t.Fatalf("len(forwardV4Rules) = %d, want 4", len(forwardV4Rules))
|
||||
}
|
||||
|
||||
postroutingV4Rules, err := conn.GetRules(runner.nft4.Nat, postroutingV4)
|
||||
if err != nil {
|
||||
t.Fatalf("conn.GetRules() failed: %v", err)
|
||||
}
|
||||
if len(postroutingV4Rules) != 0 {
|
||||
t.Fatalf("len(postroutingV4Rules) = %d, want 0", len(postroutingV4Rules))
|
||||
}
|
||||
|
||||
_, err = findV4BaseRules(conn, inputV4, forwardV4, "testTunn")
|
||||
if err != nil {
|
||||
t.Fatalf("missing v4 base rule: %v", err)
|
||||
}
|
||||
_, err = findCommonBaseRules(conn, forwardV4, "testTunn")
|
||||
if err != nil {
|
||||
t.Fatalf("missing v4 base rule: %v", err)
|
||||
}
|
||||
|
||||
// Check number of rules in each IPv6 TS chain.
|
||||
inputV6, forwardV6, postroutingV6, err := getTsChains(conn, nftables.TableFamilyIPv6)
|
||||
if err != nil {
|
||||
t.Fatalf("getTsChains() failed: %v", err)
|
||||
}
|
||||
|
||||
inputV6Rules, err := conn.GetRules(runner.nft6.Filter, inputV6)
|
||||
if err != nil {
|
||||
t.Fatalf("conn.GetRules() failed: %v", err)
|
||||
}
|
||||
if len(inputV6Rules) != 0 {
|
||||
t.Fatalf("len(inputV6Rules) = %d, want 0", len(inputV4Rules))
|
||||
}
|
||||
|
||||
forwardV6Rules, err := conn.GetRules(runner.nft6.Filter, forwardV6)
|
||||
if err != nil {
|
||||
t.Fatalf("conn.GetRules() failed: %v", err)
|
||||
}
|
||||
if len(forwardV6Rules) != 3 {
|
||||
t.Fatalf("len(forwardV6Rules) = %d, want 3", len(forwardV4Rules))
|
||||
}
|
||||
|
||||
postroutingV6Rules, err := conn.GetRules(runner.nft6.Nat, postroutingV6)
|
||||
if err != nil {
|
||||
t.Fatalf("conn.GetRules() failed: %v", err)
|
||||
}
|
||||
if len(postroutingV6Rules) != 0 {
|
||||
t.Fatalf("len(postroutingV6Rules) = %d, want 0", len(postroutingV4Rules))
|
||||
}
|
||||
|
||||
_, err = findCommonBaseRules(conn, forwardV6, "testTunn")
|
||||
if err != nil {
|
||||
t.Fatalf("missing v6 base rule: %v", err)
|
||||
}
|
||||
|
||||
runner.DelBase()
|
||||
|
||||
chains, err := conn.ListChains()
|
||||
if err != nil {
|
||||
t.Fatalf("conn.ListChains() failed: %v", err)
|
||||
}
|
||||
for _, chain := range chains {
|
||||
chainRules, err := conn.GetRules(chain.Table, chain)
|
||||
if err != nil {
|
||||
t.Fatalf("conn.GetRules() failed: %v", err)
|
||||
}
|
||||
if len(chainRules) != 0 {
|
||||
t.Fatalf("len(chainRules) = %d, want 0", len(chainRules))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func findLoopBackRule(conn *nftables.Conn, proto nftables.TableFamily, table *nftables.Table, chain *nftables.Chain, addr netip.Addr) (*nftables.Rule, error) {
|
||||
matchingAddr := addr.AsSlice()
|
||||
saddrExpr, err := newLoadSaddrExpr(proto)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get expr: %w", err)
|
||||
}
|
||||
loopBackRule := &nftables.Rule{
|
||||
Table: table,
|
||||
Chain: chain,
|
||||
Exprs: []expr.Any{
|
||||
&expr.Meta{
|
||||
Key: expr.MetaKeyIIFNAME,
|
||||
Register: 1,
|
||||
},
|
||||
&expr.Cmp{
|
||||
Op: expr.CmpOpEq,
|
||||
Register: 1,
|
||||
Data: []byte("lo"),
|
||||
},
|
||||
saddrExpr,
|
||||
&expr.Cmp{
|
||||
Op: expr.CmpOpEq,
|
||||
Register: 1,
|
||||
Data: matchingAddr,
|
||||
},
|
||||
&expr.Counter{},
|
||||
&expr.Verdict{
|
||||
Kind: expr.VerdictAccept,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
existingLoopBackRule, err := findRule(conn, loopBackRule)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("find loop back rule: %w", err)
|
||||
}
|
||||
return existingLoopBackRule, nil
|
||||
}
|
||||
|
||||
func TestNFTAddAndDelLoopbackRule(t *testing.T) {
|
||||
if os.Geteuid() != 0 {
|
||||
t.Skip(t.Name(), " requires priviliges to create a namespace in order to run")
|
||||
return
|
||||
}
|
||||
|
||||
conn, ns := newSysConn(t)
|
||||
defer cleanupSysConn(t, ns)
|
||||
|
||||
runner := newFakeNftablesRunner(t, conn)
|
||||
runner.AddChains()
|
||||
defer runner.DelChains()
|
||||
runner.AddBase("testTunn")
|
||||
defer runner.DelBase()
|
||||
|
||||
addr := netip.MustParseAddr("192.168.0.2")
|
||||
addrV6 := netip.MustParseAddr("2001:db8::2")
|
||||
runner.AddLoopbackRule(addr)
|
||||
runner.AddLoopbackRule(addrV6)
|
||||
|
||||
inputV4, _, _, err := getTsChains(conn, nftables.TableFamilyIPv4)
|
||||
if err != nil {
|
||||
t.Fatalf("getTsChains() failed: %v", err)
|
||||
}
|
||||
|
||||
inputV4Rules, err := conn.GetRules(runner.nft4.Filter, inputV4)
|
||||
if err != nil {
|
||||
t.Fatalf("conn.GetRules() failed: %v", err)
|
||||
}
|
||||
if len(inputV4Rules) != 3 {
|
||||
t.Fatalf("len(inputV4Rules) = %d, want 3", len(inputV4Rules))
|
||||
}
|
||||
|
||||
existingLoopBackRule, err := findLoopBackRule(conn, nftables.TableFamilyIPv4, runner.nft4.Filter, inputV4, addr)
|
||||
if err != nil {
|
||||
t.Fatalf("findLoopBackRule() failed: %v", err)
|
||||
}
|
||||
|
||||
if existingLoopBackRule.Position != 0 {
|
||||
t.Fatalf("existingLoopBackRule.Handle = %d, want 0", existingLoopBackRule.Handle)
|
||||
}
|
||||
|
||||
inputV6, _, _, err := getTsChains(conn, nftables.TableFamilyIPv6)
|
||||
if err != nil {
|
||||
t.Fatalf("getTsChains() failed: %v", err)
|
||||
}
|
||||
|
||||
inputV6Rules, err := conn.GetRules(runner.nft6.Filter, inputV4)
|
||||
if err != nil {
|
||||
t.Fatalf("conn.GetRules() failed: %v", err)
|
||||
}
|
||||
if len(inputV6Rules) != 1 {
|
||||
t.Fatalf("len(inputV4Rules) = %d, want 1", len(inputV4Rules))
|
||||
}
|
||||
|
||||
existingLoopBackRuleV6, err := findLoopBackRule(conn, nftables.TableFamilyIPv6, runner.nft6.Filter, inputV6, addrV6)
|
||||
if err != nil {
|
||||
t.Fatalf("findLoopBackRule() failed: %v", err)
|
||||
}
|
||||
|
||||
if existingLoopBackRuleV6.Position != 0 {
|
||||
t.Fatalf("existingLoopBackRule.Handle = %d, want 0", existingLoopBackRule.Handle)
|
||||
}
|
||||
|
||||
runner.DelLoopbackRule(addr)
|
||||
runner.DelLoopbackRule(addrV6)
|
||||
|
||||
inputV4Rules, err = conn.GetRules(runner.nft4.Filter, inputV4)
|
||||
if err != nil {
|
||||
t.Fatalf("conn.GetRules() failed: %v", err)
|
||||
}
|
||||
if len(inputV4Rules) != 2 {
|
||||
t.Fatalf("len(inputV4Rules) = %d, want 2", len(inputV4Rules))
|
||||
}
|
||||
}
|
|
@ -55,14 +55,24 @@ type netfilterRunner interface {
|
|||
HasIPV6NAT() bool
|
||||
}
|
||||
|
||||
// newNetfilterRunner creates a netfilterRunner based on the current
|
||||
// TS_DEBUG_USE_NETLINK_NFTABLES envknob flag state.
|
||||
func newNetfilterRunner(logf logger.Logf) (netfilterRunner, error) {
|
||||
var nfr netfilterRunner
|
||||
var err error
|
||||
nfr, err = linuxfw.NewIPTablesRunner(logf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
if envknob.Bool("TS_DEBUG_USE_NETLINK_NFTABLES") {
|
||||
logf("router: using nftables")
|
||||
nfr, err = linuxfw.NewNfTablesRunner(logf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
logf("router: using iptables")
|
||||
nfr, err = linuxfw.NewIPTablesRunner(logf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return nfr, nil
|
||||
}
|
||||
|
||||
|
@ -1281,6 +1291,7 @@ func normalizeCIDR(cidr netip.Prefix) string {
|
|||
func cleanup(logf logger.Logf, interfaceName string) {
|
||||
if interfaceName != "userspace-networking" {
|
||||
linuxfw.IPTablesCleanup(logf)
|
||||
linuxfw.NftablesCleanUp(logf)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -107,7 +107,7 @@ func (e *watchdogEngine) watchdogErr(name string, fn func() error) error {
|
|||
// Print everything as a single string to avoid log
|
||||
// rate limits.
|
||||
e.logf("wgengine watchdog in-flight:\n%s", b)
|
||||
e.fatalf("wgengine: watchdog timeout on %s", name)
|
||||
// e.fatalf("wgengine: watchdog timeout on %s", name)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue