mirror of
				https://github.com/tailscale/tailscale.git
				synced 2025-10-31 08:11:32 +01:00 
			
		
		
		
	* util/linuxfw: fix delete snat rule This pr is fixing the bug that in nftables mode setting snat-subnet-routes=false doesn't delete the masq rule in nat table. Updates #15661 Signed-off-by: Kevin Liang <kevinliang@tailscale.com> * change index arithmetic in test to chunk Signed-off-by: Kevin Liang <kevinliang@tailscale.com> * reuse rule creation function in rule delete Signed-off-by: Kevin Liang <kevinliang@tailscale.com> * add test for deleting the masq rule Signed-off-by: Kevin Liang <kevinliang@tailscale.com> --------- Signed-off-by: Kevin Liang <kevinliang@tailscale.com>
		
			
				
	
	
		
			1073 lines
		
	
	
		
			50 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			1073 lines
		
	
	
		
			50 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // Copyright (c) Tailscale Inc & AUTHORS
 | |
| // SPDX-License-Identifier: BSD-3-Clause
 | |
| 
 | |
| //go:build linux
 | |
| 
 | |
| package linuxfw
 | |
| 
 | |
| import (
 | |
| 	"bytes"
 | |
| 	"errors"
 | |
| 	"fmt"
 | |
| 	"net/netip"
 | |
| 	"os"
 | |
| 	"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()
 | |
| 	if os.Geteuid() != 0 {
 | |
| 		t.Skip(t.Name(), " requires privileges to create a namespace in order to run")
 | |
| 		return nil
 | |
| 	}
 | |
| 
 | |
| 	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: "not using any firewall, both available",
 | |
| 			det:  &testFWDetector{},
 | |
| 			want: FirewallModeNfTables,
 | |
| 		},
 | |
| 		{
 | |
| 			name: "not using any firewall, iptables available only",
 | |
| 			det:  &testFWDetector{iptRuleCount: 1, nftErr: errors.New("nft error")},
 | |
| 			want: FirewallModeIPTables,
 | |
| 		},
 | |
| 		{
 | |
| 			name: "not using any firewall, nftables available 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 := []byte(fmt.Sprintf("dst:%s,src:%s", dst.String(), src.String()))
 | |
| 	wantsRule := snatRule(chain.Table, chain, src, dst, meta)
 | |
| 	checkRule(t, wantsRule, runner.conn)
 | |
| }
 |