From 919abdc34fbc29b73b218046b653cc124c3cd08b Mon Sep 17 00:00:00 2001 From: Nikita Vorontsov Date: Thu, 7 Aug 2025 12:03:00 +0300 Subject: [PATCH 1/4] add DelTable(force) --- nftables_test.go | 42 ++++++++++++++++++++++++++++++++++++++++++ table.go | 16 ++++++++++++++-- 2 files changed, 56 insertions(+), 2 deletions(-) diff --git a/nftables_test.go b/nftables_test.go index fe12566..867e529 100644 --- a/nftables_test.go +++ b/nftables_test.go @@ -128,6 +128,48 @@ func ifname(n string) []byte { 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.DelTable(filter, true) + 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.DelTable(filter) + err = c.Flush() + if err != nil { + t.Fatalf("on Flush: %q", err.Error()) + } + + if LookupMyTable() { + t.Fatal("DelTable doesn't delete my table!") + } + + c.DelTable(filter, true) // just for test that 'force' ignore error 'not found' +} + func TestRuleOperations(t *testing.T) { // Create a new network namespace to test these operations, // and tear down the namespace at test completion. diff --git a/table.go b/table.go index 3686b7a..43390cd 100644 --- a/table.go +++ b/table.go @@ -16,6 +16,7 @@ package nftables import ( "fmt" + "slices" "github.com/mdlayher/netlink" "golang.org/x/sys/unix" @@ -24,6 +25,10 @@ import ( const ( newTableHeaderType = netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_NEWTABLE) 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. @@ -50,16 +55,23 @@ type Table struct { } // DelTable deletes a specific table, along with all chains/rules it contains. -func (cc *Conn) DelTable(t *Table) { +func (cc *Conn) DelTable(t *Table, force ...bool) { cc.mu.Lock() defer cc.mu.Unlock() data := cc.marshalAttr([]netlink.Attribute{ {Type: unix.NFTA_TABLE_NAME, Data: []byte(t.Name + "\x00")}, {Type: unix.NFTA_TABLE_FLAGS, Data: []byte{0, 0, 0, 0}}, }) + + var hdrType netlink.HeaderType + if slices.Contains(force, true) { + hdrType = destroyTableHeaderType + } else { + hdrType = delTableHeaderType + } cc.messages = append(cc.messages, netlinkMessage{ Header: netlink.Header{ - Type: netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_DELTABLE), + Type: hdrType, Flags: netlink.Request | netlink.Acknowledge, }, Data: append(extraHeader(uint8(t.Family), 0), data...), From 3c0f824cccb3469be45897d9aff4bae98f8fa685 Mon Sep 17 00:00:00 2001 From: Nikita Vorontsov Date: Thu, 28 Aug 2025 16:48:33 +0300 Subject: [PATCH 2/4] CR fixes --- nftables_test.go | 11 +++++------ table.go | 18 ++++++++++-------- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/nftables_test.go b/nftables_test.go index 867e529..844f481 100644 --- a/nftables_test.go +++ b/nftables_test.go @@ -137,7 +137,7 @@ func TestTableCreateDestroy(t *testing.T) { Family: nftables.TableFamilyIPv4, Name: "filter", } - c.DelTable(filter, true) + c.DestroyTable(filter) c.AddTable(filter) err := c.Flush() if err != nil { @@ -157,17 +157,16 @@ func TestTableCreateDestroy(t *testing.T) { t.Fatal("AddTable doesn't create my table!") } - c.DelTable(filter) - err = c.Flush() - if err != nil { + c.DestroyTable(filter) + if err = c.Flush(); err != nil { t.Fatalf("on Flush: %q", err.Error()) } if LookupMyTable() { - t.Fatal("DelTable doesn't delete my table!") + t.Fatal("DestroyTable doesn't delete my table!") } - c.DelTable(filter, true) // just for test that 'force' ignore error 'not found' + c.DestroyTable(filter) // just for test that 'destroy' ignore error 'not found' } func TestRuleOperations(t *testing.T) { diff --git a/table.go b/table.go index 43390cd..ce737a6 100644 --- a/table.go +++ b/table.go @@ -16,7 +16,6 @@ package nftables import ( "fmt" - "slices" "github.com/mdlayher/netlink" "golang.org/x/sys/unix" @@ -55,7 +54,16 @@ type Table struct { } // DelTable deletes a specific table, along with all chains/rules it contains. -func (cc *Conn) DelTable(t *Table, force ...bool) { +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() defer cc.mu.Unlock() data := cc.marshalAttr([]netlink.Attribute{ @@ -63,12 +71,6 @@ func (cc *Conn) DelTable(t *Table, force ...bool) { {Type: unix.NFTA_TABLE_FLAGS, Data: []byte{0, 0, 0, 0}}, }) - var hdrType netlink.HeaderType - if slices.Contains(force, true) { - hdrType = destroyTableHeaderType - } else { - hdrType = delTableHeaderType - } cc.messages = append(cc.messages, netlinkMessage{ Header: netlink.Header{ Type: hdrType, From 6baf2cbc05140271796e979ba974979103c31aa6 Mon Sep 17 00:00:00 2001 From: Nikita Vorontsov Date: Thu, 28 Aug 2025 16:50:04 +0300 Subject: [PATCH 3/4] spell fixes --- nftables_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/nftables_test.go b/nftables_test.go index 844f481..b6d0f1c 100644 --- a/nftables_test.go +++ b/nftables_test.go @@ -144,7 +144,7 @@ func TestTableCreateDestroy(t *testing.T) { t.Fatalf("on Flush: %q", err.Error()) } - LookupMyTable := func() bool { + lookupMyTable := func() bool { ts, err := c.ListTables() if err != nil { t.Fatalf("on ListTables: %q", err.Error()) @@ -153,7 +153,7 @@ func TestTableCreateDestroy(t *testing.T) { return t.Name == filter.Name && t.Family == filter.Family }) } - if !LookupMyTable() { + if !lookupMyTable() { t.Fatal("AddTable doesn't create my table!") } @@ -162,7 +162,7 @@ func TestTableCreateDestroy(t *testing.T) { t.Fatalf("on Flush: %q", err.Error()) } - if LookupMyTable() { + if lookupMyTable() { t.Fatal("DestroyTable doesn't delete my table!") } From 1f697bf868f3908c5c69772603aad3e40396ba80 Mon Sep 17 00:00:00 2001 From: Nikita Vorontsov Date: Thu, 28 Aug 2025 17:13:46 +0300 Subject: [PATCH 4/4] add SetDestroyElements --- nftables_test.go | 18 +++++++++++++++++- set.go | 18 ++++++++++++++++-- 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/nftables_test.go b/nftables_test.go index b6d0f1c..25c8148 100644 --- a/nftables_test.go +++ b/nftables_test.go @@ -3818,7 +3818,7 @@ func TestDeleteElementNamedSet(t *testing.T) { Name: "test", 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) } if err := c.Flush(); err != nil { @@ -3835,6 +3835,22 @@ func TestDeleteElementNamedSet(t *testing.T) { if err != nil { 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 { t.Fatalf("len(elems) = %d, want 1", len(elems)) } diff --git a/set.go b/set.go index e320117..dc97c4e 100644 --- a/set.go +++ b/set.go @@ -44,6 +44,9 @@ const ( NFTA_SET_ELEM_KEY_END = 10 // https://git.netfilter.org/nftables/tree/include/linux/netfilter/nf_tables.h?id=d1289bff58e1878c3162f574c603da993e29b113#n429 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. @@ -391,6 +394,16 @@ func (cc *Conn) SetDeleteElements(s *Set, vals []SetElement) error { 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 // 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. @@ -826,8 +839,9 @@ func parseSetDatatype(magic uint32) (SetDatatype, error) { } const ( - newElemHeaderType = netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_NEWSETELEM) - delElemHeaderType = netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_DELSETELEM) + newElemHeaderType = netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_NEWSETELEM) + 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) {