mirror of
https://github.com/tailscale/tailscale.git
synced 2026-02-12 00:41:57 -05:00
This file was never truly necessary and has never actually been used in the history of Tailscale's open source releases. A Brief History of AUTHORS files --- The AUTHORS file was a pattern developed at Google, originally for Chromium, then adopted by Go and a bunch of other projects. The problem was that Chromium originally had a copyright line only recognizing Google as the copyright holder. Because Google (and most open source projects) do not require copyright assignemnt for contributions, each contributor maintains their copyright. Some large corporate contributors then tried to add their own name to the copyright line in the LICENSE file or in file headers. This quickly becomes unwieldy, and puts a tremendous burden on anyone building on top of Chromium, since the license requires that they keep all copyright lines intact. The compromise was to create an AUTHORS file that would list all of the copyright holders. The LICENSE file and source file headers would then include that list by reference, listing the copyright holder as "The Chromium Authors". This also become cumbersome to simply keep the file up to date with a high rate of new contributors. Plus it's not always obvious who the copyright holder is. Sometimes it is the individual making the contribution, but many times it may be their employer. There is no way for the proejct maintainer to know. Eventually, Google changed their policy to no longer recommend trying to keep the AUTHORS file up to date proactively, and instead to only add to it when requested: https://opensource.google/docs/releasing/authors. They are also clear that: > Adding contributors to the AUTHORS file is entirely within the > project's discretion and has no implications for copyright ownership. It was primarily added to appease a small number of large contributors that insisted that they be recognized as copyright holders (which was entirely their right to do). But it's not truly necessary, and not even the most accurate way of identifying contributors and/or copyright holders. In practice, we've never added anyone to our AUTHORS file. It only lists Tailscale, so it's not really serving any purpose. It also causes confusion because Tailscalars put the "Tailscale Inc & AUTHORS" header in other open source repos which don't actually have an AUTHORS file, so it's ambiguous what that means. Instead, we just acknowledge that the contributors to Tailscale (whoever they are) are copyright holders for their individual contributions. We also have the benefit of using the DCO (developercertificate.org) which provides some additional certification of their right to make the contribution. The source file changes were purely mechanical with: git ls-files | xargs sed -i -e 's/\(Tailscale Inc &\) AUTHORS/\1 contributors/g' Updates #cleanup Change-Id: Ia101a4a3005adb9118051b3416f5a64a4a45987d Signed-off-by: Will Norris <will@tailscale.com>
1073 lines
50 KiB
Go
1073 lines
50 KiB
Go
// Copyright (c) Tailscale Inc & contributors
|
|
// 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)
|
|
}
|