nftables/nftables_test.go

7985 lines
241 KiB
Go
Raw Permalink Normal View History

2018-05-24 23:47:16 -05:00
// Copyright 2018 Google LLC. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package nftables_test
import (
"bytes"
"errors"
"flag"
"fmt"
"net"
"os"
"reflect"
"strings"
"syscall"
2018-05-24 23:47:16 -05:00
"testing"
"time"
2018-05-24 23:47:16 -05:00
"github.com/google/nftables"
"github.com/google/nftables/binaryutil"
2018-05-24 23:47:16 -05:00
"github.com/google/nftables/expr"
"github.com/google/nftables/internal/nftest"
"github.com/google/nftables/xt"
2018-05-24 23:47:16 -05:00
"github.com/mdlayher/netlink"
"golang.org/x/sys/unix"
2018-05-24 23:47:16 -05:00
)
var enableSysTests = flag.Bool("run_system_tests", false, "Run tests that operate against the live kernel")
// 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()
}
// 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 ifname(n string) []byte {
b := make([]byte, 16)
copy(b, []byte(n+"\x00"))
return b
}
2018-05-24 23:47:16 -05:00
2020-01-01 09:50:27 -06:00
func TestRuleOperations(t *testing.T) {
// Create a new network namespace to test these operations,
// and tear down the namespace at test completion.
c, newNS := nftest.OpenSystemConn(t, *enableSysTests)
defer nftest.CleanupSystemConn(t, newNS)
2020-01-01 09:50:27 -06:00
// Clear all rules at the beginning + end of the test.
c.FlushRuleset()
defer c.FlushRuleset()
filter := c.AddTable(&nftables.Table{
Family: nftables.TableFamilyIPv4,
Name: "filter",
})
prerouting := c.AddChain(&nftables.Chain{
Name: "base-chain",
Table: filter,
Type: nftables.ChainTypeFilter,
Hooknum: nftables.ChainHookPrerouting,
Priority: nftables.ChainPriorityFilter,
})
c.AddRule(&nftables.Rule{
Table: filter,
Chain: prerouting,
Exprs: []expr.Any{
&expr.Verdict{
// [ immediate reg 0 drop ]
Kind: expr.VerdictDrop,
},
},
})
c.AddRule(&nftables.Rule{
Table: filter,
Chain: prerouting,
Exprs: []expr.Any{
&expr.Verdict{
// [ immediate reg 0 drop ]
Kind: expr.VerdictDrop,
},
},
})
c.InsertRule(&nftables.Rule{
Table: filter,
Chain: prerouting,
Exprs: []expr.Any{
&expr.Verdict{
// [ immediate reg 0 accept ]
Kind: expr.VerdictAccept,
},
},
})
c.InsertRule(&nftables.Rule{
Table: filter,
Chain: prerouting,
Exprs: []expr.Any{
&expr.Verdict{
// [ immediate reg 0 queue ]
Kind: expr.VerdictQueue,
},
},
})
if err := c.Flush(); err != nil {
t.Fatal(err)
}
rules, _ := c.GetRules(filter, prerouting)
2020-01-01 09:50:27 -06:00
want := []expr.VerdictKind{
expr.VerdictQueue,
expr.VerdictAccept,
expr.VerdictDrop,
expr.VerdictDrop,
}
for i, r := range rules {
rr, _ := r.Exprs[0].(*expr.Verdict)
if rr.Kind != want[i] {
t.Fatalf("bad verdict kind at %d", i)
}
}
c.ReplaceRule(&nftables.Rule{
Table: filter,
Chain: prerouting,
Handle: rules[2].Handle,
Exprs: []expr.Any{
&expr.Verdict{
// [ immediate reg 0 accept ]
Kind: expr.VerdictAccept,
},
},
})
c.AddRule(&nftables.Rule{
Table: filter,
Chain: prerouting,
Position: rules[2].Handle,
Exprs: []expr.Any{
&expr.Verdict{
// [ immediate reg 0 drop ]
Kind: expr.VerdictDrop,
},
},
})
c.InsertRule(&nftables.Rule{
Table: filter,
Chain: prerouting,
Position: rules[2].Handle,
Exprs: []expr.Any{
&expr.Verdict{
// [ immediate reg 0 queue ]
Kind: expr.VerdictQueue,
},
},
})
if err := c.Flush(); err != nil {
t.Fatal(err)
}
rules, _ = c.GetRules(filter, prerouting)
2020-01-01 09:50:27 -06:00
want = []expr.VerdictKind{
expr.VerdictQueue,
expr.VerdictAccept,
expr.VerdictQueue,
expr.VerdictAccept,
expr.VerdictDrop,
expr.VerdictDrop,
}
for i, r := range rules {
rr, _ := r.Exprs[0].(*expr.Verdict)
if rr.Kind != want[i] {
t.Fatalf("bad verdict kind at %d", i)
}
}
}
func TestConfigureNAT(t *testing.T) {
2018-05-24 23:47:16 -05:00
// The want byte sequences come from stracing nft(8), e.g.:
// strace -f -v -x -s 2048 -eraw=sendto nft add table ip nat
//
// The nft(8) command sequence was taken from:
// https://wiki.nftables.org/wiki-nftables/index.php/Performing_Network_Address_Translation_(NAT)
want := [][]byte{
// batch begin
[]byte("\x00\x00\x00\x0a"),
2018-06-14 01:26:52 -05:00
// nft flush ruleset
[]byte("\x00\x00\x00\x00"),
2018-05-24 23:47:16 -05:00
// nft add table ip nat
[]byte("\x02\x00\x00\x00\x08\x00\x01\x00\x6e\x61\x74\x00\x08\x00\x02\x00\x00\x00\x00\x00"),
// nft add chain nat prerouting '{' type nat hook prerouting priority 0 \; '}'
[]byte("\x02\x00\x00\x00\x08\x00\x01\x00\x6e\x61\x74\x00\x0f\x00\x03\x00\x70\x72\x65\x72\x6f\x75\x74\x69\x6e\x67\x00\x00\x14\x00\x04\x80\x08\x00\x01\x00\x00\x00\x00\x00\x08\x00\x02\x00\x00\x00\x00\x00\x08\x00\x07\x00\x6e\x61\x74\x00"),
// nft add chain nat postrouting '{' type nat hook postrouting priority 100 \; '}'
2018-05-24 23:47:16 -05:00
[]byte("\x02\x00\x00\x00\x08\x00\x01\x00\x6e\x61\x74\x00\x10\x00\x03\x00\x70\x6f\x73\x74\x72\x6f\x75\x74\x69\x6e\x67\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 nat postrouting oifname uplink0 masquerade
[]byte("\x02\x00\x00\x00\x08\x00\x01\x00\x6e\x61\x74\x00\x10\x00\x02\x00\x70\x6f\x73\x74\x72\x6f\x75\x74\x69\x6e\x67\x00\x74\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\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\x75\x70\x6c\x69\x6e\x6b\x30\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"),
// nft add rule nat prerouting iif uplink0 tcp dport 4070 dnat 192.168.23.2:4080
[]byte("\x02\x00\x00\x00\x08\x00\x01\x00\x6e\x61\x74\x00\x0f\x00\x02\x00\x70\x72\x65\x72\x6f\x75\x74\x69\x6e\x67\x00\x00\x98\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\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\x75\x70\x6c\x69\x6e\x6b\x30\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\x10\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\x05\x00\x01\x00\x06\x00\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\x02\x08\x00\x03\x00\x00\x00\x00\x02\x08\x00\x04\x00\x00\x00\x00\x02\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\x0f\xe6\x00\x00\x2c\x00\x01\x80\x0e\x00\x01\x00\x69\x6d\x6d\x65\x64\x69\x61\x74\x65\x00\x00\x00\x18\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x0c\x00\x02\x80\x08\x00\x01\x00\xc0\xa8\x17\x02\x2c\x00\x01\x80\x0e\x00\x01\x00\x69\x6d\x6d\x65\x64\x69\x61\x74\x65\x00\x00\x00\x18\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x02\x0c\x00\x02\x80\x06\x00\x01\x00\x0f\xf0\x00\x00\x30\x00\x01\x80\x08\x00\x01\x00\x6e\x61\x74\x00\x24\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x02\x08\x00\x03\x00\x00\x00\x00\x01\x08\x00\x05\x00\x00\x00\x00\x02"),
2018-06-14 15:26:38 -05:00
// nft add rule nat prerouting iifname uplink0 udp dport 4070-4090 dnat 192.168.23.2:4070-4090
[]byte("\x02\x00\x00\x00\x08\x00\x01\x00\x6e\x61\x74\x00\x0f\x00\x02\x00\x70\x72\x65\x72\x6f\x75\x74\x69\x6e\x67\x00\x00\xf8\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\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\x75\x70\x6c\x69\x6e\x6b\x30\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\x10\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\x05\x00\x01\x00\x11\x00\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\x02\x08\x00\x03\x00\x00\x00\x00\x02\x08\x00\x04\x00\x00\x00\x00\x02\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\x05\x0c\x00\x03\x80\x06\x00\x01\x00\x0f\xe6\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\x03\x0c\x00\x03\x80\x06\x00\x01\x00\x0f\xfa\x00\x00\x2c\x00\x01\x80\x0e\x00\x01\x00\x69\x6d\x6d\x65\x64\x69\x61\x74\x65\x00\x00\x00\x18\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x0c\x00\x02\x80\x08\x00\x01\x00\xc0\xa8\x17\x02\x2c\x00\x01\x80\x0e\x00\x01\x00\x69\x6d\x6d\x65\x64\x69\x61\x74\x65\x00\x00\x00\x18\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x02\x0c\x00\x02\x80\x06\x00\x01\x00\x0f\xe6\x00\x00\x2c\x00\x01\x80\x0e\x00\x01\x00\x69\x6d\x6d\x65\x64\x69\x61\x74\x65\x00\x00\x00\x18\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x03\x0c\x00\x02\x80\x06\x00\x01\x00\x0f\xfa\x00\x00\x38\x00\x01\x80\x08\x00\x01\x00\x6e\x61\x74\x00\x2c\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x02\x08\x00\x03\x00\x00\x00\x00\x01\x08\x00\x05\x00\x00\x00\x00\x02\x08\x00\x06\x00\x00\x00\x00\x03"),
2024-01-04 11:28:15 -06:00
// nft add rule nat prerouting ip daddr 10.0.0.0/24 dnat prefix to 20.0.0.0/24
[]byte("\x02\x00\x00\x00\x08\x00\x01\x00\x6e\x61\x74\x00\x0f\x00\x02\x00\x70\x72\x65\x72\x6f\x75\x74\x69\x6e\x67\x00\x00\x38\x01\x04\x80\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\x10\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\xff\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\x0a\x00\x00\x00\x2c\x00\x01\x80\x0e\x00\x01\x00\x69\x6d\x6d\x65\x64\x69\x61\x74\x65\x00\x00\x00\x18\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x0c\x00\x02\x80\x08\x00\x01\x00\x14\x00\x00\x00\x2c\x00\x01\x80\x0e\x00\x01\x00\x69\x6d\x6d\x65\x64\x69\x61\x74\x65\x00\x00\x00\x18\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x02\x0c\x00\x02\x80\x08\x00\x01\x00\x14\x00\x00\xff\x38\x00\x01\x80\x08\x00\x01\x00\x6e\x61\x74\x00\x2c\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x02\x08\x00\x03\x00\x00\x00\x00\x01\x08\x00\x04\x00\x00\x00\x00\x02\x08\x00\x07\x00\x00\x00\x00\x40"),
2018-05-24 23:47:16 -05:00
// batch end
[]byte("\x00\x00\x00\x0a"),
2018-05-24 23:47:16 -05:00
}
c, err := nftables.New(nftables.WithTestDial(
func(req []netlink.Message) ([]netlink.Message, error) {
2018-05-24 23:47:16 -05:00
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)))
2018-05-24 23:47:16 -05:00
}
want = want[1:]
}
return req, nil
}))
if err != nil {
t.Fatal(err)
2018-05-24 23:47:16 -05:00
}
2018-06-14 01:26:52 -05:00
c.FlushRuleset()
2018-05-24 23:47:16 -05:00
nat := c.AddTable(&nftables.Table{
Family: nftables.TableFamilyIPv4,
Name: "nat",
})
prerouting := c.AddChain(&nftables.Chain{
2018-05-24 23:47:16 -05:00
Name: "prerouting",
Hooknum: nftables.ChainHookPrerouting,
Priority: nftables.ChainPriorityFilter,
Table: nat,
Type: nftables.ChainTypeNAT,
})
postrouting := c.AddChain(&nftables.Chain{
Name: "postrouting",
Hooknum: nftables.ChainHookPostrouting,
Priority: nftables.ChainPriorityNATSource,
Table: nat,
Type: nftables.ChainTypeNAT,
})
c.AddRule(&nftables.Rule{
Table: nat,
Chain: postrouting,
Exprs: []expr.Any{
// meta load oifname => reg 1
&expr.Meta{Key: expr.MetaKeyOIFNAME, Register: 1},
// cmp eq reg 1 0x696c7075 0x00306b6e 0x00000000 0x00000000
2018-05-24 23:47:16 -05:00
&expr.Cmp{
Op: expr.CmpOpEq,
Register: 1,
Data: ifname("uplink0"),
2018-05-24 23:47:16 -05:00
},
// masq
&expr.Masq{},
},
})
c.AddRule(&nftables.Rule{
Table: nat,
Chain: prerouting,
Exprs: []expr.Any{
// [ meta load iifname => reg 1 ]
&expr.Meta{Key: expr.MetaKeyIIFNAME, Register: 1},
// [ cmp eq reg 1 0x696c7075 0x00306b6e 0x00000000 0x00000000 ]
&expr.Cmp{
Op: expr.CmpOpEq,
Register: 1,
Data: ifname("uplink0"),
},
// [ meta load l4proto => reg 1 ]
&expr.Meta{Key: expr.MetaKeyL4PROTO, Register: 1},
// [ cmp eq reg 1 0x00000006 ]
&expr.Cmp{
Op: expr.CmpOpEq,
Register: 1,
Data: []byte{unix.IPPROTO_TCP},
},
// [ payload load 2b @ transport header + 2 => reg 1 ]
&expr.Payload{
DestRegister: 1,
Base: expr.PayloadBaseTransportHeader,
Offset: 2, // TODO
Len: 2, // TODO
},
// [ cmp eq reg 1 0x0000e60f ]
&expr.Cmp{
Op: expr.CmpOpEq,
Register: 1,
Data: binaryutil.BigEndian.PutUint16(4070),
},
// [ immediate reg 1 0x0217a8c0 ]
&expr.Immediate{
Register: 1,
Data: net.ParseIP("192.168.23.2").To4(),
},
// [ immediate reg 2 0x0000f00f ]
&expr.Immediate{
Register: 2,
Data: binaryutil.BigEndian.PutUint16(4080),
},
// [ nat dnat ip addr_min reg 1 addr_max reg 0 proto_min reg 2 proto_max reg 0 ]
&expr.NAT{
Type: expr.NATTypeDestNAT,
Family: unix.NFPROTO_IPV4,
RegAddrMin: 1,
RegProtoMin: 2,
},
},
})
2018-06-14 15:26:38 -05:00
c.AddRule(&nftables.Rule{
Table: nat,
Chain: prerouting,
Exprs: []expr.Any{
// [ meta load iifname => reg 1 ]
&expr.Meta{Key: expr.MetaKeyIIFNAME, Register: 1},
// [ cmp eq reg 1 0x696c7075 0x00306b6e 0x00000000 0x00000000 ]
&expr.Cmp{
Op: expr.CmpOpEq,
Register: 1,
Data: ifname("uplink0"),
},
// [ meta load l4proto => reg 1 ]
&expr.Meta{Key: expr.MetaKeyL4PROTO, Register: 1},
// [ cmp eq reg 1 0x00000006 ]
&expr.Cmp{
Op: expr.CmpOpEq,
Register: 1,
Data: []byte{unix.IPPROTO_UDP},
},
// [ payload load 2b @ transport header + 2 => reg 1 ]
&expr.Payload{
DestRegister: 1,
Base: expr.PayloadBaseTransportHeader,
Offset: 2, // TODO
Len: 2, // TODO
},
// [ cmp gte reg 1 0x0000e60f ]
&expr.Cmp{
Op: expr.CmpOpGte,
Register: 1,
Data: binaryutil.BigEndian.PutUint16(4070),
},
// [ cmp lte reg 1 0x0000fa0f ]
&expr.Cmp{
Op: expr.CmpOpLte,
Register: 1,
Data: binaryutil.BigEndian.PutUint16(4090),
},
// [ immediate reg 1 0x0217a8c0 ]
&expr.Immediate{
Register: 1,
Data: net.ParseIP("192.168.23.2").To4(),
},
// [ immediate reg 2 0x0000f00f ]
&expr.Immediate{
Register: 2,
Data: binaryutil.BigEndian.PutUint16(4070),
},
// [ immediate reg 3 0x0000fa0f ]
&expr.Immediate{
Register: 3,
Data: binaryutil.BigEndian.PutUint16(4090),
},
// [ nat dnat ip addr_min reg 1 addr_max reg 0 proto_min reg 2 proto_max reg 3 ]
&expr.NAT{
Type: expr.NATTypeDestNAT,
Family: unix.NFPROTO_IPV4,
RegAddrMin: 1,
RegProtoMin: 2,
RegProtoMax: 3,
},
},
})
2024-01-04 11:28:15 -06:00
dstipmatch, dstcidrmatch, err := net.ParseCIDR("10.0.0.0/24")
if err != nil {
t.Fatal(err)
}
dnatfirstip, dnatlastip, err := nftables.NetFirstAndLastIP("20.0.0.0/24")
if err != nil {
t.Fatal(err)
}
c.AddRule(&nftables.Rule{
Table: nat,
Chain: prerouting,
Exprs: []expr.Any{
&expr.Payload{
DestRegister: 1,
Base: expr.PayloadBaseNetworkHeader,
Offset: 16, // destination addr offset
Len: 4,
},
&expr.Bitwise{
SourceRegister: 1,
DestRegister: 1,
Len: 4,
// By specifying Xor to 0x0,0x0,0x0,0x0 and Mask to the CIDR mask,
// the rule will match the CIDR of the IP (e.g in this case 10.0.0.0/24).
Xor: []byte{0x0, 0x0, 0x0, 0x0},
Mask: dstcidrmatch.Mask,
},
&expr.Cmp{
Op: expr.CmpOpEq,
Register: 1,
Data: dstipmatch.To4(),
},
&expr.Immediate{
Register: 1,
Data: dnatfirstip,
},
&expr.Immediate{
Register: 2,
Data: dnatlastip,
},
&expr.NAT{
Type: expr.NATTypeDestNAT,
RegAddrMin: 1,
RegAddrMax: 2,
Prefix: true,
Family: uint32(nftables.TableFamilyIPv4),
},
},
})
2018-05-24 23:47:16 -05:00
if err := c.Flush(); err != nil {
t.Fatal(err)
}
}
2018-06-23 14:12:14 -05:00
func TestConfigureNATSourceAddress(t *testing.T) {
// The want byte sequences come from stracing nft(8), e.g.:
// strace -f -v -x -s 2048 -eraw=sendto nft add table ip nat
//
// The nft(8) command sequence was taken from:
// https://wiki.nftables.org/wiki-nftables/index.php/Performing_Network_Address_Translation_(NAT)
want := [][]byte{
// batch begin
[]byte("\x00\x00\x00\x0a"),
// nft flush ruleset
[]byte("\x00\x00\x00\x00"),
// nft add table ip nat
[]byte("\x02\x00\x00\x00\x08\x00\x01\x00\x6e\x61\x74\x00\x08\x00\x02\x00\x00\x00\x00\x00"),
// nft add chain nat postrouting '{' type nat hook postrouting priority 100 \; '}'
[]byte("\x02\x00\x00\x00\x08\x00\x01\x00\x6e\x61\x74\x00\x10\x00\x03\x00\x70\x6f\x73\x74\x72\x6f\x75\x74\x69\x6e\x67\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 nat postrouting ip saddr 192.168.69.2 masquerade
[]byte("\x02\x00\x00\x00\x08\x00\x01\x00\x6e\x61\x74\x00\x10\x00\x02\x00\x70\x6f\x73\x74\x72\x6f\x75\x74\x69\x6e\x67\x00\x78\x00\x04\x80\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\x45\x02\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"),
}
c, 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)
}
c.FlushRuleset()
nat := c.AddTable(&nftables.Table{
Family: nftables.TableFamilyIPv4,
Name: "nat",
})
postrouting := c.AddChain(&nftables.Chain{
Name: "postrouting",
Hooknum: nftables.ChainHookPostrouting,
Priority: nftables.ChainPriorityNATSource,
Table: nat,
Type: nftables.ChainTypeNAT,
})
c.AddRule(&nftables.Rule{
Table: nat,
Chain: postrouting,
Exprs: []expr.Any{
// payload load 4b @ network header + 12 => reg 1
&expr.Payload{
DestRegister: 1,
Base: expr.PayloadBaseNetworkHeader,
Offset: 12,
Len: 4,
},
// cmp eq reg 1 0x0245a8c0
&expr.Cmp{
Op: expr.CmpOpEq,
Register: 1,
Data: net.ParseIP("192.168.69.2").To4(),
},
// masq
&expr.Masq{},
},
})
if err := c.Flush(); err != nil {
t.Fatal(err)
}
}
func TestMasqMarshalUnmarshal(t *testing.T) {
c, newNS := nftest.OpenSystemConn(t, *enableSysTests)
defer nftest.CleanupSystemConn(t, newNS)
c.FlushRuleset()
defer c.FlushRuleset()
filter := c.AddTable(&nftables.Table{
Family: nftables.TableFamilyINet,
Name: "filter",
})
postrouting := c.AddChain(&nftables.Chain{
Name: "postrouting",
Table: filter,
Type: nftables.ChainTypeNAT,
Hooknum: nftables.ChainHookPostrouting,
Priority: nftables.ChainPriorityFilter,
})
min := uint32(1)
max := uint32(3)
c.AddRule(&nftables.Rule{
Table: filter,
Chain: postrouting,
Exprs: []expr.Any{
&expr.Masq{
ToPorts: true,
RegProtoMin: min,
RegProtoMax: max,
},
},
})
if err := c.Flush(); err != nil {
t.Fatalf("c.Flush() failed: %v", err)
}
rules, err := c.GetRules(
&nftables.Table{
Family: nftables.TableFamilyINet,
Name: "filter",
},
&nftables.Chain{
Name: "postrouting",
},
)
if err != nil {
t.Fatalf("c.GetRules() failed: %v", err)
}
if got, want := len(rules), 1; got != want {
t.Fatalf("unexpected rule count: got %d, want %d", got, want)
}
rule := rules[0]
if got, want := len(rule.Exprs), 1; got != want {
t.Fatalf("unexpected number of exprs: got %d, want %d", got, want)
}
me, ok := rule.Exprs[0].(*expr.Masq)
if !ok {
t.Fatalf("unexpected expression type: got %T, want *expr.Masq", rule.Exprs[0])
}
if got, want := me.ToPorts, true; got != want {
t.Errorf("unexpected masq random flag: got %v, want %v", got, want)
}
if got, want := me.RegProtoMin, min; got != want {
t.Errorf("unexpected reg proto min: got %d, want %d", got, want)
}
if got, want := me.RegProtoMax, max; got != want {
t.Errorf("unexpected reg proto max: got %d, want %d", got, want)
}
}
func TestExprLogOptions(t *testing.T) {
c, newNS := nftest.OpenSystemConn(t, *enableSysTests)
defer nftest.CleanupSystemConn(t, newNS)
c.FlushRuleset()
defer c.FlushRuleset()
filter := c.AddTable(&nftables.Table{
Family: nftables.TableFamilyIPv4,
Name: "filter",
})
input := c.AddChain(&nftables.Chain{
Name: "input",
Table: filter,
Type: nftables.ChainTypeFilter,
Hooknum: nftables.ChainHookInput,
Priority: nftables.ChainPriorityFilter,
})
forward := c.AddChain(&nftables.Chain{
Name: "forward",
Table: filter,
Type: nftables.ChainTypeFilter,
Hooknum: nftables.ChainHookInput,
Priority: nftables.ChainPriorityFilter,
})
keyGQ := uint32((1 << unix.NFTA_LOG_GROUP) | (1 << unix.NFTA_LOG_QTHRESHOLD) | (1 << unix.NFTA_LOG_SNAPLEN))
c.AddRule(&nftables.Rule{
Table: filter,
Chain: input,
Exprs: []expr.Any{
&expr.Log{
Key: keyGQ,
QThreshold: uint16(20),
Group: uint16(1),
Snaplen: uint32(132),
},
},
})
keyPL := uint32((1 << unix.NFTA_LOG_PREFIX) | (1 << unix.NFTA_LOG_LEVEL) | (1 << unix.NFTA_LOG_FLAGS))
c.AddRule(&nftables.Rule{
Table: filter,
Chain: forward,
Exprs: []expr.Any{
&expr.Log{
Key: keyPL,
Data: []byte("LOG FORWARD"),
Level: expr.LogLevelDebug,
Flags: expr.LogFlagsTCPOpt | expr.LogFlagsIPOpt,
},
},
})
if err := c.Flush(); err != nil {
t.Errorf("c.Flush() failed: %v", err)
}
rules, err := c.GetRules(
&nftables.Table{
Family: nftables.TableFamilyIPv4,
Name: "filter",
},
&nftables.Chain{
Name: "input",
},
)
if err != nil {
t.Fatal(err)
}
if got, want := len(rules), 1; got != want {
t.Fatalf("unexpected number of rules: got %d, want %d", got, want)
}
rule := rules[0]
if got, want := len(rule.Exprs), 1; got != want {
t.Fatalf("unexpected number of exprs: got %d, want %d", got, want)
}
le, ok := rule.Exprs[0].(*expr.Log)
if !ok {
t.Fatalf("unexpected expression type: got %T, want *expr.Log", rule.Exprs[0])
}
if got, want := le.Key, keyGQ; got != want {
t.Fatalf("unexpected log key: got %d, want %d", got, want)
}
if got, want := le.Group, uint16(1); got != want {
t.Fatalf("unexpected group: got %d, want %d", got, want)
}
if got, want := le.QThreshold, uint16(20); got != want {
t.Fatalf("unexpected queue-threshold: got %d, want %d", got, want)
}
if got, want := le.Snaplen, uint32(132); got != want {
t.Fatalf("unexpected snaplen: got %d, want %d", got, want)
}
rules, err = c.GetRules(
&nftables.Table{
Family: nftables.TableFamilyIPv4,
Name: "filter",
},
&nftables.Chain{
Name: "forward",
},
)
if err != nil {
t.Fatal(err)
}
if got, want := len(rules), 1; got != want {
t.Fatalf("unexpected number of rules: got %d, want %d", got, want)
}
rule = rules[0]
if got, want := len(rule.Exprs), 1; got != want {
t.Fatalf("unexpected number of exprs: got %d, want %d", got, want)
}
le, ok = rule.Exprs[0].(*expr.Log)
if !ok {
t.Fatalf("unexpected expression type: got %T, want *expr.Log", rule.Exprs[0])
}
if got, want := le.Key, keyPL; got != want {
t.Fatalf("unexpected log key: got %d, want %d", got, want)
}
if got, want := string(le.Data), "LOG FORWARD"; got != want {
t.Fatalf("unexpected prefix data: got %s, want %s", got, want)
}
if got, want := le.Level, expr.LogLevelDebug; got != want {
t.Fatalf("unexpected log level: got %d, want %d", got, want)
}
if got, want := le.Flags, expr.LogFlagsTCPOpt|expr.LogFlagsIPOpt; got != want {
t.Fatalf("unexpected log flags: got %d, want %d", got, want)
}
}
func TestExprLogPrefix(t *testing.T) {
c, newNS := nftest.OpenSystemConn(t, *enableSysTests)
defer nftest.CleanupSystemConn(t, newNS)
c.FlushRuleset()
defer c.FlushRuleset()
filter := c.AddTable(&nftables.Table{
Family: nftables.TableFamilyIPv4,
Name: "filter",
})
input := c.AddChain(&nftables.Chain{
Name: "input",
Table: filter,
Type: nftables.ChainTypeFilter,
Hooknum: nftables.ChainHookInput,
Priority: nftables.ChainPriorityFilter,
})
c.AddRule(&nftables.Rule{
Table: filter,
Chain: input,
Exprs: []expr.Any{
&expr.Log{
Key: 1 << unix.NFTA_LOG_PREFIX,
Data: []byte("LOG INPUT"),
},
},
})
if err := c.Flush(); err != nil {
t.Errorf("c.Flush() failed: %v", err)
}
rules, err := c.GetRules(
&nftables.Table{
Family: nftables.TableFamilyIPv4,
Name: "filter",
},
&nftables.Chain{
Name: "input",
},
)
if err != nil {
t.Fatal(err)
}
if got, want := len(rules), 1; got != want {
t.Fatalf("unexpected number of rules: got %d, want %d", got, want)
}
if got, want := len(rules[0].Exprs), 1; got != want {
t.Fatalf("unexpected number of exprs: got %d, want %d", got, want)
}
logExpr, ok := rules[0].Exprs[0].(*expr.Log)
if !ok {
t.Fatalf("Exprs[0] is type %T, want *expr.Log", rules[0].Exprs[0])
}
// nftables defaults to warn log level when no level is specified and group is not defined
// see https://wiki.nftables.org/wiki-nftables/index.php/Logging_traffic
if got, want := logExpr.Key, uint32((1<<unix.NFTA_LOG_PREFIX)|(1<<unix.NFTA_LOG_LEVEL)); got != want {
t.Fatalf("unexpected *expr.Log key: got %d, want %d", got, want)
}
if got, want := string(logExpr.Data), "LOG INPUT"; got != want {
t.Fatalf("unexpected *expr.Log data: got %s, want %s", got, want)
}
if got, want := logExpr.Level, expr.LogLevelWarning; got != want {
t.Fatalf("unexpected *expr.Log level: got %d, want %d", got, want)
}
}
func TestGetRules(t *testing.T) {
2018-06-23 14:12:14 -05:00
// The want byte sequences come from stracing nft(8), e.g.:
// strace -f -v -x -s 2048 -eraw=sendto nft list chain ip filter forward
want := [][]byte{
{0x2, 0x0, 0x0, 0x0, 0xb, 0x0, 0x1, 0x0, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x0, 0x0, 0xa, 0x0, 0x2, 0x0, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x0, 0x0, 0x0},
2018-06-23 14:12:14 -05:00
}
// The reply messages come from adding log.Printf("msgs: %#v", msgs) to
// (*github.com/mdlayher/netlink/Conn).receive
reply := [][]netlink.Message{
nil,
{{Header: netlink.Header{Length: 0x68, Type: 0xa06, Flags: 0x802, Sequence: 0x9acb0443, PID: 0xba38ef3c}, Data: []uint8{0x2, 0x0, 0x0, 0xc, 0xb, 0x0, 0x1, 0x0, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x0, 0x0, 0xc, 0x0, 0x2, 0x0, 0x66, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x0, 0xc, 0x0, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x30, 0x0, 0x4, 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, 0x6d, 0x92, 0x20, 0x20, 0xc, 0x0, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xa, 0x48, 0xd9}}},
{{Header: netlink.Header{Length: 0x14, Type: 0x3, Flags: 0x2, Sequence: 0x9acb0443, PID: 0xba38ef3c}, Data: []uint8{0x0, 0x0, 0x0, 0x0}}},
2018-06-23 14:12:14 -05:00
}
c, err := nftables.New(nftables.WithTestDial(
func(req []netlink.Message) ([]netlink.Message, error) {
2018-06-23 14:12:14 -05:00
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)))
2018-06-23 14:12:14 -05:00
}
want = want[1:]
}
rep := reply[0]
reply = reply[1:]
return rep, nil
}))
if err != nil {
t.Fatal(err)
2018-06-23 14:12:14 -05:00
}
rules, err := c.GetRules(
2018-06-23 14:12:14 -05:00
&nftables.Table{
Family: nftables.TableFamilyIPv4,
Name: "filter",
},
&nftables.Chain{
Name: "input",
},
)
if err != nil {
t.Fatal(err)
}
if got, want := len(rules), 1; got != want {