// 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" "tailscale.com/net/tsaddr" ) // 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() } 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) *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{ // 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) 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) 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) 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) 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) 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) 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) 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{ // 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\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) 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{ // 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) 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() 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 newFakeNftablesRunner(t *testing.T, conn *nftables.Conn) *nftablesRunner { nft4 := &nftable{Proto: nftables.TableFamilyIPv4} nft6 := &nftable{Proto: 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 privileges to create a namespace in order to run") return } conn := newSysConn(t) 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 := 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 } func TestNFTAddAndDelNetfilterBase(t *testing.T) { if os.Geteuid() != 0 { t.Skip(t.Name(), " requires privileges to create a namespace in order to run") return } conn := newSysConn(t) 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, 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) { if os.Geteuid() != 0 { t.Skip(t.Name(), " requires privileges to create a namespace in order to run") return } conn := newSysConn(t) 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)) } }