Compare commits

...

5 Commits

Author SHA1 Message Date
Antonio Ojea d68771344c
Merge 1e48c1007e into ba5b671e14 2025-09-15 16:20:15 +02:00
Nick Garlis ba5b671e14
Add GetRuleByHandle, ResetRule & ResetRules methods (#326)
This commit introduces the following methods:

  - GetRuleByHandle
  - ResetRule
  - ResetRules

It also refactors GetRules and the deprecated GetRule methods to share
a common getRules implementation.
2025-09-12 10:09:30 +02:00
Nikita Vorontsov 1148f1a84f
add DestroyTable and SetDestroyElements (#322)
These methods are like their DeleteTable and SetDeleteElements counterparts, but they do not return an error if the specified table/set does not exist.
2025-09-02 14:08:18 +02:00
Nick Garlis 3efc75f481
Add GetGen method to retrieve current generation ID (#325)
Add GetGen method to retrieve current generation ID

nftables uses generation IDs (gen IDs) for optimistic concurrency
control. This commit adds a GetGen method to expose current gen ID so
that users can retrieve it explicitly.

Typical usage:
  1. Call GetGen to retrieve current gen ID.
  2. Read the the current state.
  3. Send the batch along with the gen ID by calling Flush.

If the state changes before the flush, the kernel will reject the
batch, preventing stale writes.

- https://wiki.nftables.org/wiki-nftables/index.php/Portal:DeveloperDocs/nftables_internals#Batched_handlers
- https://docs.kernel.org/networking/netlink_spec/nftables.html#getgen
- 3957a57201/net/netfilter/nfnetlink.c (L424)
2025-09-02 14:05:05 +02:00
Antonio Ojea 1e48c1007e nf2go: convert nftables rules to golang code
One of the biggest barriers to adopt the netlink format for nftables is
the complexity of writing bytecode.

This commits adds a tool that allows to take an nftables dump and
generate the corresponding golang code and validating that the generated
code produces the exact same output.

Change-Id: I491b35e0d8062de33c67091dd4126d843b231838
Signed-off-by: Antonio Ojea <aojea@google.com>
2025-02-03 15:16:57 +00:00
10 changed files with 1413 additions and 19 deletions

44
conn.go
View File

@ -233,6 +233,19 @@ func (cc *Conn) CloseLasting() error {
// Flush sends all buffered commands in a single batch to nftables. // Flush sends all buffered commands in a single batch to nftables.
func (cc *Conn) Flush() error { func (cc *Conn) Flush() error {
return cc.flush(0)
}
// FlushWithGenID sends all buffered commands in a single batch to nftables
// along with the provided gen ID. If the ruleset has changed since the gen ID
// was retrieved, an ERESTART error will be returned.
func (cc *Conn) FlushWithGenID(genID uint32) error {
return cc.flush(genID)
}
// flush sends all buffered commands in a single batch to nftables. If genID is
// non-zero, it will be included in the batch messages.
func (cc *Conn) flush(genID uint32) error {
cc.mu.Lock() cc.mu.Lock()
defer func() { defer func() {
cc.messages = nil cc.messages = nil
@ -259,7 +272,12 @@ func (cc *Conn) Flush() error {
return err return err
} }
messages, err := conn.SendMessages(batch(cc.messages)) batch, err := batch(cc.messages, genID)
if err != nil {
return err
}
messages, err := conn.SendMessages(batch)
if err != nil { if err != nil {
return fmt.Errorf("SendMessages: %w", err) return fmt.Errorf("SendMessages: %w", err)
} }
@ -388,14 +406,30 @@ func (cc *Conn) marshalExpr(fam byte, e expr.Any) []byte {
return b return b
} }
func batch(messages []netlinkMessage) []netlink.Message { // batch wraps the given messages in a batch begin and end message, and returns
// the resulting slice of netlink messages. If the genID is non-zero, it will be
// included in both batch messages.
func batch(messages []netlinkMessage, genID uint32) ([]netlink.Message, error) {
batch := make([]netlink.Message, len(messages)+2) batch := make([]netlink.Message, len(messages)+2)
data := extraHeader(0, unix.NFNL_SUBSYS_NFTABLES)
if genID > 0 {
attr, err := netlink.MarshalAttributes([]netlink.Attribute{
{Type: unix.NFNL_BATCH_GENID, Data: binaryutil.BigEndian.PutUint32(genID)},
})
if err != nil {
return nil, err
}
data = append(data, attr...)
}
batch[0] = netlink.Message{ batch[0] = netlink.Message{
Header: netlink.Header{ Header: netlink.Header{
Type: netlink.HeaderType(unix.NFNL_MSG_BATCH_BEGIN), Type: netlink.HeaderType(unix.NFNL_MSG_BATCH_BEGIN),
Flags: netlink.Request, Flags: netlink.Request,
}, },
Data: extraHeader(0, unix.NFNL_SUBSYS_NFTABLES), Data: data,
} }
for i, msg := range messages { for i, msg := range messages {
@ -410,10 +444,10 @@ func batch(messages []netlinkMessage) []netlink.Message {
Type: netlink.HeaderType(unix.NFNL_MSG_BATCH_END), Type: netlink.HeaderType(unix.NFNL_MSG_BATCH_END),
Flags: netlink.Request, Flags: netlink.Request,
}, },
Data: extraHeader(0, unix.NFNL_SUBSYS_NFTABLES), Data: data,
} }
return batch return batch, nil
} }
// allocateTransactionID allocates an identifier which is only valid in the // allocateTransactionID allocates an identifier which is only valid in the

50
gen.go
View File

@ -8,15 +8,18 @@ import (
"golang.org/x/sys/unix" "golang.org/x/sys/unix"
) )
type GenMsg struct { type Gen struct {
ID uint32 ID uint32
ProcPID uint32 ProcPID uint32
ProcComm string // [16]byte - max 16bytes - kernel TASK_COMM_LEN ProcComm string // [16]byte - max 16bytes - kernel TASK_COMM_LEN
} }
// Deprecated: GenMsg is an inconsistent old name for Gen. Prefer using Gen.
type GenMsg = Gen
const genHeaderType = netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_NEWGEN) const genHeaderType = netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_NEWGEN)
func genFromMsg(msg netlink.Message) (*GenMsg, error) { func genFromMsg(msg netlink.Message) (*Gen, error) {
if got, want := msg.Header.Type, genHeaderType; got != want { if got, want := msg.Header.Type, genHeaderType; got != want {
return nil, fmt.Errorf("unexpected header type: got %v, want %v", got, want) return nil, fmt.Errorf("unexpected header type: got %v, want %v", got, want)
} }
@ -26,7 +29,7 @@ func genFromMsg(msg netlink.Message) (*GenMsg, error) {
} }
ad.ByteOrder = binary.BigEndian ad.ByteOrder = binary.BigEndian
msgOut := &GenMsg{} msgOut := &Gen{}
for ad.Next() { for ad.Next() {
switch ad.Type() { switch ad.Type() {
case unix.NFTA_GEN_ID: case unix.NFTA_GEN_ID:
@ -36,7 +39,7 @@ func genFromMsg(msg netlink.Message) (*GenMsg, error) {
case unix.NFTA_GEN_PROC_NAME: case unix.NFTA_GEN_PROC_NAME:
msgOut.ProcComm = ad.String() msgOut.ProcComm = ad.String()
default: default:
return nil, fmt.Errorf("Unknown attribute: %d %v\n", ad.Type(), ad.Bytes()) return nil, fmt.Errorf("unknown attribute: %d, %v", ad.Type(), ad.Bytes())
} }
} }
if err := ad.Err(); err != nil { if err := ad.Err(); err != nil {
@ -44,3 +47,42 @@ func genFromMsg(msg netlink.Message) (*GenMsg, error) {
} }
return msgOut, nil return msgOut, nil
} }
// GetGen retrieves the current nftables generation ID together with the name
// and ID of the process that last modified the ruleset.
// https://docs.kernel.org/networking/netlink_spec/nftables.html#getgen
func (cc *Conn) GetGen() (*Gen, error) {
conn, closer, err := cc.netlinkConn()
if err != nil {
return nil, err
}
defer func() { _ = closer() }()
data, err := netlink.MarshalAttributes([]netlink.Attribute{
{Type: unix.NFTA_GEN_ID},
})
if err != nil {
return nil, err
}
message := netlink.Message{
Header: netlink.Header{
Type: netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_GETGEN),
Flags: netlink.Request | netlink.Acknowledge,
},
Data: append(extraHeader(0, 0), data...),
}
if _, err := conn.SendMessages([]netlink.Message{message}); err != nil {
return nil, fmt.Errorf("SendMessages: %v", err)
}
reply, err := receiveAckAware(conn, message.Header.Flags)
if err != nil {
return nil, fmt.Errorf("receiveAckAware: %v", err)
}
if len(reply) == 0 {
return nil, fmt.Errorf("receiveAckAware: no reply")
}
return genFromMsg(reply[0])
}

299
internal/nf2go/main.go Normal file
View File

@ -0,0 +1,299 @@
package main
import (
"bytes"
"context"
"fmt"
"io"
"io/ioutil"
"log"
"os"
"os/exec"
"path/filepath"
"regexp"
"runtime"
"strings"
"time"
"github.com/google/go-cmp/cmp"
"github.com/google/nftables"
"github.com/vishvananda/netns"
)
func main() {
args := os.Args[1:]
if len(args) != 1 {
log.Fatalf("need to specify the file to read the \"nft list ruleset\" dump")
}
filename := args[0]
runtime.LockOSThread()
defer runtime.UnlockOSThread()
// Create a new network namespace
ns, err := netns.New()
if err != nil {
log.Fatalf("netns.New() failed: %v", err)
}
n, err := nftables.New(nftables.WithNetNSFd(int(ns)))
if err != nil {
log.Fatalf("nftables.New() failed: %v", err)
}
scriptOutput, err := applyNFTRuleset(filename)
if err != nil {
log.Fatalf("Failed to apply nftables script: %v\noutput:%s", err, scriptOutput)
}
var buf bytes.Buffer
// Helper function to print to the file
pf := func(format string, a ...interface{}) {
_, err := fmt.Fprintf(&buf, format, a...)
if err != nil {
log.Fatal(err)
}
}
pf("// Code generated by nft2go. DO NOT EDIT.\n")
pf("package main\n\n")
pf("import (\n")
pf("\t\"fmt\"\n")
pf("\t\"log\"\n")
pf("\t\"github.com/google/nftables\"\n")
pf("\t\"github.com/google/nftables/expr\"\n")
pf(")\n\n")
pf("func main() {\n")
pf("\tn, err:= nftables.New()\n")
pf("\tif err!= nil {\n")
pf("\t\tlog.Fatal(err)\n")
pf("\t}\n\n")
pf("\n")
pf("\tvar expressions []expr.Any\n")
pf("\tvar chain *nftables.Chain\n")
pf("\tvar table *nftables.Table\n")
tables, err := n.ListTables()
if err != nil {
log.Fatalf("ListTables failed: %v", err)
}
chains, err := n.ListChains()
if err != nil {
log.Fatal(err)
}
for _, table := range tables {
log.Printf("processing table: %s", table.Name)
pf("\ttable = n.AddTable(&nftables.Table{Family: %s,Name: \"%s\"})\n", TableFamilyString(table.Family), table.Name)
for _, chain := range chains {
if chain.Table.Name != table.Name {
continue
}
sets, err := n.GetSets(table)
if err != nil {
log.Fatal(err)
}
for _, set := range sets {
// TODO datatype and the other options
pf("\tn.AddSet(&nftables.Set{\n")
pf("\t\tTable: table,\n")
pf("\t\tName: \"%s\",\n", set.Name)
pf("\t}, nil)\n")
}
pf("\tchain = n.AddChain(&nftables.Chain{Name: \"%s\", Table: table, Type: %s, Hooknum: %s, Priority: %s})\n",
chain.Name, ChainTypeString(chain.Type), ChainHookRef(chain.Hooknum), ChainPrioRef(chain.Priority))
rules, err := n.GetRules(table, chain)
if err != nil {
log.Fatal(err)
}
for _, rule := range rules {
pf("\texpressions = []expr.Any{\n")
for _, exp := range rule.Exprs {
pf("\t\t%#v,\n", exp)
}
pf("\t\t}\n")
pf("\tn.AddRule(&nftables.Rule{\n")
pf("\t\tTable: table,\n")
pf("\t\tChain: chain,\n")
pf("\t\tExprs: expressions,\n")
pf("\t})\n")
}
}
}
pf("\n\tif err:= n.Flush(); err!= nil {\n")
pf("\t\tlog.Fatal(err)\n")
pf("\t}\n\n")
pf("\tfmt.Println(\"nft ruleset applied.\")\n")
pf("}\n")
// Program nftables using your Go code
if err := flushNFTRuleset(); err != nil {
log.Fatalf("Failed to flush nftables ruleset: %v", err)
}
// Create the output file
// Create a temporary directory
tempDir, err := ioutil.TempDir("", "nftables_gen")
if err != nil {
log.Fatal(err)
}
defer os.RemoveAll(tempDir) // Clean up the temporary directory
// Create the temporary Go file
tempGoFile := filepath.Join(tempDir, "nftables_recreate.go")
f, err := os.Create(tempGoFile)
if err != nil {
log.Fatal(err)
}
defer f.Close()
mw := io.MultiWriter(f, os.Stdout)
buf.WriteTo(mw)
// Format the generated code
log.Printf("formating file: %s", tempGoFile)
cmd := exec.Command("gofmt", "-w", "-s", tempGoFile)
output, err := cmd.CombinedOutput()
if err != nil {
log.Fatalf("gofmt error: %v\nOutput: %s", err, output)
}
// Run the generated code
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
log.Printf("executing file: %s", tempGoFile)
cmd = exec.CommandContext(ctx, "go", "run", tempGoFile)
output, err = cmd.CombinedOutput()
if err != nil {
log.Fatalf("Execution error: %v\nOutput: %s", err, output)
}
// Retrieve nftables state using nft
log.Printf("obtain current ruleset: %s", tempGoFile)
actualOutput, err := listNFTRuleset()
if err != nil {
log.Fatalf("Failed to list nftables ruleset: %v\noutput:%s", err, actualOutput)
}
expectedOutput, err := os.ReadFile(filename)
if err != nil {
log.Fatalf("Failed to list nftables ruleset: %v\noutput:%s", err, actualOutput)
}
if !compareMultilineStringsIgnoreIndentation(string(expectedOutput), actualOutput) {
log.Printf("Expected output:\n%s", string(expectedOutput))
log.Printf("Actual output:\n%s", actualOutput)
log.Fatalf("nftables ruleset mismatch:\n%s", cmp.Diff(string(expectedOutput), actualOutput))
}
if err := flushNFTRuleset(); err != nil {
log.Fatalf("Failed to flush nftables ruleset: %v", err)
}
}
func applyNFTRuleset(scriptPath string) (string, error) {
cmd := exec.Command("nft", "--debug=netlink", "-f", scriptPath)
out, err := cmd.CombinedOutput()
if err != nil {
return string(out), err
}
return strings.TrimSpace(string(out)), nil
}
func listNFTRuleset() (string, error) {
cmd := exec.Command("nft", "list", "ruleset")
out, err := cmd.CombinedOutput()
if err != nil {
return string(out), err
}
return strings.TrimSpace(string(out)), nil
}
func flushNFTRuleset() error {
cmd := exec.Command("nft", "flush", "ruleset")
return cmd.Run()
}
func ChainHookRef(hookNum *nftables.ChainHook) string {
i := uint32(0)
if hookNum != nil {
i = uint32(*hookNum)
}
switch i {
case 0:
return "nftables.ChainHookPrerouting"
case 1:
return "nftables.ChainHookInput"
case 2:
return "nftables.ChainHookForward"
case 3:
return "nftables.ChainHookOutput"
case 4:
return "nftables.ChainHookPostrouting"
case 5:
return "nftables.ChainHookIngress"
case 6:
return "nftables.ChainHookEgress"
}
return ""
}
func ChainPrioRef(priority *nftables.ChainPriority) string {
i := int32(0)
if priority != nil {
i = int32(*priority)
}
return fmt.Sprintf("nftables.ChainPriorityRef(%d)", i)
}
func ChainTypeString(chaintype nftables.ChainType) string {
switch chaintype {
case nftables.ChainTypeFilter:
return "nftables.ChainTypeFilter"
case nftables.ChainTypeRoute:
return "nftables.ChainTypeRoute"
case nftables.ChainTypeNAT:
return "nftables.ChainTypeNAT"
default:
return "nftables.ChainTypeFilter"
}
}
func TableFamilyString(family nftables.TableFamily) string {
switch family {
case nftables.TableFamilyUnspecified:
return "nftables.TableFamilyUnspecified"
case nftables.TableFamilyINet:
return "nftables.TableFamilyINet"
case nftables.TableFamilyIPv4:
return "nftables.TableFamilyIPv4"
case nftables.TableFamilyIPv6:
return "nftables.TableFamilyIPv6"
case nftables.TableFamilyARP:
return "nftables.TableFamilyARP"
case nftables.TableFamilyNetdev:
return "nftables.TableFamilyNetdev"
case nftables.TableFamilyBridge:
return "nftables.TableFamilyBridge"
default:
return "nftables.TableFamilyIPv4"
}
}
func compareMultilineStringsIgnoreIndentation(str1, str2 string) bool {
// Remove all indentation from both strings
re := regexp.MustCompile(`(?m)^\s+`)
str1 = re.ReplaceAllString(str1, "")
str2 = re.ReplaceAllString(str2, "")
return str1 == str2
}

550
internal/nf2go/rules.txt Normal file
View File

@ -0,0 +1,550 @@
table ip nat {
chain PREROUTING {
type nat hook prerouting priority dstnat; policy accept;
counter packets 769 bytes 46239 jump KUBE-SERVICES
ip daddr 192.168.8.1 counter packets 0 bytes 0 jump DOCKER_OUTPUT
}
chain INPUT {
type nat hook input priority 100; policy accept;
}
chain OUTPUT {
type nat hook output priority -100; policy accept;
counter packets 245348 bytes 14721139 jump KUBE-SERVICES
ip daddr 192.168.8.1 counter packets 122 bytes 9151 jump DOCKER_OUTPUT
}
chain POSTROUTING {
type nat hook postrouting priority srcnat; policy accept;
counter packets 246110 bytes 14766910 jump KUBE-POSTROUTING
ip daddr 192.168.8.1 counter packets 0 bytes 0 jump DOCKER_POSTROUTING
}
chain DOCKER_OUTPUT {
ip daddr 192.168.8.1 tcp dport 53 counter packets 0 bytes 0 dnat to 127.0.0.11:36405
ip daddr 192.168.8.1 udp dport 53 counter packets 122 bytes 9151 dnat to 127.0.0.11:39066
}
chain DOCKER_POSTROUTING {
ip saddr 127.0.0.11 tcp sport 36405 counter packets 0 bytes 0 snat to 192.168.8.1:53
ip saddr 127.0.0.11 udp sport 39066 counter packets 0 bytes 0 snat to 192.168.8.1:53
}
chain KUBE-KUBELET-CANARY {
}
chain KUBE-PROXY-CANARY {
}
chain KUBE-SERVICES {
meta l4proto tcp ip daddr 10.96.0.10 tcp dport 9153 counter packets 0 bytes 0 jump KUBE-SVC-JD5MR3NA4I4DYORP
meta l4proto udp ip daddr 10.96.0.10 udp dport 53 counter packets 0 bytes 0 jump KUBE-SVC-TCOU7JCQXEZGVUNU
meta l4proto tcp ip daddr 10.96.106.185 tcp dport 9080 counter packets 0 bytes 0 jump KUBE-SVC-D5JKTLXOFYHV5HQZ
meta l4proto tcp ip daddr 10.96.70.203 tcp dport 9080 counter packets 0 bytes 0 jump KUBE-SVC-IB3WK5BQ64FMB5FP
meta l4proto tcp ip daddr 10.96.0.10 tcp dport 53 counter packets 13 bytes 780 jump KUBE-SVC-ERIFXISQEP7F7OF4
meta l4proto tcp ip daddr 10.96.86.60 tcp dport 9080 counter packets 0 bytes 0 jump KUBE-SVC-QUBDBT4PCRU7S2PI
meta l4proto tcp ip daddr 10.96.184.88 tcp dport 9080 counter packets 0 bytes 0 jump KUBE-SVC-ZD23KKVZJDKFKTCE
meta l4proto tcp ip daddr 10.96.149.162 tcp dport 443 counter packets 0 bytes 0 jump KUBE-SVC-WHNIZNLB5XFXIX2C
meta l4proto tcp ip daddr 10.96.225.221 tcp dport 9080 counter packets 0 bytes 0 jump KUBE-SVC-ROH4UCJ7RVN2OSM4
meta l4proto tcp ip daddr 10.96.50.119 tcp dport 9080 counter packets 0 bytes 0 jump KUBE-SVC-SB7WEE53EMIXFNKY
meta l4proto tcp ip daddr 10.96.230.205 tcp dport 9080 counter packets 0 bytes 0 jump KUBE-SVC-OJLEMCF5KYSTXAAJ
meta l4proto tcp ip daddr 10.96.149.162 tcp dport 15014 counter packets 0 bytes 0 jump KUBE-SVC-XHUBMW47Y5G3ICIS
meta l4proto tcp ip daddr 10.96.83.127 tcp dport 9080 counter packets 0 bytes 0 jump KUBE-SVC-4MYBDLPZ2DFGC5Z6
meta l4proto tcp ip daddr 10.96.245.249 tcp dport 9080 counter packets 0 bytes 0 jump KUBE-SVC-53SQRANQXVHTJ6HK
meta l4proto tcp ip daddr 10.96.85.31 tcp dport 9090 counter packets 0 bytes 0 jump KUBE-SVC-VVO7BBXOSCJQDQML
meta l4proto tcp ip daddr 10.96.0.1 tcp dport 443 counter packets 0 bytes 0 jump KUBE-SVC-NPX46M4PTMTKRN6Y
meta l4proto tcp ip daddr 10.96.113.49 tcp dport 9080 counter packets 0 bytes 0 jump KUBE-SVC-COV23IKAKYWND6VU
meta l4proto tcp ip daddr 10.96.149.162 tcp dport 15010 counter packets 0 bytes 0 jump KUBE-SVC-NVNLZVDQSGQUD3NM
meta l4proto tcp ip daddr 10.96.231.15 tcp dport 3000 counter packets 0 bytes 0 jump KUBE-SVC-XUJLWDDTZEWKLHU6
meta l4proto tcp ip daddr 10.96.149.162 tcp dport 15012 counter packets 2 bytes 120 jump KUBE-SVC-CG3LQLBYYHBKATGN
fib daddr type local counter packets 329 bytes 19740 jump KUBE-NODEPORTS
}
chain KUBE-POSTROUTING {
meta mark & 0x00004000 != 0x00004000 counter packets 1593 bytes 95580 return
counter packets 13 bytes 780 meta mark set mark xor 0x4000
counter packets 13 bytes 780 masquerade fully-random
}
chain KUBE-NODEPORTS {
meta l4proto tcp tcp dport 30207 counter packets 0 bytes 0 jump KUBE-EXT-VVO7BBXOSCJQDQML
meta l4proto tcp tcp dport 31182 counter packets 0 bytes 0 jump KUBE-EXT-XUJLWDDTZEWKLHU6
}
chain KUBE-MARK-MASQ {
counter packets 13 bytes 780 meta mark set mark or 0x4000
}
chain KUBE-SVC-COV23IKAKYWND6VU {
meta l4proto tcp ip saddr != 10.244.0.0/16 ip daddr 10.96.113.49 tcp dport 9080 counter packets 0 bytes 0 jump KUBE-MARK-MASQ
counter packets 0 bytes 0 jump KUBE-SEP-UYFG3BP6SBY2ENL5
}
chain KUBE-SVC-OJLEMCF5KYSTXAAJ {
meta l4proto tcp ip saddr != 10.244.0.0/16 ip daddr 10.96.230.205 tcp dport 9080 counter packets 0 bytes 0 jump KUBE-MARK-MASQ
counter packets 0 bytes 0 jump KUBE-SEP-4Y5RNE5AMASHQ2OZ
}
chain KUBE-SVC-4MYBDLPZ2DFGC5Z6 {
meta l4proto tcp ip saddr != 10.244.0.0/16 ip daddr 10.96.83.127 tcp dport 9080 counter packets 0 bytes 0 jump KUBE-MARK-MASQ
counter packets 0 bytes 0 jump KUBE-SEP-VLSRDM63ATDZAA3A
}
chain KUBE-SVC-53SQRANQXVHTJ6HK {
meta l4proto tcp ip saddr != 10.244.0.0/16 ip daddr 10.96.245.249 tcp dport 9080 counter packets 0 bytes 0 jump KUBE-MARK-MASQ
meta random & 2147483647 < 715827883 counter packets 0 bytes 0 jump KUBE-SEP-AYXBHI7HU6SF34DI
meta random & 2147483647 < 1073741824 counter packets 0 bytes 0 jump KUBE-SEP-PBXQV3T6XQVEVBGL
counter packets 0 bytes 0 jump KUBE-SEP-WLEZNEPJCJLP5SOM
}
chain KUBE-EXT-VVO7BBXOSCJQDQML {
counter packets 0 bytes 0 jump KUBE-MARK-MASQ
counter packets 0 bytes 0 jump KUBE-SVC-VVO7BBXOSCJQDQML
}
chain KUBE-SVC-VVO7BBXOSCJQDQML {
meta l4proto tcp ip saddr != 10.244.0.0/16 ip daddr 10.96.85.31 tcp dport 9090 counter packets 0 bytes 0 jump KUBE-MARK-MASQ
counter packets 0 bytes 0 jump KUBE-SEP-6ZOGXI2ZCDJV5G4O
}
chain KUBE-SVC-NPX46M4PTMTKRN6Y {
meta l4proto tcp ip saddr != 10.244.0.0/16 ip daddr 10.96.0.1 tcp dport 443 counter packets 29 bytes 1740 jump KUBE-MARK-MASQ
counter packets 30 bytes 1800 jump KUBE-SEP-B5DUEXKFRYN46BFH
}
chain KUBE-SEP-B5DUEXKFRYN46BFH {
ip saddr 192.168.8.4 counter packets 0 bytes 0 jump KUBE-MARK-MASQ
meta l4proto tcp counter packets 30 bytes 1800 dnat to 192.168.8.4:6443
}
chain KUBE-EXT-XUJLWDDTZEWKLHU6 {
counter packets 0 bytes 0 jump KUBE-MARK-MASQ
counter packets 0 bytes 0 jump KUBE-SVC-XUJLWDDTZEWKLHU6
}
chain KUBE-SVC-XUJLWDDTZEWKLHU6 {
meta l4proto tcp ip saddr != 10.244.0.0/16 ip daddr 10.96.231.15 tcp dport 3000 counter packets 0 bytes 0 jump KUBE-MARK-MASQ
counter packets 0 bytes 0 jump KUBE-SEP-AEOIAT7KXYABTON5
}
chain KUBE-SVC-D5JKTLXOFYHV5HQZ {
meta l4proto tcp ip saddr != 10.244.0.0/16 ip daddr 10.96.106.185 tcp dport 9080 counter packets 0 bytes 0 jump KUBE-MARK-MASQ
counter packets 0 bytes 0 jump KUBE-SEP-IRX4MQBX4KRVQAZU
}
chain KUBE-SVC-IB3WK5BQ64FMB5FP {
meta l4proto tcp ip saddr != 10.244.0.0/16 ip daddr 10.96.70.203 tcp dport 9080 counter packets 0 bytes 0 jump KUBE-MARK-MASQ
counter packets 0 bytes 0 jump KUBE-SEP-ME7B5OZS3EMRJJUS
}
chain KUBE-SVC-SB7WEE53EMIXFNKY {
meta l4proto tcp ip saddr != 10.244.0.0/16 ip daddr 10.96.50.119 tcp dport 9080 counter packets 0 bytes 0 jump KUBE-MARK-MASQ
counter packets 0 bytes 0 jump KUBE-SEP-UBXSLXFPFJOARYRN
}
chain KUBE-SVC-QUBDBT4PCRU7S2PI {
meta l4proto tcp ip saddr != 10.244.0.0/16 ip daddr 10.96.86.60 tcp dport 9080 counter packets 0 bytes 0 jump KUBE-MARK-MASQ
counter packets 0 bytes 0 jump KUBE-SEP-ENYDTZIVOME6TBBV
}
chain KUBE-SVC-ZD23KKVZJDKFKTCE {
meta l4proto tcp ip saddr != 10.244.0.0/16 ip daddr 10.96.184.88 tcp dport 9080 counter packets 0 bytes 0 jump KUBE-MARK-MASQ
counter packets 0 bytes 0 jump KUBE-SEP-5KXO57AZTMNCDCVX
}
chain KUBE-SVC-ROH4UCJ7RVN2OSM4 {
meta l4proto tcp ip saddr != 10.244.0.0/16 ip daddr 10.96.225.221 tcp dport 9080 counter packets 0 bytes 0 jump KUBE-MARK-MASQ
counter packets 0 bytes 0 jump KUBE-SEP-YMBKJHKN7Y2F6666
}
chain KUBE-SEP-YMBKJHKN7Y2F6666 {
ip saddr 10.244.1.189 counter packets 0 bytes 0 jump KUBE-MARK-MASQ
meta l4proto tcp counter packets 0 bytes 0 dnat to 10.244.1.189:9080
}
chain KUBE-SEP-5KXO57AZTMNCDCVX {
ip saddr 10.244.1.189 counter packets 0 bytes 0 jump KUBE-MARK-MASQ
meta l4proto tcp counter packets 0 bytes 0 dnat to 10.244.1.189:9080
}
chain KUBE-SEP-UBXSLXFPFJOARYRN {
ip saddr 10.244.1.108 counter packets 0 bytes 0 jump KUBE-MARK-MASQ
meta l4proto tcp counter packets 0 bytes 0 dnat to 10.244.1.108:9080
}
chain KUBE-SEP-ENYDTZIVOME6TBBV {
ip saddr 10.244.1.108 counter packets 0 bytes 0 jump KUBE-MARK-MASQ
meta l4proto tcp counter packets 0 bytes 0 dnat to 10.244.1.108:9080
}
chain KUBE-SEP-6ZOGXI2ZCDJV5G4O {
ip saddr 10.244.1.9 counter packets 0 bytes 0 jump KUBE-MARK-MASQ
meta l4proto tcp counter packets 0 bytes 0 dnat to 10.244.1.9:9090
}
chain KUBE-SEP-UYFG3BP6SBY2ENL5 {
ip saddr 10.244.1.215 counter packets 0 bytes 0 jump KUBE-MARK-MASQ
meta l4proto tcp counter packets 0 bytes 0 dnat to 10.244.1.215:9080
}
chain KUBE-SEP-4Y5RNE5AMASHQ2OZ {
ip saddr 10.244.2.98 counter packets 0 bytes 0 jump KUBE-MARK-MASQ
meta l4proto tcp counter packets 0 bytes 0 dnat to 10.244.2.98:9080
}
chain KUBE-SEP-VLSRDM63ATDZAA3A {
ip saddr 10.244.1.237 counter packets 0 bytes 0 jump KUBE-MARK-MASQ
meta l4proto tcp counter packets 0 bytes 0 dnat to 10.244.1.237:9080
}
chain KUBE-SEP-AYXBHI7HU6SF34DI {
ip saddr 10.244.1.131 counter packets 0 bytes 0 jump KUBE-MARK-MASQ
meta l4proto tcp counter packets 0 bytes 0 dnat to 10.244.1.131:9080
}
chain KUBE-SEP-PBXQV3T6XQVEVBGL {
ip saddr 10.244.1.215 counter packets 0 bytes 0 jump KUBE-MARK-MASQ
meta l4proto tcp counter packets 0 bytes 0 dnat to 10.244.1.215:9080
}
chain KUBE-SEP-WLEZNEPJCJLP5SOM {
ip saddr 10.244.2.98 counter packets 0 bytes 0 jump KUBE-MARK-MASQ
meta l4proto tcp counter packets 0 bytes 0 dnat to 10.244.2.98:9080
}
chain KUBE-SEP-AEOIAT7KXYABTON5 {
ip saddr 10.244.1.13 counter packets 0 bytes 0 jump KUBE-MARK-MASQ
meta l4proto tcp counter packets 0 bytes 0 dnat to 10.244.1.13:3000
}
chain KUBE-SEP-IRX4MQBX4KRVQAZU {
ip saddr 10.244.1.237 counter packets 0 bytes 0 jump KUBE-MARK-MASQ
meta l4proto tcp counter packets 0 bytes 0 dnat to 10.244.1.237:9080
}
chain KUBE-SEP-ME7B5OZS3EMRJJUS {
ip saddr 10.244.1.131 counter packets 0 bytes 0 jump KUBE-MARK-MASQ
meta l4proto tcp counter packets 0 bytes 0 dnat to 10.244.1.131:9080
}
chain KUBE-SVC-WHNIZNLB5XFXIX2C {
meta l4proto tcp ip saddr != 10.244.0.0/16 ip daddr 10.96.149.162 tcp dport 443 counter packets 0 bytes 0 jump KUBE-MARK-MASQ
counter packets 0 bytes 0 jump KUBE-SEP-22OJIEOUHUS2VH36
}
chain KUBE-SEP-22OJIEOUHUS2VH36 {
ip saddr 10.244.2.88 counter packets 0 bytes 0 jump KUBE-MARK-MASQ
meta l4proto tcp counter packets 0 bytes 0 dnat to 10.244.2.88:15017
}
chain KUBE-SVC-XHUBMW47Y5G3ICIS {
meta l4proto tcp ip saddr != 10.244.0.0/16 ip daddr 10.96.149.162 tcp dport 15014 counter packets 0 bytes 0 jump KUBE-MARK-MASQ
counter packets 0 bytes 0 jump KUBE-SEP-N6BCKY3MRNRV2ZJ2
}
chain KUBE-SEP-N6BCKY3MRNRV2ZJ2 {
ip saddr 10.244.2.88 counter packets 0 bytes 0 jump KUBE-MARK-MASQ
meta l4proto tcp counter packets 0 bytes 0 dnat to 10.244.2.88:15014
}
chain KUBE-SVC-CG3LQLBYYHBKATGN {
meta l4proto tcp ip saddr != 10.244.0.0/16 ip daddr 10.96.149.162 tcp dport 15012 counter packets 0 bytes 0 jump KUBE-MARK-MASQ
counter packets 259 bytes 15540 jump KUBE-SEP-E675BOVPZS3XINT6
}
chain KUBE-SEP-E675BOVPZS3XINT6 {
ip saddr 10.244.2.88 counter packets 0 bytes 0 jump KUBE-MARK-MASQ
meta l4proto tcp counter packets 259 bytes 15540 dnat to 10.244.2.88:15012
}
chain KUBE-SVC-NVNLZVDQSGQUD3NM {
meta l4proto tcp ip saddr != 10.244.0.0/16 ip daddr 10.96.149.162 tcp dport 15010 counter packets 0 bytes 0 jump KUBE-MARK-MASQ
counter packets 0 bytes 0 jump KUBE-SEP-E2OCVXQ5RANXZKNO
}
chain KUBE-SEP-E2OCVXQ5RANXZKNO {
ip saddr 10.244.2.88 counter packets 0 bytes 0 jump KUBE-MARK-MASQ
meta l4proto tcp counter packets 0 bytes 0 dnat to 10.244.2.88:15010
}
chain KUBE-SVC-ERIFXISQEP7F7OF4 {
meta l4proto tcp ip saddr != 10.244.0.0/16 ip daddr 10.96.0.10 tcp dport 53 counter packets 1718 bytes 103080 jump KUBE-MARK-MASQ
meta random & 2147483647 < 1073741824 counter packets 875 bytes 52500 jump KUBE-SEP-IH2TMTJLEHQTEXG4
counter packets 843 bytes 50580 jump KUBE-SEP-QHHO2BBHA2W6ABVA
}
chain KUBE-SEP-IH2TMTJLEHQTEXG4 {
ip saddr 10.244.0.44 counter packets 0 bytes 0 jump KUBE-MARK-MASQ
meta l4proto tcp counter packets 875 bytes 52500 dnat to 10.244.0.44:53
}
chain KUBE-SVC-JD5MR3NA4I4DYORP {
meta l4proto tcp ip saddr != 10.244.0.0/16 ip daddr 10.96.0.10 tcp dport 9153 counter packets 0 bytes 0 jump KUBE-MARK-MASQ
meta random & 2147483647 < 1073741824 counter packets 0 bytes 0 jump KUBE-SEP-KTQ44RRS25IYXDAU
counter packets 0 bytes 0 jump KUBE-SEP-SWQQGKEGIJFNRJRL
}
chain KUBE-SEP-KTQ44RRS25IYXDAU {
ip saddr 10.244.0.44 counter packets 0 bytes 0 jump KUBE-MARK-MASQ
meta l4proto tcp counter packets 0 bytes 0 dnat to 10.244.0.44:9153
}
chain KUBE-SVC-TCOU7JCQXEZGVUNU {
meta l4proto udp ip saddr != 10.244.0.0/16 ip daddr 10.96.0.10 udp dport 53 counter packets 0 bytes 0 jump KUBE-MARK-MASQ
meta random & 2147483647 < 1073741824 counter packets 2 bytes 171 jump KUBE-SEP-YGFHBW2DM6N3IEK3
counter packets 0 bytes 0 jump KUBE-SEP-W3GSBK4IMEBEFHPJ
}
chain KUBE-SEP-YGFHBW2DM6N3IEK3 {
ip saddr 10.244.0.44 counter packets 0 bytes 0 jump KUBE-MARK-MASQ
meta l4proto udp counter packets 2 bytes 171 dnat to 10.244.0.44:53
}
chain KUBE-SEP-QHHO2BBHA2W6ABVA {
ip saddr 10.244.0.63 counter packets 0 bytes 0 jump KUBE-MARK-MASQ
meta l4proto tcp counter packets 843 bytes 50580 dnat to 10.244.0.63:53
}
chain KUBE-SEP-SWQQGKEGIJFNRJRL {
ip saddr 10.244.0.63 counter packets 0 bytes 0 jump KUBE-MARK-MASQ
meta l4proto tcp counter packets 0 bytes 0 dnat to 10.244.0.63:9153
}
chain KUBE-SEP-W3GSBK4IMEBEFHPJ {
ip saddr 10.244.0.63 counter packets 0 bytes 0 jump KUBE-MARK-MASQ
meta l4proto udp counter packets 0 bytes 0 dnat to 10.244.0.63:53
}
}
table ip mangle {
chain KUBE-IPTABLES-HINT {
}
chain KUBE-KUBELET-CANARY {
}
chain KUBE-PROXY-CANARY {
}
}
table ip filter {
chain KUBE-FIREWALL {
ip saddr != 127.0.0.0/8 ip daddr 127.0.0.0/8 ct status dnat counter packets 0 bytes 0 drop
}
chain OUTPUT {
type filter hook output priority filter; policy accept;
ct state new counter packets 245383 bytes 14723644 jump KUBE-PROXY-FIREWALL
ct state new counter packets 245383 bytes 14723644 jump KUBE-SERVICES
counter packets 3043981 bytes 2633914697 jump KUBE-FIREWALL
}
chain INPUT {
type filter hook input priority filter; policy accept;
ct state new counter packets 47332 bytes 2840200 jump KUBE-PROXY-FIREWALL
counter packets 3210147 bytes 435991279 jump KUBE-NODEPORTS
ct state new counter packets 47332 bytes 2840200 jump KUBE-EXTERNAL-SERVICES
counter packets 3210791 bytes 436579809 jump KUBE-FIREWALL
}
chain KUBE-KUBELET-CANARY {
}
chain KUBE-PROXY-CANARY {
}
chain KUBE-EXTERNAL-SERVICES {
}
chain FORWARD {
type filter hook forward priority filter; policy accept;
ct state new counter packets 774 bytes 46491 jump KUBE-PROXY-FIREWALL
counter packets 287624 bytes 35538980 jump KUBE-FORWARD
ct state new counter packets 774 bytes 46491 jump KUBE-SERVICES
ct state new counter packets 774 bytes 46491 jump KUBE-EXTERNAL-SERVICES
}
chain KUBE-NODEPORTS {
}
chain KUBE-SERVICES {
meta l4proto tcp ip daddr 10.96.65.65 tcp dport 80 counter packets 0 bytes 0 reject
}
chain KUBE-FORWARD {
meta mark & 0x00004000 == 0x00004000 counter packets 0 bytes 0 accept
ct state related,established counter packets 1866 bytes 233783 accept
}
chain KUBE-PROXY-FIREWALL {
}
}
table ip6 mangle {
chain KUBE-IPTABLES-HINT {
}
chain KUBE-KUBELET-CANARY {
}
chain KUBE-PROXY-CANARY {
}
}
table ip6 nat {
chain KUBE-KUBELET-CANARY {
}
chain POSTROUTING {
type nat hook postrouting priority srcnat; policy accept;
counter packets 0 bytes 0 jump KUBE-POSTROUTING
}
chain KUBE-PROXY-CANARY {
}
chain KUBE-SERVICES {
ip6 daddr != ::1 fib daddr type local counter packets 0 bytes 0 jump KUBE-NODEPORTS
}
chain OUTPUT {
type nat hook output priority -100; policy accept;
counter packets 0 bytes 0 jump KUBE-SERVICES
}
chain PREROUTING {
type nat hook prerouting priority dstnat; policy accept;
counter packets 0 bytes 0 jump KUBE-SERVICES
}
chain KUBE-POSTROUTING {
meta mark & 0x00004000 != 0x00004000 counter packets 0 bytes 0 return
counter packets 0 bytes 0 meta mark set mark xor 0x4000
counter packets 0 bytes 0
}
chain KUBE-NODEPORTS {
}
chain KUBE-MARK-MASQ {
counter packets 0 bytes 0 meta mark set mark or 0x4000
}
}
table ip6 filter {
chain KUBE-KUBELET-CANARY {
}
chain KUBE-PROXY-CANARY {
}
chain KUBE-EXTERNAL-SERVICES {
}
chain INPUT {
type filter hook input priority filter; policy accept;
counter packets 8 bytes 552 jump KUBE-FIREWALL
ct state new counter packets 0 bytes 0 jump KUBE-PROXY-FIREWALL
counter packets 8 bytes 552 jump KUBE-NODEPORTS
ct state new counter packets 0 bytes 0 jump KUBE-EXTERNAL-SERVICES
}
chain FORWARD {
type filter hook forward priority filter; policy accept;
ct state new counter packets 0 bytes 0 jump KUBE-PROXY-FIREWALL
counter packets 0 bytes 0 jump KUBE-FORWARD
ct state new counter packets 0 bytes 0 jump KUBE-SERVICES
ct state new counter packets 0 bytes 0 jump KUBE-EXTERNAL-SERVICES
}
chain KUBE-NODEPORTS {
}
chain KUBE-SERVICES {
}
chain OUTPUT {
type filter hook output priority filter; policy accept;
counter packets 139 bytes 7888 jump KUBE-FIREWALL
ct state new counter packets 0 bytes 0 jump KUBE-PROXY-FIREWALL
ct state new counter packets 0 bytes 0 jump KUBE-SERVICES
}
chain KUBE-FORWARD {
meta mark & 0x00004000 == 0x00004000 counter packets 0 bytes 0 accept
ct state related,established counter packets 0 bytes 0 accept
}
chain KUBE-PROXY-FIREWALL {
}
chain KUBE-FIREWALL {
}
}
table inet kindnet-network-policies {
set podips-v4 {
type ipv4_addr
}
set podips-v6 {
type ipv6_addr
}
chain postrouting {
type filter hook postrouting priority srcnat - 5; policy accept;
udp dport 53 accept
meta l4proto ipv6-icmp accept
meta skuid 0 accept
ct state established,related accept
ip saddr @podips-v4 queue flags bypass to 102
ip daddr @podips-v4 queue flags bypass to 102
ip6 saddr @podips-v6 queue flags bypass to 102
ip6 daddr @podips-v6 queue flags bypass to 102
}
chain prerouting {
type filter hook prerouting priority dstnat + 5; policy accept;
meta l4proto != udp accept
udp dport != 53 accept
ip saddr @podips-v4 queue flags bypass to 102
ip daddr @podips-v4 queue flags bypass to 102
ip6 saddr @podips-v6 queue flags bypass to 102
ip6 daddr @podips-v6 queue flags bypass to 102
}
}
table inet kindnet-dnscache {
set set-v4-nameservers {
type ipv4_addr
elements = { 10.96.0.10 }
}
chain prerouting {
type filter hook prerouting priority raw; policy accept;
ip saddr 10.244.2.0/24 ip daddr @set-v4-nameservers udp dport 53 queue flags bypass to 103
}
chain output {
type filter hook output priority raw; policy accept;
meta mark 0x0000006e udp sport 53 notrack
}
}
table inet kindnet-ipmasq {
set noMasqV4 {
type ipv4_addr
flags interval
auto-merge
elements = { 10.244.0.0/24, 10.244.1.0/24,
10.244.2.0/24 }
}
set noMasqV6 {
type ipv6_addr
flags interval
auto-merge
}
chain postrouting {
type nat hook postrouting priority srcnat - 10; policy accept;
ct state established,related accept
fib saddr type local accept
ip daddr @noMasqV4 accept
ip6 daddr @noMasqV6 accept
masquerade
}
}

View File

@ -0,0 +1,17 @@
table ip filter {
chain output {
type filter hook output priority 100; policy accept;
}
chain input {
type filter hook input priority 0; policy accept;
iifname "lan0" accept
iifname "wan0" drop
}
chain forward {
type filter hook forward priority 0; policy drop;
iifname "lan0" oifname "wan0" accept
iifname "wan0" oifname "lan0" ct state related,established accept
}
}

View File

@ -71,7 +71,7 @@ func TestMonitor(t *testing.T) {
return return
} }
genMsg := event.GeneratedBy.Data.(*nftables.GenMsg) genMsg := event.GeneratedBy.Data.(*nftables.Gen)
fileName := filepath.Base(os.Args[0]) fileName := filepath.Base(os.Args[0])
if genMsg.ProcComm != fileName { if genMsg.ProcComm != fileName {

View File

@ -128,6 +128,47 @@ func ifname(n string) []byte {
return b return b
} }
func TestTableCreateDestroy(t *testing.T) {
c, newNS := nftest.OpenSystemConn(t, *enableSysTests)
defer nftest.CleanupSystemConn(t, newNS)
defer c.FlushRuleset()
filter := &nftables.Table{
Family: nftables.TableFamilyIPv4,
Name: "filter",
}
c.DestroyTable(filter)
c.AddTable(filter)
err := c.Flush()
if err != nil {
t.Fatalf("on Flush: %q", err.Error())
}
lookupMyTable := func() bool {
ts, err := c.ListTables()
if err != nil {
t.Fatalf("on ListTables: %q", err.Error())
}
return slices.ContainsFunc(ts, func(t *nftables.Table) bool {
return t.Name == filter.Name && t.Family == filter.Family
})
}
if !lookupMyTable() {
t.Fatal("AddTable doesn't create my table!")
}
c.DestroyTable(filter)
if err = c.Flush(); err != nil {
t.Fatalf("on Flush: %q", err.Error())
}
if lookupMyTable() {
t.Fatal("DestroyTable doesn't delete my table!")
}
c.DestroyTable(filter) // just for test that 'destroy' ignore error 'not found'
}
func TestRuleOperations(t *testing.T) { func TestRuleOperations(t *testing.T) {
// Create a new network namespace to test these operations, // Create a new network namespace to test these operations,
// and tear down the namespace at test completion. // and tear down the namespace at test completion.
@ -3777,7 +3818,7 @@ func TestDeleteElementNamedSet(t *testing.T) {
Name: "test", Name: "test",
KeyType: nftables.TypeInetService, KeyType: nftables.TypeInetService,
} }
if err := c.AddSet(portSet, []nftables.SetElement{{Key: []byte{0, 22}}, {Key: []byte{0, 23}}}); err != nil { if err := c.AddSet(portSet, []nftables.SetElement{{Key: []byte{0, 22}}, {Key: []byte{0, 23}}, {Key: []byte{0, 24}}}); err != nil {
t.Errorf("c.AddSet(portSet) failed: %v", err) t.Errorf("c.AddSet(portSet) failed: %v", err)
} }
if err := c.Flush(); err != nil { if err := c.Flush(); err != nil {
@ -3794,6 +3835,22 @@ func TestDeleteElementNamedSet(t *testing.T) {
if err != nil { if err != nil {
t.Errorf("c.GetSets() failed: %v", err) t.Errorf("c.GetSets() failed: %v", err)
} }
if len(elems) != 2 {
t.Fatalf("len(elems) = %d, want 2", len(elems))
}
c.SetDestroyElements(portSet, []nftables.SetElement{{Key: []byte{0, 24}}})
c.SetDestroyElements(portSet, []nftables.SetElement{{Key: []byte{0, 24}}})
c.SetDestroyElements(portSet, []nftables.SetElement{{Key: []byte{0, 99}}})
if err := c.Flush(); err != nil {
t.Errorf("Third c.Flush() failed: %v", err)
}
elems, err = c.GetSetElements(portSet)
if err != nil {
t.Errorf("c.GetSets() failed: %v", err)
}
if len(elems) != 1 { if len(elems) != 1 {
t.Fatalf("len(elems) = %d, want 1", len(elems)) t.Fatalf("len(elems) = %d, want 1", len(elems))
} }
@ -7429,3 +7486,303 @@ func TestAutoBufferSize(t *testing.T) {
t.Fatalf("failed to flush: %v", err) t.Fatalf("failed to flush: %v", err)
} }
} }
func TestGetGen(t *testing.T) {
conn, newNS := nftest.OpenSystemConn(t, *enableSysTests)
defer nftest.CleanupSystemConn(t, newNS)
defer conn.FlushRuleset()
gen, err := conn.GetGen()
if err != nil {
t.Fatalf("failed to get gen: %v", err)
}
conn.AddTable(&nftables.Table{
Name: "test-table",
Family: nftables.TableFamilyIPv4,
})
// Flush to increment the generation ID.
if err := conn.Flush(); err != nil {
t.Fatalf("failed to flush: %v", err)
}
newGen, err := conn.GetGen()
if err != nil {
t.Fatalf("failed to get gen: %v", err)
}
if newGen.ID <= gen.ID {
t.Fatalf("gen ID did not increase, got %d, want > %d", newGen.ID, gen.ID)
}
if newGen.ProcComm != gen.ProcComm {
t.Errorf("gen ProcComm changed, got %s, want %s", newGen.ProcComm, gen.ProcComm)
}
if newGen.ProcPID != gen.ProcPID {
t.Errorf("gen ProcPID changed, got %d, want %d", newGen.ProcPID, gen.ProcPID)
}
}
func TestFlushWithGenID(t *testing.T) {
conn, newNS := nftest.OpenSystemConn(t, *enableSysTests)
defer nftest.CleanupSystemConn(t, newNS)
defer conn.FlushRuleset()
gen, err := conn.GetGen()
if err != nil {
t.Fatalf("failed to get gen: %v", err)
}
conn.AddTable(&nftables.Table{
Name: "test-table",
Family: nftables.TableFamilyIPv4,
})
// Flush to increment the generation ID.
if err := conn.Flush(); err != nil {
t.Fatalf("failed to flush: %v", err)
}
conn.AddTable(&nftables.Table{
Name: "test-table-2",
Family: nftables.TableFamilyIPv4,
})
err = conn.FlushWithGenID(gen.ID)
if err == nil || !errors.Is(err, syscall.ERESTART) {
t.Errorf("expected error to be ERESTART, got: %v", err)
}
table, err := conn.ListTable("test-table-2")
if table != nil && !errors.Is(err, syscall.ENOENT) {
t.Errorf("expected table to not exist, got: %v", table)
}
}
func TestGetRuleByHandle(t *testing.T) {
conn, newNS := nftest.OpenSystemConn(t, *enableSysTests)
defer nftest.CleanupSystemConn(t, newNS)
defer conn.FlushRuleset()
table := conn.AddTable(&nftables.Table{
Name: "test-table",
Family: nftables.TableFamilyIPv4,
})
chain := conn.AddChain(&nftables.Chain{
Name: "test-chain",
Table: table,
})
for i := range 3 {
conn.AddRule(&nftables.Rule{
Table: table,
Chain: chain,
UserData: fmt.Appendf([]byte{}, "rule-%d", i+1),
Exprs: []expr.Any{
&expr.Verdict{
Kind: expr.VerdictAccept,
},
},
})
}
if err := conn.Flush(); err != nil {
t.Fatalf("failed to flush: %v", err)
}
rules, err := conn.GetRules(table, chain)
if err != nil {
t.Fatalf("GetRules failed: %v", err)
}
want := rules[1]
got, err := conn.GetRuleByHandle(table, chain, want.Handle)
if err != nil {
t.Fatalf("GetRuleByHandle failed: %v", err)
}
if !bytes.Equal(got.UserData, want.UserData) {
t.Fatalf("expected userdata %q, got %q", got.UserData, want.UserData)
}
}
func TestResetRule(t *testing.T) {
conn, newNS := nftest.OpenSystemConn(t, *enableSysTests)
defer nftest.CleanupSystemConn(t, newNS)
defer conn.FlushRuleset()
table := conn.AddTable(&nftables.Table{
Name: "test-table",
Family: nftables.TableFamilyIPv4,
})
chain := conn.AddChain(&nftables.Chain{
Name: "test-chain",
Table: table,
})
tests := [...]struct {
Bytes uint64
Packets uint64
Reset bool
}{
{
Bytes: 1024,
Packets: 1,
Reset: false,
},
{
Bytes: 2048,
Packets: 2,
Reset: true,
},
{
Bytes: 4096,
Packets: 4,
Reset: false,
},
}
for _, tt := range tests {
conn.AddRule(&nftables.Rule{
Table: table,
Chain: chain,
Exprs: []expr.Any{
&expr.Counter{
Bytes: tt.Bytes,
Packets: tt.Packets,
},
&expr.Verdict{
Kind: expr.VerdictAccept,
},
},
})
}
if err := conn.Flush(); err != nil {
t.Fatalf("flush failed: %v", err)
}
rules, err := conn.GetRules(table, chain)
if err != nil {
t.Fatalf("GetRules failed: %v", err)
}
if len(rules) != len(tests) {
t.Fatalf("expected %d rules, got %d", len(tests), len(rules))
}
for i, r := range rules {
if !tests[i].Reset {
continue
}
_, err := conn.ResetRule(table, chain, r.Handle)
if err != nil {
t.Fatalf("ResetRule failed: %v", err)
}
}
rules, err = conn.GetRules(table, chain)
if err != nil {
t.Fatalf("GetRules failed: %v", err)
}
for i, r := range rules {
counter, ok := r.Exprs[0].(*expr.Counter)
if !ok {
t.Errorf("expected first expr to be Counter, got %T", r.Exprs[0])
}
if tests[i].Reset {
if counter.Bytes != 0 || counter.Packets != 0 {
t.Errorf(
"expected counter values to be reset to zero, got Bytes=%d, Packets=%d",
counter.Bytes,
counter.Packets,
)
}
} else {
// Making sure that only the selected rules were reset
if counter.Bytes != tests[i].Bytes || counter.Packets != tests[i].Packets {
t.Errorf(
"unexpected counter values: got Bytes=%d, Packets=%d, want Bytes=%d, Packets=%d",
counter.Bytes,
counter.Packets,
tests[i].Bytes,
tests[i].Packets)
}
}
}
}
func TestResetRules(t *testing.T) {
conn, newNS := nftest.OpenSystemConn(t, *enableSysTests)
defer nftest.CleanupSystemConn(t, newNS)
defer conn.FlushRuleset()
table := conn.AddTable(&nftables.Table{
Name: "test-table",
Family: nftables.TableFamilyIPv4,
})
chain := conn.AddChain(&nftables.Chain{
Name: "test-chain",
Table: table,
})
for range 3 {
conn.AddRule(&nftables.Rule{
Table: table,
Chain: chain,
Exprs: []expr.Any{
&expr.Counter{
Bytes: 1,
Packets: 1,
},
&expr.Verdict{
Kind: expr.VerdictAccept,
},
},
})
}
if err := conn.Flush(); err != nil {
t.Fatalf("flush failed: %v", err)
}
rules, err := conn.GetRules(table, chain)
if err != nil {
t.Fatalf("GetRules failed: %v", err)
}
if len(rules) != 3 {
t.Fatalf("expected %d rules, got %d", 3, len(rules))
}
if _, err := conn.ResetRules(table, chain); err != nil {
t.Fatalf("ResetRules failed: %v", err)
}
rules, err = conn.GetRules(table, chain)
if err != nil {
t.Fatalf("GetRules failed: %v", err)
}
for _, r := range rules {
counter, ok := r.Exprs[0].(*expr.Counter)
if !ok {
t.Errorf("expected first expr to be Counter, got %T", r.Exprs[0])
}
if counter.Bytes != 0 || counter.Packets != 0 {
t.Errorf(
"expected counter values to be reset to zero, got Bytes=%d, Packets=%d",
counter.Bytes,
counter.Packets,
)
}
}
}

77
rule.go
View File

@ -71,31 +71,98 @@ type Rule struct {
// GetRule returns the rules in the specified table and chain. // GetRule returns the rules in the specified table and chain.
// //
// Deprecated: use GetRules instead. // Deprecated: use GetRuleByHandle instead.
func (cc *Conn) GetRule(t *Table, c *Chain) ([]*Rule, error) { func (cc *Conn) GetRule(t *Table, c *Chain) ([]*Rule, error) {
return cc.GetRules(t, c) return cc.GetRules(t, c)
} }
// GetRuleByHandle returns the rule in the specified table and chain by its
// handle.
// https://docs.kernel.org/networking/netlink_spec/nftables.html#getrule
func (cc *Conn) GetRuleByHandle(t *Table, c *Chain, handle uint64) (*Rule, error) {
rules, err := cc.getRules(t, c, unix.NFT_MSG_GETRULE, handle)
if err != nil {
return nil, err
}
if got, want := len(rules), 1; got != want {
return nil, fmt.Errorf("expected rule count %d, got %d", want, got)
}
return rules[0], nil
}
// GetRules returns the rules in the specified table and chain. // GetRules returns the rules in the specified table and chain.
func (cc *Conn) GetRules(t *Table, c *Chain) ([]*Rule, error) { func (cc *Conn) GetRules(t *Table, c *Chain) ([]*Rule, error) {
return cc.getRules(t, c, unix.NFT_MSG_GETRULE, 0)
}
// ResetRule resets the stateful expressions (e.g., counters) of the given
// rule. The reset is applied immediately (no Flush is required). The returned
// rule reflects its state prior to the reset. The provided rule must have a
// valid Handle.
// https://docs.kernel.org/networking/netlink_spec/nftables.html#getrule-reset
func (cc *Conn) ResetRule(t *Table, c *Chain, handle uint64) (*Rule, error) {
if handle == 0 {
return nil, fmt.Errorf("rule must have a valid handle")
}
rules, err := cc.getRules(t, c, unix.NFT_MSG_GETRULE_RESET, handle)
if err != nil {
return nil, err
}
if got, want := len(rules), 1; got != want {
return nil, fmt.Errorf("expected rule count %d, got %d", want, got)
}
return rules[0], nil
}
// ResetRules resets the stateful expressions (e.g., counters) of all rules
// in the given table and chain. The reset is applied immediately (no Flush
// is required). The returned rules reflect their state prior to the reset.
// state.
// https://docs.kernel.org/networking/netlink_spec/nftables.html#getrule-reset
func (cc *Conn) ResetRules(t *Table, c *Chain) ([]*Rule, error) {
return cc.getRules(t, c, unix.NFT_MSG_GETRULE_RESET, 0)
}
// getRules retrieves rules from the given table and chain, using the provided
// msgType (either unix.NFT_MSG_GETRULE or unix.NFT_MSG_GETRULE_RESET). If the
// handle is non-zero, the operation applies only to the rule with that handle.
func (cc *Conn) getRules(t *Table, c *Chain, msgType int, handle uint64) ([]*Rule, error) {
conn, closer, err := cc.netlinkConn() conn, closer, err := cc.netlinkConn()
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer func() { _ = closer() }() defer func() { _ = closer() }()
data, err := netlink.MarshalAttributes([]netlink.Attribute{ attrs := []netlink.Attribute{
{Type: unix.NFTA_RULE_TABLE, Data: []byte(t.Name + "\x00")}, {Type: unix.NFTA_RULE_TABLE, Data: []byte(t.Name + "\x00")},
{Type: unix.NFTA_RULE_CHAIN, Data: []byte(c.Name + "\x00")}, {Type: unix.NFTA_RULE_CHAIN, Data: []byte(c.Name + "\x00")},
}) }
var flags netlink.HeaderFlags = netlink.Request | netlink.Acknowledge | netlink.Dump
if handle != 0 {
attrs = append(attrs, netlink.Attribute{
Type: unix.NFTA_RULE_HANDLE,
Data: binaryutil.BigEndian.PutUint64(handle),
})
flags = netlink.Request | netlink.Acknowledge
}
data, err := netlink.MarshalAttributes(attrs)
if err != nil { if err != nil {
return nil, err return nil, err
} }
message := netlink.Message{ message := netlink.Message{
Header: netlink.Header{ Header: netlink.Header{
Type: netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_GETRULE), Type: netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | msgType),
Flags: netlink.Request | netlink.Acknowledge | netlink.Dump, Flags: flags,
}, },
Data: append(extraHeader(uint8(t.Family), 0), data...), Data: append(extraHeader(uint8(t.Family), 0), data...),
} }

18
set.go
View File

@ -44,6 +44,9 @@ const (
NFTA_SET_ELEM_KEY_END = 10 NFTA_SET_ELEM_KEY_END = 10
// https://git.netfilter.org/nftables/tree/include/linux/netfilter/nf_tables.h?id=d1289bff58e1878c3162f574c603da993e29b113#n429 // https://git.netfilter.org/nftables/tree/include/linux/netfilter/nf_tables.h?id=d1289bff58e1878c3162f574c603da993e29b113#n429
NFTA_SET_ELEM_EXPRESSIONS = 0x11 NFTA_SET_ELEM_EXPRESSIONS = 0x11
// FIXME: in sys@v0.34.0 no unix.NFT_MSG_DESTROYSETELEM const yet.
// See nf_tables_msg_types enum in https://git.netfilter.org/nftables/tree/include/linux/netfilter/nf_tables.h
NFT_MSG_DESTROYSETELEM = 0x1e
) )
// SetDatatype represents a datatype declared by nft. // SetDatatype represents a datatype declared by nft.
@ -391,6 +394,16 @@ func (cc *Conn) SetDeleteElements(s *Set, vals []SetElement) error {
return cc.appendElemList(s, vals, unix.NFT_MSG_DELSETELEM) return cc.appendElemList(s, vals, unix.NFT_MSG_DELSETELEM)
} }
// SetDestroyElements like SetDeleteElements, but not an error if setelement doesn't exists
func (cc *Conn) SetDestroyElements(s *Set, vals []SetElement) error {
cc.mu.Lock()
defer cc.mu.Unlock()
if s.Anonymous {
return errors.New("anonymous sets cannot be updated")
}
return cc.appendElemList(s, vals, NFT_MSG_DESTROYSETELEM)
}
// maxElemBatchSize is the maximum size in bytes of encoded set elements which // maxElemBatchSize is the maximum size in bytes of encoded set elements which
// are sent in one netlink message. The size field of a netlink attribute is a // are sent in one netlink message. The size field of a netlink attribute is a
// uint16, and 1024 bytes is more than enough for the per-message headers. // uint16, and 1024 bytes is more than enough for the per-message headers.
@ -826,8 +839,9 @@ func parseSetDatatype(magic uint32) (SetDatatype, error) {
} }
const ( const (
newElemHeaderType = netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_NEWSETELEM) newElemHeaderType = netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_NEWSETELEM)
delElemHeaderType = netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_DELSETELEM) delElemHeaderType = netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_DELSETELEM)
destroyElemHeaderType = netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | NFT_MSG_DESTROYSETELEM)
) )
func elementsFromMsg(fam byte, msg netlink.Message) ([]SetElement, error) { func elementsFromMsg(fam byte, msg netlink.Message) ([]SetElement, error) {

View File

@ -24,6 +24,10 @@ import (
const ( const (
newTableHeaderType = netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_NEWTABLE) newTableHeaderType = netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_NEWTABLE)
delTableHeaderType = netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_DELTABLE) delTableHeaderType = netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_DELTABLE)
// FIXME: in sys@v0.34.0 no unix.NFT_MSG_DESTROYTABLE const yet.
// See nf_tables_msg_types enum in https://git.netfilter.org/nftables/tree/include/linux/netfilter/nf_tables.h
destroyTableHeaderType = netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | 0x1a)
) )
// TableFamily specifies the address family for this table. // TableFamily specifies the address family for this table.
@ -51,15 +55,25 @@ type Table struct {
// DelTable deletes a specific table, along with all chains/rules it contains. // DelTable deletes a specific table, along with all chains/rules it contains.
func (cc *Conn) DelTable(t *Table) { func (cc *Conn) DelTable(t *Table) {
cc.delTable(t, delTableHeaderType)
}
// DestroyTable is like DelTable, but not an error if table doesn't exists
func (cc *Conn) DestroyTable(t *Table) {
cc.delTable(t, destroyTableHeaderType)
}
func (cc *Conn) delTable(t *Table, hdrType netlink.HeaderType) {
cc.mu.Lock() cc.mu.Lock()
defer cc.mu.Unlock() defer cc.mu.Unlock()
data := cc.marshalAttr([]netlink.Attribute{ data := cc.marshalAttr([]netlink.Attribute{
{Type: unix.NFTA_TABLE_NAME, Data: []byte(t.Name + "\x00")}, {Type: unix.NFTA_TABLE_NAME, Data: []byte(t.Name + "\x00")},
{Type: unix.NFTA_TABLE_FLAGS, Data: []byte{0, 0, 0, 0}}, {Type: unix.NFTA_TABLE_FLAGS, Data: []byte{0, 0, 0, 0}},
}) })
cc.messages = append(cc.messages, netlinkMessage{ cc.messages = append(cc.messages, netlinkMessage{
Header: netlink.Header{ Header: netlink.Header{
Type: netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_DELTABLE), Type: hdrType,
Flags: netlink.Request | netlink.Acknowledge, Flags: netlink.Request | netlink.Acknowledge,
}, },
Data: append(extraHeader(uint8(t.Family), 0), data...), Data: append(extraHeader(uint8(t.Family), 0), data...),