943b426038
Fix a panic in getOrCreateChain when the kernel lacks nftables support (CONFIG_NF_TABLES). When the nftables netlink connection fails, chain objects returned by getChainFromTable can have nil Hooknum and Priority fields. Dereferencing these caused tailscaled to SIGSEGV during router configuration, which manifested as tailscaled silently crashing ~13 seconds after "tailscale up" on arm64 gokrazy (whose kernel.arm64 build doesn't include nftables). Updates #13038 Change-Id: I14433616da5ed57895cad37038921fb4f79c3534 Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
1348 lines
62 KiB
Go
1348 lines
62 KiB
Go
// Copyright (c) Tailscale Inc & contributors
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
|
|
|
//go:build linux
|
|
|
|
package linuxfw
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"fmt"
|
|
"net/netip"
|
|
"runtime"
|
|
"slices"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/google/nftables"
|
|
"github.com/google/nftables/expr"
|
|
"github.com/mdlayher/netlink"
|
|
"github.com/vishvananda/netns"
|
|
"tailscale.com/net/tsaddr"
|
|
"tailscale.com/tstest"
|
|
"tailscale.com/types/logger"
|
|
)
|
|
|
|
func toAnySlice[T any](s []T) []any {
|
|
out := make([]any, len(s))
|
|
for i, v := range s {
|
|
out[i] = v
|
|
}
|
|
return out
|
|
}
|
|
|
|
// 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
|
|
for c := range slices.Chunk(b, 4) {
|
|
format := strings.Repeat("%02x ", len(c))
|
|
fmt.Fprintf(&buf, format+"\n", toAnySlice(c)...)
|
|
}
|
|
return buf.String()
|
|
}
|
|
|
|
func TestMaskof(t *testing.T) {
|
|
pfx, err := netip.ParsePrefix("192.168.1.0/24")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
want := []byte{0xff, 0xff, 0xff, 0x00}
|
|
if got := maskof(pfx); !bytes.Equal(got, want) {
|
|
t.Errorf("got %v; want %v", got, want)
|
|
}
|
|
}
|
|
|
|
// 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, reply [][]netlink.Message) *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:]
|
|
}
|
|
// no reply for batch end message
|
|
if len(want) == 0 {
|
|
return nil, nil
|
|
}
|
|
rep := reply[0]
|
|
reply = reply[1:]
|
|
return rep, nil
|
|
}))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return conn
|
|
}
|
|
|
|
func TestInsertHookRule(t *testing.T) {
|
|
proto := nftables.TableFamilyIPv4
|
|
want := [][]byte{
|
|
// batch begin
|
|
[]byte("\x00\x00\x00\x0a"),
|
|
// nft add table ip ts-filter-test
|
|
[]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"),
|
|
// nft add chain ip ts-filter-test ts-input-test { type filter hook input priority 0 \; }
|
|
[]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"),
|
|
// nft add chain ip ts-filter-test ts-jumpto
|
|
[]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\x0e\x00\x03\x00\x74\x73\x2d\x6a\x75\x6d\x70\x74\x6f\x00\x00\x00"),
|
|
// nft add rule ip ts-filter-test ts-input-test counter jump ts-jumptp
|
|
[]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\x70\x00\x04\x80\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\x40\x00\x01\x80\x0e\x00\x01\x00\x69\x6d\x6d\x65\x64\x69\x61\x74\x65\x00\x00\x00\x2c\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x00\x20\x00\x02\x80\x1c\x00\x02\x80\x08\x00\x01\x00\xff\xff\xff\xfd\x0e\x00\x02\x00\x74\x73\x2d\x6a\x75\x6d\x70\x74\x6f\x00\x00\x00"),
|
|
// batch end
|
|
[]byte("\x00\x00\x00\x0a"),
|
|
}
|
|
testConn := newTestConn(t, want, nil)
|
|
table := testConn.AddTable(&nftables.Table{
|
|
Family: proto,
|
|
Name: "ts-filter-test",
|
|
})
|
|
|
|
fromchain := testConn.AddChain(&nftables.Chain{
|
|
Name: "ts-input-test",
|
|
Table: table,
|
|
Type: nftables.ChainTypeFilter,
|
|
Hooknum: nftables.ChainHookInput,
|
|
Priority: nftables.ChainPriorityFilter,
|
|
})
|
|
|
|
tochain := testConn.AddChain(&nftables.Chain{
|
|
Name: "ts-jumpto",
|
|
Table: table,
|
|
})
|
|
|
|
err := addHookRule(testConn, table, fromchain, tochain.Name)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
}
|
|
|
|
func TestInsertLoopbackRule(t *testing.T) {
|
|
proto := nftables.TableFamilyIPv4
|
|
want := [][]byte{
|
|
// batch begin
|
|
[]byte("\x00\x00\x00\x0a"),
|
|
// nft add table ip ts-filter-test
|
|
[]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"),
|
|
// nft add chain ip ts-filter-test ts-input-test { type filter hook input priority 0 \; }
|
|
[]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"),
|
|
// nft add rule ip ts-filter-test ts-input-test iifname "lo" ip saddr 192.168.0.2 counter accept
|
|
[]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"),
|
|
// batch end
|
|
[]byte("\x00\x00\x00\x0a"),
|
|
}
|
|
testConn := newTestConn(t, want, nil)
|
|
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{
|
|
// batch begin
|
|
[]byte("\x00\x00\x00\x0a"),
|
|
// nft add table ip6 ts-filter-test
|
|
[]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"),
|
|
// nft add chain ip6 ts-filter-test ts-input-test { type filter hook input priority 0\; }
|
|
[]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"),
|
|
// nft add rule ip6 ts-filter-test ts-input-test iifname "lo" ip6 addr 2001:db8::1 counter accept
|
|
[]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"),
|
|
// batch end
|
|
[]byte("\x00\x00\x00\x0a"),
|
|
}
|
|
testConn := newTestConn(t, want, nil)
|
|
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{
|
|
// batch begin
|
|
[]byte("\x00\x00\x00\x0a"),
|
|
// nft add table ip ts-filter-test
|
|
[]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"),
|
|
// nft add chain ip ts-filter-test ts-input-test { type filter hook input priority 0\; }
|
|
[]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"),
|
|
// nft add rule ip ts-filter-test ts-input-test iifname != "testTunn" ip saddr 100.115.92.0/23 counter return
|
|
[]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"),
|
|
// batch end
|
|
[]byte("\x00\x00\x00\x0a"),
|
|
}
|
|
testConn := newTestConn(t, want, nil)
|
|
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{
|
|
// batch begin
|
|
[]byte("\x00\x00\x00\x0a"),
|
|
// nft add table ip ts-filter-test
|
|
[]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"),
|
|
// nft add chain ip ts-filter-test ts-input-test { type filter hook input priority filter; }
|
|
[]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"),
|
|
// nft add rule ip ts-filter-test ts-input-test iifname != "testTunn" ip saddr 100.64.0.0/10 counter drop
|
|
[]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"),
|
|
// batch end
|
|
[]byte("\x00\x00\x00\x0a"),
|
|
}
|
|
testConn := newTestConn(t, want, nil)
|
|
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{
|
|
// batch begin
|
|
[]byte("\x00\x00\x00\x0a"),
|
|
// nft add table ip ts-filter-test
|
|
[]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"),
|
|
// nft add chain ip ts-filter-test ts-forward-test { type filter hook forward priority 0\; }
|
|
[]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"),
|
|
// nft add rule ip ts-filter-test ts-forward-test iifname "testTunn" counter meta mark set mark and 0xff00ffff xor 0x40000
|
|
[]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\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\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\x03\x00\x00\x00\x00\x01"),
|
|
// batch end
|
|
[]byte("\x00\x00\x00\x0a"),
|
|
}
|
|
testConn := newTestConn(t, want, nil)
|
|
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{
|
|
// batch begin
|
|
[]byte("\x00\x00\x00\x0a"),
|
|
// nft add table ip ts-filter-test
|
|
[]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"),
|
|
// nft add chain ip ts-filter-test ts-forward-test { type filter hook forward priority 0\; }
|
|
[]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"),
|
|
// nft add rule ip ts-filter-test ts-forward-test oifname "testTunn" ip saddr 100.64.0.0/10 counter drop
|
|
[]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"),
|
|
// batch end
|
|
[]byte("\x00\x00\x00\x0a"),
|
|
}
|
|
testConn := newTestConn(t, want, nil)
|
|
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{
|
|
// batch begin
|
|
[]byte("\x00\x00\x00\x0a"),
|
|
// nft add table ip ts-filter-test
|
|
[]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"),
|
|
// nft add chain ip ts-filter-test ts-forward-test { type filter hook forward priority 0\; }
|
|
[]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"),
|
|
// nft add rule ip ts-filter-test ts-forward-test oifname "testTunn" counter accept
|
|
[]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"),
|
|
// batch end
|
|
[]byte("\x00\x00\x00\x0a"),
|
|
}
|
|
testConn := newTestConn(t, want, nil)
|
|
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 TestAddAcceptIncomingPacketRule(t *testing.T) {
|
|
proto := nftables.TableFamilyIPv4
|
|
want := [][]byte{
|
|
// batch begin
|
|
[]byte("\x00\x00\x00\x0a"),
|
|
// nft add table ip ts-filter-test
|
|
[]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"),
|
|
// nft add chain ip ts-filter-test ts-input-test { type filter hook input priority 0\; }
|
|
[]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"),
|
|
// nft add rule ip ts-filter-test ts-input-test iifname "testTunn" counter accept
|
|
[]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\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\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\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"),
|
|
// batch end
|
|
[]byte("\x00\x00\x00\x0a"),
|
|
}
|
|
testConn := newTestConn(t, want, nil)
|
|
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 := addAcceptIncomingPacketRule(testConn, table, chain, "testTunn")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func TestAddMatchSubnetRouteMarkRuleMasq(t *testing.T) {
|
|
proto := nftables.TableFamilyIPv4
|
|
want := [][]byte{
|
|
// batch begin
|
|
[]byte("\x00\x00\x00\x0a"),
|
|
// nft add table ip ts-nat-test
|
|
[]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"),
|
|
// nft add chain ip ts-nat-test ts-postrouting-test { type nat hook postrouting priority 100; }
|
|
[]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"),
|
|
// nft add rule ip ts-nat-test ts-postrouting-test meta mark & 0x00ff0000 == 0x00040000 counter masquerade
|
|
[]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\xd8\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\x14\x00\x01\x80\x09\x00\x01\x00\x6d\x61\x73\x71\x00\x00\x00\x00\x04\x00\x02\x80"),
|
|
// batch end
|
|
[]byte("\x00\x00\x00\x0a"),
|
|
}
|
|
testConn := newTestConn(t, want, nil)
|
|
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, Masq)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func TestDelMatchSubnetRouteMarkMasqRule(t *testing.T) {
|
|
proto := nftables.TableFamilyIPv4
|
|
reply := [][]netlink.Message{
|
|
nil,
|
|
{{Header: netlink.Header{Length: 0x128, Type: 0xa06, Flags: 0x802, Sequence: 0xa213d55d, PID: 0x11e79}, Data: []uint8{0x2, 0x0, 0x0, 0x8c, 0xd, 0x0, 0x1, 0x0, 0x6e, 0x61, 0x74, 0x2d, 0x74, 0x65, 0x73, 0x74, 0x0, 0x0, 0x0, 0x0, 0x18, 0x0, 0x2, 0x0, 0x74, 0x73, 0x2d, 0x70, 0x6f, 0x73, 0x74, 0x72, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x2d, 0x74, 0x65, 0x73, 0x74, 0x0, 0xc, 0x0, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 0xe0, 0x0, 0x4, 0x0, 0x24, 0x0, 0x1, 0x0, 0x9, 0x0, 0x1, 0x0, 0x6d, 0x65, 0x74, 0x61, 0x0, 0x0, 0x0, 0x0, 0x14, 0x0, 0x2, 0x0, 0x8, 0x0, 0x2, 0x0, 0x0, 0x0, 0x0, 0x3, 0x8, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x1, 0x4c, 0x0, 0x1, 0x0, 0xc, 0x0, 0x1, 0x0, 0x62, 0x69, 0x74, 0x77, 0x69, 0x73, 0x65, 0x0, 0x3c, 0x0, 0x2, 0x0, 0x8, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x1, 0x8, 0x0, 0x2, 0x0, 0x0, 0x0, 0x0, 0x1, 0x8, 0x0, 0x3, 0x0, 0x0, 0x0, 0x0, 0x4, 0x8, 0x0, 0x6, 0x0, 0x0, 0x0, 0x0, 0x0, 0xc, 0x0, 0x4, 0x0, 0x8, 0x0, 0x1, 0x0, 0x0, 0xff, 0x0, 0x0, 0xc, 0x0, 0x5, 0x0, 0x8, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2c, 0x0, 0x1, 0x0, 0x8, 0x0, 0x1, 0x0, 0x63, 0x6d, 0x70, 0x0, 0x20, 0x0, 0x2, 0x0, 0x8, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x1, 0x8, 0x0, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0xc, 0x0, 0x3, 0x0, 0x8, 0x0, 0x1, 0x0, 0x0, 0x4, 0x0, 0x0, 0x2c, 0x0, 0x1, 0x0, 0xc, 0x0, 0x1, 0x0, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x0, 0x1c, 0x0, 0x2, 0x0, 0xc, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xc, 0x0, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x14, 0x0, 0x1, 0x0, 0x9, 0x0, 0x1, 0x0, 0x6d, 0x61, 0x73, 0x71, 0x0, 0x0, 0x0, 0x0, 0x4, 0x0, 0x2, 0x0}}},
|
|
{{Header: netlink.Header{Length: 0x14, Type: 0x3, Flags: 0x2, Sequence: 0x311fdccb, PID: 0x11e79}, Data: []uint8{0x0, 0x0, 0x0, 0x0}}},
|
|
{{Header: netlink.Header{Length: 0x24, Type: 0x2, Flags: 0x100, Sequence: 0x311fdccb, PID: 0x11e79}, Data: []uint8{0x0, 0x0, 0x0, 0x0, 0x48, 0x0, 0x0, 0x0, 0x8, 0xa, 0x5, 0x0, 0xcb, 0xdc, 0x1f, 0x31, 0x79, 0x1e, 0x1, 0x0}}},
|
|
}
|
|
want := [][]byte{
|
|
// get rules in nat-test table ts-postrouting-test chain
|
|
[]byte("\x02\x00\x00\x00\x0d\x00\x01\x00\x6e\x61\x74\x2d\x74\x65\x73\x74\x00\x00\x00\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"),
|
|
// batch begin
|
|
[]byte("\x00\x00\x00\x0a"),
|
|
// nft delete rule ip nat-test ts-postrouting-test handle 4
|
|
[]byte("\x02\x00\x00\x00\x0d\x00\x01\x00\x6e\x61\x74\x2d\x74\x65\x73\x74\x00\x00\x00\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\x0c\x00\x03\x00\x00\x00\x00\x00\x00\x00\x00\x04"),
|
|
// batch end
|
|
[]byte("\x00\x00\x00\x0a"),
|
|
}
|
|
|
|
conn := newTestConn(t, want, reply)
|
|
|
|
table := &nftables.Table{
|
|
Family: proto,
|
|
Name: "nat-test",
|
|
}
|
|
chain := &nftables.Chain{
|
|
Name: "ts-postrouting-test",
|
|
Table: table,
|
|
Type: nftables.ChainTypeNAT,
|
|
Hooknum: nftables.ChainHookPostrouting,
|
|
Priority: nftables.ChainPriorityNATSource,
|
|
}
|
|
|
|
err := delMatchSubnetRouteMarkMasqRule(conn, table, chain)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func TestAddMatchSubnetRouteMarkRuleAccept(t *testing.T) {
|
|
proto := nftables.TableFamilyIPv4
|
|
want := [][]byte{
|
|
// batch begin
|
|
[]byte("\x00\x00\x00\x0a"),
|
|
// nft add table ip ts-filter-test
|
|
[]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"),
|
|
// nft add chain ip ts-filter-test ts-forward-test { type filter hook forward priority 0\; }
|
|
[]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"),
|
|
// nft add rule ip ts-filter-test ts-forward-test meta mark and 0x00ff0000 eq 0x00040000 counter accept
|
|
[]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"),
|
|
// batch end
|
|
[]byte("\x00\x00\x00\x0a"),
|
|
}
|
|
testConn := newTestConn(t, want, nil)
|
|
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 {
|
|
t.Helper()
|
|
tstest.RequireRoot(t)
|
|
|
|
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)
|
|
}
|
|
|
|
t.Cleanup(func() { cleanupSysConn(t, ns) })
|
|
|
|
return c
|
|
}
|
|
|
|
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 checkChains(t *testing.T, conn *nftables.Conn, fam nftables.TableFamily, wantCount int) {
|
|
t.Helper()
|
|
got, err := conn.ListChainsOfTableFamily(fam)
|
|
if err != nil {
|
|
t.Fatalf("conn.ListChainsOfTableFamily(%v) failed: %v", fam, err)
|
|
}
|
|
if len(got) != wantCount {
|
|
t.Fatalf("len(got) = %d, want %d", len(got), wantCount)
|
|
}
|
|
}
|
|
func checkTables(t *testing.T, conn *nftables.Conn, fam nftables.TableFamily, wantCount int) {
|
|
t.Helper()
|
|
got, err := conn.ListTablesOfFamily(fam)
|
|
if err != nil {
|
|
t.Fatalf("conn.ListTablesOfFamily(%v) failed: %v", fam, err)
|
|
}
|
|
if len(got) != wantCount {
|
|
t.Fatalf("len(got) = %d, want %d", len(got), wantCount)
|
|
}
|
|
}
|
|
|
|
func TestAddAndDelNetfilterChains(t *testing.T) {
|
|
type test struct {
|
|
hostHasIPv6 bool
|
|
initIPv4ChainCount int
|
|
initIPv6ChainCount int
|
|
ipv4TableCount int
|
|
ipv6TableCount int
|
|
ipv4ChainCount int
|
|
ipv6ChainCount int
|
|
ipv4ChainCountPostDelete int
|
|
ipv6ChainCountPostDelete int
|
|
}
|
|
tests := []test{
|
|
{
|
|
hostHasIPv6: true,
|
|
initIPv4ChainCount: 0,
|
|
initIPv6ChainCount: 0,
|
|
ipv4TableCount: 2,
|
|
ipv6TableCount: 2,
|
|
ipv4ChainCount: 6,
|
|
ipv6ChainCount: 6,
|
|
ipv4ChainCountPostDelete: 3,
|
|
ipv6ChainCountPostDelete: 3,
|
|
},
|
|
{ // host without IPv6 support
|
|
ipv4TableCount: 2,
|
|
ipv4ChainCount: 6,
|
|
ipv4ChainCountPostDelete: 3,
|
|
}}
|
|
for _, tt := range tests {
|
|
t.Logf("running a test case for IPv6 support: %v", tt.hostHasIPv6)
|
|
conn := newSysConn(t)
|
|
runner := newFakeNftablesRunnerWithConn(t, conn, tt.hostHasIPv6)
|
|
|
|
// Check that we start off with no chains.
|
|
checkChains(t, conn, nftables.TableFamilyIPv4, tt.initIPv4ChainCount)
|
|
checkChains(t, conn, nftables.TableFamilyIPv6, tt.initIPv6ChainCount)
|
|
|
|
if err := runner.AddChains(); err != nil {
|
|
t.Fatalf("runner.AddChains() failed: %v", err)
|
|
}
|
|
|
|
// Check that the amount of tables for each IP family is as expected.
|
|
checkTables(t, conn, nftables.TableFamilyIPv4, tt.ipv4TableCount)
|
|
checkTables(t, conn, nftables.TableFamilyIPv6, tt.ipv6TableCount)
|
|
|
|
// Check that the amount of chains for each IP family is as expected.
|
|
checkChains(t, conn, nftables.TableFamilyIPv4, tt.ipv4ChainCount)
|
|
checkChains(t, conn, nftables.TableFamilyIPv6, tt.ipv6ChainCount)
|
|
|
|
if err := runner.DelChains(); err != nil {
|
|
t.Fatalf("runner.DelChains() failed: %v", err)
|
|
}
|
|
|
|
// Test that the tables as well as the default chains are still present.
|
|
checkChains(t, conn, nftables.TableFamilyIPv4, tt.ipv4ChainCountPostDelete)
|
|
checkChains(t, conn, nftables.TableFamilyIPv6, tt.ipv6ChainCountPostDelete)
|
|
checkTables(t, conn, nftables.TableFamilyIPv4, tt.ipv4TableCount)
|
|
checkTables(t, conn, nftables.TableFamilyIPv6, tt.ipv6TableCount)
|
|
}
|
|
}
|
|
|
|
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 := createRangeRule(inpChain.Table, inpChain, tunname, tsaddr.ChromeOSVMRange(), expr.VerdictReturn)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("create rule: %w", err)
|
|
}
|
|
want = append(want, rule)
|
|
rule, err = createRangeRule(inpChain.Table, inpChain, tunname, tsaddr.CGNATRange(), expr.VerdictDrop)
|
|
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
|
|
}
|
|
|
|
// checkChainRules verifies that the chain has the expected number of rules.
|
|
func checkChainRules(t *testing.T, conn *nftables.Conn, chain *nftables.Chain, wantCount int) {
|
|
t.Helper()
|
|
got, err := conn.GetRules(chain.Table, chain)
|
|
if err != nil {
|
|
t.Fatalf("conn.GetRules() failed: %v", err)
|
|
}
|
|
if len(got) != wantCount {
|
|
t.Fatalf("got = %d, want %d", len(got), wantCount)
|
|
}
|
|
}
|
|
|
|
func TestNFTAddAndDelNetfilterBase(t *testing.T) {
|
|
conn := newSysConn(t)
|
|
|
|
runner := newFakeNftablesRunnerWithConn(t, conn, true)
|
|
|
|
if err := runner.AddChains(); err != nil {
|
|
t.Fatalf("AddChains() failed: %v", err)
|
|
}
|
|
defer runner.DelChains()
|
|
if err := runner.AddBase("testTunn"); err != nil {
|
|
t.Fatalf("AddBase() failed: %v", err)
|
|
}
|
|
|
|
// 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)
|
|
}
|
|
checkChainRules(t, conn, inputV4, 3)
|
|
checkChainRules(t, conn, forwardV4, 4)
|
|
checkChainRules(t, conn, postroutingV4, 0)
|
|
|
|
_, 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)
|
|
}
|
|
checkChainRules(t, conn, inputV6, 3)
|
|
checkChainRules(t, conn, forwardV6, 4)
|
|
checkChainRules(t, conn, postroutingV6, 0)
|
|
|
|
_, 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 {
|
|
checkChainRules(t, conn, chain, 0)
|
|
}
|
|
}
|
|
|
|
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, 1)
|
|
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) {
|
|
conn := newSysConn(t)
|
|
|
|
runner := newFakeNftablesRunnerWithConn(t, conn, true)
|
|
if err := runner.AddChains(); err != nil {
|
|
t.Fatalf("AddChains() failed: %v", err)
|
|
}
|
|
defer runner.DelChains()
|
|
|
|
inputV4, _, _, err := getTsChains(conn, nftables.TableFamilyIPv4)
|
|
if err != nil {
|
|
t.Fatalf("getTsChains() failed: %v", err)
|
|
}
|
|
|
|
inputV6, _, _, err := getTsChains(conn, nftables.TableFamilyIPv6)
|
|
if err != nil {
|
|
t.Fatalf("getTsChains() failed: %v", err)
|
|
}
|
|
checkChainRules(t, conn, inputV4, 0)
|
|
checkChainRules(t, conn, inputV6, 0)
|
|
|
|
runner.AddBase("testTunn")
|
|
defer runner.DelBase()
|
|
checkChainRules(t, conn, inputV4, 3)
|
|
checkChainRules(t, conn, inputV6, 3)
|
|
|
|
addr := netip.MustParseAddr("192.168.0.2")
|
|
addrV6 := netip.MustParseAddr("2001:db8::2")
|
|
runner.AddLoopbackRule(addr)
|
|
runner.AddLoopbackRule(addrV6)
|
|
|
|
checkChainRules(t, conn, inputV4, 4)
|
|
checkChainRules(t, conn, inputV6, 4)
|
|
|
|
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)
|
|
}
|
|
|
|
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)
|
|
|
|
checkChainRules(t, conn, inputV4, 3)
|
|
checkChainRules(t, conn, inputV6, 3)
|
|
}
|
|
|
|
func TestNFTAddAndDelHookRule(t *testing.T) {
|
|
conn := newSysConn(t)
|
|
runner := newFakeNftablesRunnerWithConn(t, conn, true)
|
|
if err := runner.AddChains(); err != nil {
|
|
t.Fatalf("AddChains() failed: %v", err)
|
|
}
|
|
defer runner.DelChains()
|
|
if err := runner.AddHooks(); err != nil {
|
|
t.Fatalf("AddHooks() failed: %v", err)
|
|
}
|
|
|
|
forwardChain, err := getChainFromTable(conn, runner.nft4.Filter, "FORWARD")
|
|
if err != nil {
|
|
t.Fatalf("failed to get forwardChain: %v", err)
|
|
}
|
|
inputChain, err := getChainFromTable(conn, runner.nft4.Filter, "INPUT")
|
|
if err != nil {
|
|
t.Fatalf("failed to get inputChain: %v", err)
|
|
}
|
|
postroutingChain, err := getChainFromTable(conn, runner.nft4.Nat, "POSTROUTING")
|
|
if err != nil {
|
|
t.Fatalf("failed to get postroutingChain: %v", err)
|
|
}
|
|
|
|
checkChainRules(t, conn, forwardChain, 1)
|
|
checkChainRules(t, conn, inputChain, 1)
|
|
checkChainRules(t, conn, postroutingChain, 1)
|
|
|
|
runner.DelHooks(t.Logf)
|
|
|
|
checkChainRules(t, conn, forwardChain, 0)
|
|
checkChainRules(t, conn, inputChain, 0)
|
|
checkChainRules(t, conn, postroutingChain, 0)
|
|
}
|
|
|
|
type testFWDetector struct {
|
|
iptRuleCount, nftRuleCount int
|
|
iptErr, nftErr error
|
|
}
|
|
|
|
func (t *testFWDetector) iptDetect() (int, error) {
|
|
return t.iptRuleCount, t.iptErr
|
|
}
|
|
|
|
func (t *testFWDetector) nftDetect() (int, error) {
|
|
return t.nftRuleCount, t.nftErr
|
|
}
|
|
|
|
// TestCreateDummyPostroutingChains tests that on a system with nftables
|
|
// available, the function does not return an error and that the dummy
|
|
// postrouting chains are cleaned up.
|
|
func TestCreateDummyPostroutingChains(t *testing.T) {
|
|
conn := newSysConn(t)
|
|
runner := newFakeNftablesRunnerWithConn(t, conn, true)
|
|
if err := runner.createDummyPostroutingChains(); err != nil {
|
|
t.Fatalf("createDummyPostroutingChains() failed: %v", err)
|
|
}
|
|
for _, table := range runner.getTables() {
|
|
nt, err := getTableIfExists(conn, table.Proto, tsDummyTableName)
|
|
if err != nil {
|
|
t.Fatalf("getTableIfExists() failed: %v", err)
|
|
}
|
|
if nt != nil {
|
|
t.Fatalf("expected table to be nil, got %v", nt)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestPickFirewallModeFromInstalledRules(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
det *testFWDetector
|
|
want FirewallMode
|
|
}{
|
|
{
|
|
name: "using-iptables-legacy",
|
|
det: &testFWDetector{iptRuleCount: 1},
|
|
want: FirewallModeIPTables,
|
|
},
|
|
{
|
|
name: "using-nftables",
|
|
det: &testFWDetector{nftRuleCount: 1},
|
|
want: FirewallModeNfTables,
|
|
},
|
|
{
|
|
name: "using-both-iptables-and-nftables",
|
|
det: &testFWDetector{iptRuleCount: 2, nftRuleCount: 2},
|
|
want: FirewallModeNfTables,
|
|
},
|
|
{
|
|
name: "no-firewall-both-available",
|
|
det: &testFWDetector{},
|
|
want: FirewallModeNfTables,
|
|
},
|
|
{
|
|
name: "no-firewall-iptables-only",
|
|
det: &testFWDetector{iptRuleCount: 1, nftErr: errors.New("nft error")},
|
|
want: FirewallModeIPTables,
|
|
},
|
|
{
|
|
name: "no-firewall-nftables-only",
|
|
det: &testFWDetector{iptErr: errors.New("iptables error"), nftRuleCount: 1},
|
|
want: FirewallModeNfTables,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
got := pickFirewallModeFromInstalledRules(t.Logf, tt.det)
|
|
if got != tt.want {
|
|
t.Errorf("chooseFireWallMode() = %v, want %v", got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// This test creates a temporary network namespace for the nftables rules being
|
|
// set up, so it needs to run in a privileged mode. Locally it needs to be run
|
|
// by root, else it will be silently skipped. In CI it runs in a privileged
|
|
// container.
|
|
func TestEnsureSNATForDst_nftables(t *testing.T) {
|
|
conn := newSysConn(t)
|
|
runner := newFakeNftablesRunnerWithConn(t, conn, true)
|
|
ip1, ip2, ip3 := netip.MustParseAddr("100.99.99.99"), netip.MustParseAddr("100.88.88.88"), netip.MustParseAddr("100.77.77.77")
|
|
|
|
// 1. A new rule gets added
|
|
mustCreateSNATRule_nft(t, runner, ip1, ip2)
|
|
chainRuleCount(t, "POSTROUTING", 1, conn, nftables.TableFamilyIPv4)
|
|
checkSNATRule_nft(t, runner, runner.nft4.Proto, ip1, ip2)
|
|
|
|
// 2. Another call to EnsureSNATForDst with the same src and dst does not result in another rule being added.
|
|
mustCreateSNATRule_nft(t, runner, ip1, ip2)
|
|
chainRuleCount(t, "POSTROUTING", 1, conn, nftables.TableFamilyIPv4) // still just one rule
|
|
checkSNATRule_nft(t, runner, runner.nft4.Proto, ip1, ip2)
|
|
|
|
// 3. Another call to EnsureSNATForDst with a different src and the same dst results in the earlier rule being
|
|
// deleted.
|
|
mustCreateSNATRule_nft(t, runner, ip3, ip2)
|
|
chainRuleCount(t, "POSTROUTING", 1, conn, nftables.TableFamilyIPv4) // still just one rule
|
|
checkSNATRule_nft(t, runner, runner.nft4.Proto, ip3, ip2)
|
|
|
|
// 4. Another call to EnsureSNATForDst with a different dst should not get the earlier rule deleted.
|
|
mustCreateSNATRule_nft(t, runner, ip3, ip1)
|
|
chainRuleCount(t, "POSTROUTING", 2, conn, nftables.TableFamilyIPv4) // now two rules
|
|
checkSNATRule_nft(t, runner, runner.nft4.Proto, ip3, ip1)
|
|
}
|
|
|
|
func newFakeNftablesRunnerWithConn(t *testing.T, conn *nftables.Conn, hasIPv6 bool) *nftablesRunner {
|
|
t.Helper()
|
|
if !hasIPv6 {
|
|
tstest.Replace(t, &checkIPv6ForTest, func(logger.Logf) error {
|
|
return errors.New("test: no IPv6")
|
|
})
|
|
|
|
}
|
|
return newNfTablesRunnerWithConn(t.Logf, conn)
|
|
}
|
|
|
|
func mustCreateSNATRule_nft(t *testing.T, runner *nftablesRunner, src, dst netip.Addr) {
|
|
t.Helper()
|
|
if err := runner.EnsureSNATForDst(src, dst); err != nil {
|
|
t.Fatalf("error ensuring SNAT rule: %v", err)
|
|
}
|
|
}
|
|
|
|
// checkSNATRule_nft verifies that a SNAT rule for the given destination and source exists.
|
|
func checkSNATRule_nft(t *testing.T, runner *nftablesRunner, fam nftables.TableFamily, src, dst netip.Addr) {
|
|
t.Helper()
|
|
chains, err := runner.conn.ListChainsOfTableFamily(fam)
|
|
if err != nil {
|
|
t.Fatalf("error listing chains: %v", err)
|
|
}
|
|
var chain *nftables.Chain
|
|
for _, ch := range chains {
|
|
if ch.Name == "POSTROUTING" {
|
|
chain = ch
|
|
break
|
|
}
|
|
}
|
|
if chain == nil {
|
|
t.Fatal("POSTROUTING chain does not exist")
|
|
}
|
|
meta := fmt.Appendf(nil, "dst:%s,src:%s", dst.String(), src.String())
|
|
wantsRule := snatRule(chain.Table, chain, src, dst, meta)
|
|
checkRule(t, wantsRule, runner.conn)
|
|
}
|
|
|
|
// TestNFTAddAndDelConnmarkRules tests adding and removing connmark rules
|
|
// in a real network namespace. This verifies the rules are correctly created
|
|
// and cleaned up.
|
|
func TestNFTAddAndDelConnmarkRules(t *testing.T) {
|
|
conn := newSysConn(t)
|
|
runner := newFakeNftablesRunnerWithConn(t, conn, true)
|
|
|
|
// Helper to get mangle chains
|
|
getMangleChains := func(fam nftables.TableFamily) (prerouting, output *nftables.Chain, err error) {
|
|
chains, err := conn.ListChainsOfTableFamily(fam)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
for _, ch := range chains {
|
|
if ch.Table.Name != "mangle" {
|
|
continue
|
|
}
|
|
if ch.Name == "PREROUTING" {
|
|
prerouting = ch
|
|
} else if ch.Name == "OUTPUT" {
|
|
output = ch
|
|
}
|
|
}
|
|
return prerouting, output, nil
|
|
}
|
|
|
|
// Check initial state - mangle chains might not exist yet
|
|
prerouting4Before, output4Before, _ := getMangleChains(nftables.TableFamilyIPv4)
|
|
prerouting6Before, output6Before, _ := getMangleChains(nftables.TableFamilyIPv6)
|
|
|
|
var prerouting4RulesBefore, output4RulesBefore, prerouting6RulesBefore, output6RulesBefore int
|
|
if prerouting4Before != nil {
|
|
rules, _ := conn.GetRules(prerouting4Before.Table, prerouting4Before)
|
|
prerouting4RulesBefore = len(rules)
|
|
}
|
|
if output4Before != nil {
|
|
rules, _ := conn.GetRules(output4Before.Table, output4Before)
|
|
output4RulesBefore = len(rules)
|
|
}
|
|
if prerouting6Before != nil {
|
|
rules, _ := conn.GetRules(prerouting6Before.Table, prerouting6Before)
|
|
prerouting6RulesBefore = len(rules)
|
|
}
|
|
if output6Before != nil {
|
|
rules, _ := conn.GetRules(output6Before.Table, output6Before)
|
|
output6RulesBefore = len(rules)
|
|
}
|
|
|
|
// Add connmark rules
|
|
if err := runner.AddConnmarkSaveRule(); err != nil {
|
|
t.Fatalf("AddConnmarkSaveRule() failed: %v", err)
|
|
}
|
|
|
|
// Verify rules were added
|
|
prerouting4After, output4After, err := getMangleChains(nftables.TableFamilyIPv4)
|
|
if err != nil {
|
|
t.Fatalf("Failed to get IPv4 mangle chains: %v", err)
|
|
}
|
|
if prerouting4After == nil || output4After == nil {
|
|
t.Fatal("IPv4 mangle chains not created")
|
|
}
|
|
|
|
prerouting4Rules, err := conn.GetRules(prerouting4After.Table, prerouting4After)
|
|
if err != nil {
|
|
t.Fatalf("GetRules(PREROUTING) failed: %v", err)
|
|
}
|
|
output4Rules, err := conn.GetRules(output4After.Table, output4After)
|
|
if err != nil {
|
|
t.Fatalf("GetRules(OUTPUT) failed: %v", err)
|
|
}
|
|
|
|
// Should have added 1 rule to each chain
|
|
if len(prerouting4Rules) != prerouting4RulesBefore+1 {
|
|
t.Fatalf("PREROUTING rules: got %d, want %d", len(prerouting4Rules), prerouting4RulesBefore+1)
|
|
}
|
|
if len(output4Rules) != output4RulesBefore+1 {
|
|
t.Fatalf("OUTPUT rules: got %d, want %d", len(output4Rules), output4RulesBefore+1)
|
|
}
|
|
|
|
// Verify IPv6 rules
|
|
prerouting6After, output6After, err := getMangleChains(nftables.TableFamilyIPv6)
|
|
if err != nil {
|
|
t.Fatalf("Failed to get IPv6 mangle chains: %v", err)
|
|
}
|
|
if prerouting6After == nil || output6After == nil {
|
|
t.Fatal("IPv6 mangle chains not created")
|
|
}
|
|
|
|
prerouting6Rules, err := conn.GetRules(prerouting6After.Table, prerouting6After)
|
|
if err != nil {
|
|
t.Fatalf("GetRules(IPv6 PREROUTING) failed: %v", err)
|
|
}
|
|
output6Rules, err := conn.GetRules(output6After.Table, output6After)
|
|
if err != nil {
|
|
t.Fatalf("GetRules(IPv6 OUTPUT) failed: %v", err)
|
|
}
|
|
|
|
if len(prerouting6Rules) != prerouting6RulesBefore+1 {
|
|
t.Fatalf("IPv6 PREROUTING rules: got %d, want %d", len(prerouting6Rules), prerouting6RulesBefore+1)
|
|
}
|
|
if len(output6Rules) != output6RulesBefore+1 {
|
|
t.Fatalf("IPv6 OUTPUT rules: got %d, want %d", len(output6Rules), output6RulesBefore+1)
|
|
}
|
|
|
|
// Verify the rules contain conntrack expressions
|
|
foundCtInPrerouting := false
|
|
foundCtInOutput := false
|
|
for _, e := range prerouting4Rules[0].Exprs {
|
|
if _, ok := e.(*expr.Ct); ok {
|
|
foundCtInPrerouting = true
|
|
break
|
|
}
|
|
}
|
|
for _, e := range output4Rules[0].Exprs {
|
|
if _, ok := e.(*expr.Ct); ok {
|
|
foundCtInOutput = true
|
|
break
|
|
}
|
|
}
|
|
if !foundCtInPrerouting {
|
|
t.Error("PREROUTING rule doesn't contain conntrack expression")
|
|
}
|
|
if !foundCtInOutput {
|
|
t.Error("OUTPUT rule doesn't contain conntrack expression")
|
|
}
|
|
|
|
// Delete connmark rules
|
|
if err := runner.DelConnmarkSaveRule(); err != nil {
|
|
t.Fatalf("DelConnmarkSaveRule() failed: %v", err)
|
|
}
|
|
|
|
// Verify rules were deleted
|
|
prerouting4After, output4After, _ = getMangleChains(nftables.TableFamilyIPv4)
|
|
if prerouting4After != nil {
|
|
rules, _ := conn.GetRules(prerouting4After.Table, prerouting4After)
|
|
if len(rules) != prerouting4RulesBefore {
|
|
t.Fatalf("IPv4 PREROUTING rules after delete: got %d, want %d", len(rules), prerouting4RulesBefore)
|
|
}
|
|
}
|
|
if output4After != nil {
|
|
rules, _ := conn.GetRules(output4After.Table, output4After)
|
|
if len(rules) != output4RulesBefore {
|
|
t.Fatalf("IPv4 OUTPUT rules after delete: got %d, want %d", len(rules), output4RulesBefore)
|
|
}
|
|
}
|
|
|
|
prerouting6After, output6After, _ = getMangleChains(nftables.TableFamilyIPv6)
|
|
if prerouting6After != nil {
|
|
rules, _ := conn.GetRules(prerouting6After.Table, prerouting6After)
|
|
if len(rules) != prerouting6RulesBefore {
|
|
t.Fatalf("IPv6 PREROUTING rules after delete: got %d, want %d", len(rules), prerouting6RulesBefore)
|
|
}
|
|
}
|
|
if output6After != nil {
|
|
rules, _ := conn.GetRules(output6After.Table, output6After)
|
|
if len(rules) != output6RulesBefore {
|
|
t.Fatalf("IPv6 OUTPUT rules after delete: got %d, want %d", len(rules), output6RulesBefore)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestMakeConnmarkRestoreExprs tests the nftables expressions for restoring
|
|
// marks from conntrack. This is a regression test that ensures the byte encoding
|
|
// doesn't change unexpectedly.
|
|
func TestMakeConnmarkRestoreExprs(t *testing.T) {
|
|
// Expected netlink bytes for the restore rule
|
|
// Generated by running makeConnmarkRestoreExprs() and capturing the output
|
|
want := [][]byte{
|
|
// batch begin
|
|
[]byte("\x00\x00\x00\x0a"),
|
|
// nft add table ip mangle
|
|
[]byte("\x02\x00\x00\x00\x0b\x00\x01\x00\x6d\x61\x6e\x67\x6c\x65\x00\x00\x08\x00\x02\x00\x00\x00\x00\x00"),
|
|
// nft add chain ip mangle PREROUTING { type filter hook prerouting priority mangle; }
|
|
[]byte("\x02\x00\x00\x00\x0b\x00\x01\x00\x6d\x61\x6e\x67\x6c\x65\x00\x00\x0f\x00\x03\x00\x50\x52\x45\x52\x4f\x55\x54\x49\x4e\x47\x00\x00\x14\x00\x04\x80\x08\x00\x01\x00\x00\x00\x00\x00\x08\x00\x02\x00\xff\xff\xff\x6a\x0b\x00\x07\x00\x66\x69\x6c\x74\x65\x72\x00\x00"),
|
|
// nft add rule ip mangle PREROUTING ct state established,related ct mark & 0xff0000 != 0 meta mark set ct mark & 0xff0000
|
|
[]byte("\x02\x00\x00\x00\x0b\x00\x01\x00\x6d\x61\x6e\x67\x6c\x65\x00\x00\x0f\x00\x02\x00\x50\x52\x45\x52\x4f\x55\x54\x49\x4e\x47\x00\x00\x1c\x01\x04\x80\x20\x00\x01\x80\x07\x00\x01\x00\x63\x74\x00\x00\x14\x00\x02\x80\x08\x00\x02\x00\x00\x00\x00\x00\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\x06\x00\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\x01\x0c\x00\x03\x80\x08\x00\x01\x00\x00\x00\x00\x00\x20\x00\x01\x80\x07\x00\x01\x00\x63\x74\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\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\x03\x00\x00\x00\x00\x01"),
|
|
// batch end
|
|
[]byte("\x00\x00\x00\x0a"),
|
|
}
|
|
|
|
testConn := newTestConn(t, want, nil)
|
|
table := testConn.AddTable(&nftables.Table{
|
|
Family: nftables.TableFamilyIPv4,
|
|
Name: "mangle",
|
|
})
|
|
chain := testConn.AddChain(&nftables.Chain{
|
|
Name: "PREROUTING",
|
|
Table: table,
|
|
Type: nftables.ChainTypeFilter,
|
|
Hooknum: nftables.ChainHookPrerouting,
|
|
Priority: nftables.ChainPriorityMangle,
|
|
})
|
|
testConn.InsertRule(&nftables.Rule{
|
|
Table: table,
|
|
Chain: chain,
|
|
Exprs: makeConnmarkRestoreExprs(),
|
|
})
|
|
if err := testConn.Flush(); err != nil {
|
|
t.Fatalf("Flush() failed: %v", err)
|
|
}
|
|
}
|
|
|
|
// TestMakeConnmarkSaveExprs tests the nftables expressions for saving marks
|
|
// to conntrack. This is a regression test that ensures the byte encoding
|
|
// doesn't change unexpectedly.
|
|
func TestMakeConnmarkSaveExprs(t *testing.T) {
|
|
// Expected netlink bytes for the save rule
|
|
// Generated by running makeConnmarkSaveExprs() and capturing the output
|
|
want := [][]byte{
|
|
// batch begin
|
|
[]byte("\x00\x00\x00\x0a"),
|
|
// nft add table ip mangle
|
|
[]byte("\x02\x00\x00\x00\x0b\x00\x01\x00\x6d\x61\x6e\x67\x6c\x65\x00\x00\x08\x00\x02\x00\x00\x00\x00\x00"),
|
|
// nft add chain ip mangle OUTPUT { type route hook output priority mangle; }
|
|
[]byte("\x02\x00\x00\x00\x0b\x00\x01\x00\x6d\x61\x6e\x67\x6c\x65\x00\x00\x0b\x00\x03\x00\x4f\x55\x54\x50\x55\x54\x00\x00\x14\x00\x04\x80\x08\x00\x01\x00\x00\x00\x00\x03\x08\x00\x02\x00\xff\xff\xff\x6a\x0a\x00\x07\x00\x72\x6f\x75\x74\x65\x00\x00\x00"),
|
|
// nft add rule ip mangle OUTPUT ct state new meta mark & 0xff0000 != 0 ct mark set meta mark & 0xff0000
|
|
[]byte("\x02\x00\x00\x00\x0b\x00\x01\x00\x6d\x61\x6e\x67\x6c\x65\x00\x00\x0b\x00\x02\x00\x4f\x55\x54\x50\x55\x54\x00\x00\xb0\x01\x04\x80\x20\x00\x01\x80\x07\x00\x01\x00\x63\x74\x00\x00\x14\x00\x02\x80\x08\x00\x02\x00\x00\x00\x00\x00\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\x08\x00\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\x01\x0c\x00\x03\x80\x08\x00\x01\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\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\x01\x0c\x00\x03\x80\x08\x00\x01\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\x00\xff\x00\x00\x0c\x00\x05\x80\x08\x00\x01\x00\x00\x00\x00\x00\x20\x00\x01\x80\x07\x00\x01\x00\x63\x74\x00\x00\x14\x00\x02\x80\x08\x00\x02\x00\x00\x00\x00\x03\x08\x00\x04\x00\x00\x00\x00\x01"),
|
|
// batch end
|
|
[]byte("\x00\x00\x00\x0a"),
|
|
}
|
|
|
|
testConn := newTestConn(t, want, nil)
|
|
table := testConn.AddTable(&nftables.Table{
|
|
Family: nftables.TableFamilyIPv4,
|
|
Name: "mangle",
|
|
})
|
|
chain := testConn.AddChain(&nftables.Chain{
|
|
Name: "OUTPUT",
|
|
Table: table,
|
|
Type: nftables.ChainTypeRoute,
|
|
Hooknum: nftables.ChainHookOutput,
|
|
Priority: nftables.ChainPriorityMangle,
|
|
})
|
|
testConn.InsertRule(&nftables.Rule{
|
|
Table: table,
|
|
Chain: chain,
|
|
Exprs: makeConnmarkSaveExprs(),
|
|
})
|
|
if err := testConn.Flush(); err != nil {
|
|
t.Fatalf("Flush() failed: %v", err)
|
|
}
|
|
}
|
|
|
|
// TestGetOrCreateChainNilHooknum verifies that getOrCreateChain returns a clear
|
|
// error when a ts- chain exists but has nil Hooknum/Priority, which happens when
|
|
// the kernel lacks nftables support (CONFIG_NF_TABLES).
|
|
func TestGetOrCreateChainNilHooknum(t *testing.T) {
|
|
conn := newSysConn(t)
|
|
|
|
table := conn.AddTable(&nftables.Table{
|
|
Family: nftables.TableFamilyIPv4,
|
|
Name: "ts-filter-test",
|
|
})
|
|
// Add a ts- chain without hooknum/priority (regular chain), simulating
|
|
// the broken state returned by a kernel without nftables support.
|
|
conn.AddChain(&nftables.Chain{
|
|
Name: "ts-input",
|
|
Table: table,
|
|
})
|
|
if err := conn.Flush(); err != nil {
|
|
t.Fatalf("Flush() failed: %v", err)
|
|
}
|
|
|
|
// Now try getOrCreateChain expecting a base chain with hooknum/priority.
|
|
_, err := getOrCreateChain(conn, chainInfo{
|
|
table: table,
|
|
name: "ts-input",
|
|
chainType: nftables.ChainTypeFilter,
|
|
chainHook: nftables.ChainHookInput,
|
|
chainPriority: nftables.ChainPriorityFilter,
|
|
})
|
|
if err == nil {
|
|
t.Fatal("expected error for chain with nil hooknum/priority, got nil")
|
|
}
|
|
if !strings.Contains(err.Error(), "nil hooknum") {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
}
|