mirror of
https://github.com/tailscale/tailscale.git
synced 2025-02-18 02:48:40 +00:00
![James Tucker](/assets/img/avatar_default.png)
Add an explicit accept rule for input to the tun interface, as a mirror to the explicit rule to accept output from the tun interface. The rule matches any packet in to our tun interface and accepts it, and the rule is positioned and prioritized such that it should be evaluated prior to conventional ufw/iptables/nft rules. Updates #391 Fixes #7332 Updates #9084 Signed-off-by: James Tucker <james@tailscale.com>
949 lines
43 KiB
Go
949 lines
43 KiB
Go
// 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 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)
|
|
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)
|
|
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 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)
|
|
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\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) != 6 {
|
|
t.Fatalf("len(chainsV4) = %d, want 6", len(chainsV4))
|
|
}
|
|
|
|
chainsV6, err := conn.ListChainsOfTableFamily(nftables.TableFamilyIPv6)
|
|
if err != nil {
|
|
t.Fatalf("list chains failed: %v", err)
|
|
}
|
|
|
|
if len(chainsV6) != 6 {
|
|
t.Fatalf("len(chainsV6) = %d, want 6", 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))
|
|
}
|
|
}
|
|
|
|
func TestNFTAddAndDelHookRule(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.AddHooks()
|
|
|
|
forwardChain, err := getChainFromTable(conn, runner.nft4.Filter, "FORWARD")
|
|
if err != nil {
|
|
t.Fatalf("failed to get forwardChain: %v", err)
|
|
}
|
|
|
|
forwardChainRules, err := conn.GetRules(forwardChain.Table, forwardChain)
|
|
if err != nil {
|
|
t.Fatalf("failed to get rules: %v", err)
|
|
}
|
|
|
|
if len(forwardChainRules) != 1 {
|
|
t.Fatalf("expected 1 rule in FORWARD chain, got %v", len(forwardChainRules))
|
|
}
|
|
|
|
inputChain, err := getChainFromTable(conn, runner.nft4.Filter, "INPUT")
|
|
if err != nil {
|
|
t.Fatalf("failed to get inputChain: %v", err)
|
|
}
|
|
|
|
inputChainRules, err := conn.GetRules(inputChain.Table, inputChain)
|
|
if err != nil {
|
|
t.Fatalf("failed to get rules: %v", err)
|
|
}
|
|
|
|
if len(inputChainRules) != 1 {
|
|
t.Fatalf("expected 1 rule in INPUT chain, got %v", len(inputChainRules))
|
|
}
|
|
|
|
postroutingChain, err := getChainFromTable(conn, runner.nft4.Nat, "POSTROUTING")
|
|
if err != nil {
|
|
t.Fatalf("failed to get postroutingChain: %v", err)
|
|
}
|
|
|
|
postroutingChainRules, err := conn.GetRules(postroutingChain.Table, postroutingChain)
|
|
if err != nil {
|
|
t.Fatalf("failed to get rules: %v", err)
|
|
}
|
|
|
|
if len(postroutingChainRules) != 1 {
|
|
t.Fatalf("expected 1 rule in POSTROUTING chain, got %v", len(postroutingChainRules))
|
|
}
|
|
|
|
runner.DelHooks(t.Logf)
|
|
|
|
forwardChainRules, err = conn.GetRules(forwardChain.Table, forwardChain)
|
|
if err != nil {
|
|
t.Fatalf("failed to get rules: %v", err)
|
|
}
|
|
|
|
if len(forwardChainRules) != 0 {
|
|
t.Fatalf("expected 0 rule in FORWARD chain, got %v", len(forwardChainRules))
|
|
}
|
|
|
|
inputChainRules, err = conn.GetRules(inputChain.Table, inputChain)
|
|
if err != nil {
|
|
t.Fatalf("failed to get rules: %v", err)
|
|
}
|
|
|
|
if len(inputChainRules) != 0 {
|
|
t.Fatalf("expected 0 rule in INPUT chain, got %v", len(inputChainRules))
|
|
}
|
|
|
|
postroutingChainRules, err = conn.GetRules(postroutingChain.Table, postroutingChain)
|
|
if err != nil {
|
|
t.Fatalf("failed to get rules: %v", err)
|
|
}
|
|
|
|
if len(postroutingChainRules) != 0 {
|
|
t.Fatalf("expected 0 rule in POSTROUTING chain, got %v", len(postroutingChainRules))
|
|
}
|
|
}
|