go.mod: update nftables and drop patches

I have upstreamed these patches.

Change-Id: I27abbea4fa3ce25450f2e14a7247ddc9eeff676a
Reviewed-on: https://review.monogon.dev/c/monogon/+/4041
Tested-by: Jenkins CI
Reviewed-by: Tim Windelschmidt <tim@monogon.tech>
diff --git a/build/bazel/go.MODULE.bazel b/build/bazel/go.MODULE.bazel
index 9e06933..d8836c4 100644
--- a/build/bazel/go.MODULE.bazel
+++ b/build/bazel/go.MODULE.bazel
@@ -332,8 +332,6 @@
     "github.com/google/nftables": {
         "pre_patches": [
             "//third_party/com_github_google_nftables:nftables-dynamic-exprs.patch",
-            "//third_party/com_github_google_nftables:nftables-rule-handle.patch",
-            "//third_party/com_github_google_nftables:nftables-element-batching.patch",
         ],
     },
     "k8s.io/kubectl": {
diff --git a/go.mod b/go.mod
index 8e5effe..6c3213c 100644
--- a/go.mod
+++ b/go.mod
@@ -76,7 +76,7 @@
 	github.com/google/go-tpm v0.3.3
 	github.com/google/go-tpm-tools v0.3.5
 	github.com/google/gopacket v1.1.19
-	github.com/google/nftables v0.3.0
+	github.com/google/nftables v0.3.1-0.20250326085720-8095c5167898
 	github.com/google/uuid v1.6.0
 	github.com/iancoleman/strcase v0.3.0
 	github.com/improbable-eng/grpc-web v0.15.0
@@ -121,12 +121,12 @@
 	go.uber.org/multierr v1.11.0
 	go.uber.org/zap v1.27.0
 	go4.org/netipx v0.0.0-20231129151722-fdeea329fbba
-	golang.org/x/crypto v0.33.0
-	golang.org/x/net v0.35.0
-	golang.org/x/sync v0.11.0
-	golang.org/x/sys v0.30.0
-	golang.org/x/term v0.29.0
-	golang.org/x/text v0.22.0
+	golang.org/x/crypto v0.36.0
+	golang.org/x/net v0.37.0
+	golang.org/x/sync v0.12.0
+	golang.org/x/sys v0.31.0
+	golang.org/x/term v0.30.0
+	golang.org/x/text v0.23.0
 	golang.org/x/time v0.10.0
 	golang.org/x/tools v0.30.0
 	golang.zx2c4.com/wireguard/wgctrl v0.0.0-20220208144051-fde48d68ee68
diff --git a/go.sum b/go.sum
index d802710..d828426 100644
--- a/go.sum
+++ b/go.sum
@@ -2369,8 +2369,8 @@
 github.com/google/martian/v3 v3.3.3/go.mod h1:iEPrYcgCF7jA9OtScMFQyAlZZ4YXTKEtJ1E6RWzmBA0=
 github.com/google/nftables v0.0.0-20190906062827-5d14089d2edc/go.mod h1:DfTD7lq9Gq5pLrgCmJDbGtrcWF/h7i5XWgEC/6bQu0s=
 github.com/google/nftables v0.0.0-20200316075819-7127d9d22474/go.mod h1:cfspEyr/Ap+JDIITA+N9a0ernqG0qZ4W1aqMRgDZa1g=
-github.com/google/nftables v0.3.0 h1:bkyZ0cbpVeMHXOrtlFc8ISmfVqq5gPJukoYieyVmITg=
-github.com/google/nftables v0.3.0/go.mod h1:BCp9FsrbF1Fn/Yu6CLUc9GGZFw/+hsxfluNXXmxBfRM=
+github.com/google/nftables v0.3.1-0.20250326085720-8095c5167898 h1:kdcBh1HNp+qSEd1leJYC/voCJu4xmPzvr/xWDwcwDAk=
+github.com/google/nftables v0.3.1-0.20250326085720-8095c5167898/go.mod h1:M2FzQ4PnNg7LkLoSXwnIozHWJb8P86rclqioWO6Pgeg=
 github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
 github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
 github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
@@ -3835,8 +3835,8 @@
 golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
 golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
 golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
-golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus=
-golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
+golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
+golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
 golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@@ -4036,8 +4036,8 @@
 golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
 golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg=
 golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=
-golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
-golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
+golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c=
+golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
 golang.org/x/oauth2 v0.0.0-20180227000427-d7d64896b5ff/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@@ -4112,8 +4112,8 @@
 golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
 golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
 golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
-golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
-golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
+golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
+golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
 golang.org/x/sys v0.0.0-20180224232135-f6cff0780e54/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -4315,8 +4315,8 @@
 golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
-golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
-golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
+golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
 golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
 golang.org/x/telemetry v0.0.0-20240521205824-bda55230c457/go.mod h1:pRgIJT+bRLFKnoM1ldnzKoxTIn14Yxz928LQRYYgIN0=
 golang.org/x/telemetry v0.0.0-20241106142447-58a1122356f5 h1:TCDqnvbBsFapViksHcHySl/sW4+rTGNIAoJJesHRuMM=
@@ -4349,8 +4349,8 @@
 golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4=
 golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk=
 golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M=
-golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU=
-golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s=
+golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y=
+golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g=
 golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -4376,8 +4376,8 @@
 golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
 golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
 golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
-golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
-golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
+golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
+golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
 golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
diff --git a/third_party/com_github_google_nftables/nftables-element-batching.patch b/third_party/com_github_google_nftables/nftables-element-batching.patch
deleted file mode 100644
index 4723b16..0000000
--- a/third_party/com_github_google_nftables/nftables-element-batching.patch
+++ /dev/null
@@ -1,232 +0,0 @@
-commit 0a454ac56a5f6e9343e0bfafa31fd63d5dc831b5
-Author: Jan Schär <jan@monogon.tech>
-Date:   Wed Feb 26 18:27:57 2025 +0100
-
-    Split set elements into batches if needed
-    
-    If the number of elements to be added to or removed from a set is large,
-    they may not all fit into one message, because the size field of a
-    netlink attribute is a uint16 and would overflow. To support this case,
-    the elements need to be split into multiple batches.
-    
-    Upstream PR: https://github.com/google/nftables/pull/303
-
-diff --git a/set.go b/set.go
-index 412d75a..4d1dcae 100644
---- a/set.go
-+++ b/set.go
-@@ -375,24 +375,31 @@ func (cc *Conn) SetAddElements(s *Set, vals []SetElement) error {
- 	if s.Anonymous {
- 		return errors.New("anonymous sets cannot be updated")
- 	}
-+	return cc.appendElemList(s, vals, unix.NFT_MSG_NEWSETELEM)
-+}
- 
--	elements, err := s.makeElemList(vals, s.ID)
--	if err != nil {
--		return err
-+// SetDeleteElements deletes data points from an nftables set.
-+func (cc *Conn) SetDeleteElements(s *Set, vals []SetElement) error {
-+	cc.mu.Lock()
-+	defer cc.mu.Unlock()
-+	if s.Anonymous {
-+		return errors.New("anonymous sets cannot be updated")
- 	}
--	cc.messages = append(cc.messages, netlinkMessage{
--		Header: netlink.Header{
--			Type:  netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_NEWSETELEM),
--			Flags: netlink.Request | netlink.Acknowledge | netlink.Create,
--		},
--		Data: append(extraHeader(uint8(s.Table.Family), 0), cc.marshalAttr(elements)...),
--	})
--
--	return nil
-+	return cc.appendElemList(s, vals, unix.NFT_MSG_DELSETELEM)
- }
- 
--func (s *Set) makeElemList(vals []SetElement, id uint32) ([]netlink.Attribute, error) {
-+// 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.
-+const maxElemBatchSize = 0x10000 - 1024
-+
-+func (cc *Conn) appendElemList(s *Set, vals []SetElement, hdrType uint16) error {
-+	if len(vals) == 0 {
-+		return nil
-+	}
- 	var elements []netlink.Attribute
-+	batchSize := 0
-+	var batches [][]netlink.Attribute
- 
- 	for i, v := range vals {
- 		item := make([]netlink.Attribute, 0)
-@@ -404,14 +411,14 @@ func (s *Set) makeElemList(vals []SetElement, id uint32) ([]netlink.Attribute, e
- 
- 		encodedKey, err := netlink.MarshalAttributes([]netlink.Attribute{{Type: unix.NFTA_DATA_VALUE, Data: v.Key}})
- 		if err != nil {
--			return nil, fmt.Errorf("marshal key %d: %v", i, err)
-+			return fmt.Errorf("marshal key %d: %v", i, err)
- 		}
- 
- 		item = append(item, netlink.Attribute{Type: unix.NFTA_SET_ELEM_KEY | unix.NLA_F_NESTED, Data: encodedKey})
- 		if len(v.KeyEnd) > 0 {
- 			encodedKeyEnd, err := netlink.MarshalAttributes([]netlink.Attribute{{Type: unix.NFTA_DATA_VALUE, Data: v.KeyEnd}})
- 			if err != nil {
--				return nil, fmt.Errorf("marshal key end %d: %v", i, err)
-+				return fmt.Errorf("marshal key end %d: %v", i, err)
- 			}
- 			item = append(item, netlink.Attribute{Type: NFTA_SET_ELEM_KEY_END | unix.NLA_F_NESTED, Data: encodedKeyEnd})
- 		}
-@@ -431,7 +438,7 @@ func (s *Set) makeElemList(vals []SetElement, id uint32) ([]netlink.Attribute, e
- 				{Type: unix.NFTA_DATA_VALUE, Data: binaryutil.BigEndian.PutUint32(uint32(v.VerdictData.Kind))},
- 			})
- 			if err != nil {
--				return nil, fmt.Errorf("marshal item %d: %v", i, err)
-+				return fmt.Errorf("marshal item %d: %v", i, err)
- 			}
- 			encodedVal = append(encodedVal, encodedKind...)
- 			if len(v.VerdictData.Chain) != 0 {
-@@ -439,21 +446,21 @@ func (s *Set) makeElemList(vals []SetElement, id uint32) ([]netlink.Attribute, e
- 					{Type: unix.NFTA_SET_ELEM_DATA, Data: []byte(v.VerdictData.Chain + "\x00")},
- 				})
- 				if err != nil {
--					return nil, fmt.Errorf("marshal item %d: %v", i, err)
-+					return fmt.Errorf("marshal item %d: %v", i, err)
- 				}
- 				encodedVal = append(encodedVal, encodedChain...)
- 			}
- 			encodedVerdict, err := netlink.MarshalAttributes([]netlink.Attribute{
- 				{Type: unix.NFTA_SET_ELEM_DATA | unix.NLA_F_NESTED, Data: encodedVal}})
- 			if err != nil {
--				return nil, fmt.Errorf("marshal item %d: %v", i, err)
-+				return fmt.Errorf("marshal item %d: %v", i, err)
- 			}
- 			item = append(item, netlink.Attribute{Type: unix.NFTA_SET_ELEM_DATA | unix.NLA_F_NESTED, Data: encodedVerdict})
- 		case len(v.Val) > 0:
- 			// Since v.Val's length is not 0 then, v is a regular map element, need to add to the attributes
- 			encodedVal, err := netlink.MarshalAttributes([]netlink.Attribute{{Type: unix.NFTA_DATA_VALUE, Data: v.Val}})
- 			if err != nil {
--				return nil, fmt.Errorf("marshal item %d: %v", i, err)
-+				return fmt.Errorf("marshal item %d: %v", i, err)
- 			}
- 
- 			item = append(item, netlink.Attribute{Type: unix.NFTA_SET_ELEM_DATA | unix.NLA_F_NESTED, Data: encodedVal})
-@@ -469,22 +476,42 @@ func (s *Set) makeElemList(vals []SetElement, id uint32) ([]netlink.Attribute, e
- 
- 		encodedItem, err := netlink.MarshalAttributes(item)
- 		if err != nil {
--			return nil, fmt.Errorf("marshal item %d: %v", i, err)
-+			return fmt.Errorf("marshal item %d: %v", i, err)
-+		}
-+
-+		itemSize := unix.NLA_HDRLEN + len(encodedItem)
-+		if batchSize+itemSize > maxElemBatchSize {
-+			batches = append(batches, elements)
-+			elements = nil
-+			batchSize = 0
- 		}
- 		elements = append(elements, netlink.Attribute{Type: uint16(i+1) | unix.NLA_F_NESTED, Data: encodedItem})
-+		batchSize += itemSize
- 	}
-+	batches = append(batches, elements)
- 
--	encodedElem, err := netlink.MarshalAttributes(elements)
--	if err != nil {
--		return nil, fmt.Errorf("marshal elements: %v", err)
--	}
-+	for _, batch := range batches {
-+		encodedElem, err := netlink.MarshalAttributes(batch)
-+		if err != nil {
-+			return fmt.Errorf("marshal elements: %v", err)
-+		}
- 
--	return []netlink.Attribute{
--		{Type: unix.NFTA_SET_NAME, Data: []byte(s.Name + "\x00")},
--		{Type: unix.NFTA_LOOKUP_SET_ID, Data: binaryutil.BigEndian.PutUint32(id)},
--		{Type: unix.NFTA_SET_TABLE, Data: []byte(s.Table.Name + "\x00")},
--		{Type: unix.NFTA_SET_ELEM_LIST_ELEMENTS | unix.NLA_F_NESTED, Data: encodedElem},
--	}, nil
-+		message := []netlink.Attribute{
-+			{Type: unix.NFTA_SET_ELEM_LIST_SET, Data: []byte(s.Name + "\x00")},
-+			{Type: unix.NFTA_SET_ELEM_LIST_SET_ID, Data: binaryutil.BigEndian.PutUint32(s.ID)},
-+			{Type: unix.NFTA_SET_ELEM_LIST_TABLE, Data: []byte(s.Table.Name + "\x00")},
-+			{Type: unix.NFTA_SET_ELEM_LIST_ELEMENTS | unix.NLA_F_NESTED, Data: encodedElem},
-+		}
-+
-+		cc.messages = append(cc.messages, netlinkMessage{
-+			Header: netlink.Header{
-+				Type:  netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | hdrType),
-+				Flags: netlink.Request | netlink.Acknowledge | netlink.Create,
-+			},
-+			Data: append(extraHeader(uint8(s.Table.Family), 0), cc.marshalAttr(message)...),
-+		})
-+	}
-+	return nil
- }
- 
- // AddSet adds the specified Set.
-@@ -659,22 +686,7 @@ func (cc *Conn) AddSet(s *Set, vals []SetElement) error {
- 	})
- 
- 	// Set the values of the set if initial values were provided.
--	if len(vals) > 0 {
--		hdrType := unix.NFT_MSG_NEWSETELEM
--		elements, err := s.makeElemList(vals, s.ID)
--		if err != nil {
--			return err
--		}
--		cc.messages = append(cc.messages, netlinkMessage{
--			Header: netlink.Header{
--				Type:  netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | hdrType),
--				Flags: netlink.Request | netlink.Acknowledge | netlink.Create,
--			},
--			Data: append(extraHeader(uint8(s.Table.Family), 0), cc.marshalAttr(elements)...),
--		})
--	}
--
--	return nil
-+	return cc.appendElemList(s, vals, unix.NFT_MSG_NEWSETELEM)
- }
- 
- // DelSet deletes a specific set, along with all elements it contains.
-@@ -694,29 +706,6 @@ func (cc *Conn) DelSet(s *Set) {
- 	})
- }
- 
--// SetDeleteElements deletes data points from an nftables set.
--func (cc *Conn) SetDeleteElements(s *Set, vals []SetElement) error {
--	cc.mu.Lock()
--	defer cc.mu.Unlock()
--	if s.Anonymous {
--		return errors.New("anonymous sets cannot be updated")
--	}
--
--	elements, err := s.makeElemList(vals, s.ID)
--	if err != nil {
--		return err
--	}
--	cc.messages = append(cc.messages, netlinkMessage{
--		Header: netlink.Header{
--			Type:  netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_DELSETELEM),
--			Flags: netlink.Request | netlink.Acknowledge | netlink.Create,
--		},
--		Data: append(extraHeader(uint8(s.Table.Family), 0), cc.marshalAttr(elements)...),
--	})
--
--	return nil
--}
--
- // FlushSet deletes all data points from an nftables set.
- func (cc *Conn) FlushSet(s *Set) {
- 	cc.mu.Lock()
-@@ -972,8 +961,8 @@ func (cc *Conn) GetSetElements(s *Set) ([]SetElement, error) {
- 	defer func() { _ = closer() }()
- 
- 	data, err := netlink.MarshalAttributes([]netlink.Attribute{
--		{Type: unix.NFTA_SET_TABLE, Data: []byte(s.Table.Name + "\x00")},
--		{Type: unix.NFTA_SET_NAME, Data: []byte(s.Name + "\x00")},
-+		{Type: unix.NFTA_SET_ELEM_LIST_TABLE, Data: []byte(s.Table.Name + "\x00")},
-+		{Type: unix.NFTA_SET_ELEM_LIST_SET, Data: []byte(s.Name + "\x00")},
- 	})
- 	if err != nil {
- 		return nil, err
diff --git a/third_party/com_github_google_nftables/nftables-rule-handle.patch b/third_party/com_github_google_nftables/nftables-rule-handle.patch
deleted file mode 100644
index df00fef..0000000
--- a/third_party/com_github_google_nftables/nftables-rule-handle.patch
+++ /dev/null
@@ -1,636 +0,0 @@
-commit b230daafa27f7cf22c9b9795aee2f0116f108a70
-Author: Jan Schär <jan@monogon.tech>
-Date:   Mon Feb 24 10:52:11 2025 +0100
-
-    Set rule handle during flush
-    
-    This change makes it possible to delete rules after inserting them,
-    without needing to query the rules first. Rules can be deleted both
-    before and after they are flushed. Additionally, this allows positioning
-    a new rule next to an existing rule, both before and after the existing
-    rule is flushed.
-    
-    Upstream PR: https://github.com/google/nftables/pull/299
-
-diff --git a/chain.go b/chain.go
-index 4f4c0a5..f1853cf 100644
---- a/chain.go
-+++ b/chain.go
-@@ -140,7 +140,7 @@ func (cc *Conn) AddChain(c *Chain) *Chain {
- 			{Type: unix.NFTA_CHAIN_TYPE, Data: []byte(c.Type + "\x00")},
- 		})...)
- 	}
--	cc.messages = append(cc.messages, netlink.Message{
-+	cc.messages = append(cc.messages, netlinkMessage{
- 		Header: netlink.Header{
- 			Type:  netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_NEWCHAIN),
- 			Flags: netlink.Request | netlink.Acknowledge | netlink.Create,
-@@ -161,7 +161,7 @@ func (cc *Conn) DelChain(c *Chain) {
- 		{Type: unix.NFTA_CHAIN_NAME, Data: []byte(c.Name + "\x00")},
- 	})
- 
--	cc.messages = append(cc.messages, netlink.Message{
-+	cc.messages = append(cc.messages, netlinkMessage{
- 		Header: netlink.Header{
- 			Type:  netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_DELCHAIN),
- 			Flags: netlink.Request | netlink.Acknowledge,
-@@ -179,7 +179,7 @@ func (cc *Conn) FlushChain(c *Chain) {
- 		{Type: unix.NFTA_RULE_TABLE, Data: []byte(c.Table.Name + "\x00")},
- 		{Type: unix.NFTA_RULE_CHAIN, Data: []byte(c.Name + "\x00")},
- 	})
--	cc.messages = append(cc.messages, netlink.Message{
-+	cc.messages = append(cc.messages, netlinkMessage{
- 		Header: netlink.Header{
- 			Type:  netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_DELRULE),
- 			Flags: netlink.Request | netlink.Acknowledge,
-diff --git a/conn.go b/conn.go
-index fef9c2a..6b10844 100644
---- a/conn.go
-+++ b/conn.go
-@@ -17,6 +17,7 @@ package nftables
- import (
- 	"errors"
- 	"fmt"
-+	"math"
- 	"os"
- 	"sync"
- 	"syscall"
-@@ -38,12 +39,20 @@ type Conn struct {
- 	TestDial nltest.Func // for testing only; passed to nltest.Dial
- 	NetNS    int         // fd referencing the network namespace netlink will interact with.
- 
--	lasting     bool       // establish a lasting connection to be used across multiple netlink operations.
--	mu          sync.Mutex // protects the following state
--	messages    []netlink.Message
--	err         error
--	nlconn      *netlink.Conn // netlink socket using NETLINK_NETFILTER protocol.
--	sockOptions []SockOption
-+	lasting      bool       // establish a lasting connection to be used across multiple netlink operations.
-+	mu           sync.Mutex // protects the following state
-+	messages     []netlinkMessage
-+	err          error
-+	nlconn       *netlink.Conn // netlink socket using NETLINK_NETFILTER protocol.
-+	sockOptions  []SockOption
-+	lastID       uint32
-+	allocatedIDs uint32
-+}
-+
-+type netlinkMessage struct {
-+	Header      netlink.Header
-+	Data        []byte
-+	handleReply func(reply netlink.Message) error
- }
- 
- // ConnOption is an option to change the behavior of the nftables Conn returned by Open.
-@@ -168,24 +177,6 @@ func receiveAckAware(nlconn *netlink.Conn, sentMsgFlags netlink.HeaderFlags) ([]
- 		return reply, nil
- 	}
- 
--	if len(reply) != 0 {
--		last := reply[len(reply)-1]
--		for re := last.Header.Type; (re&netlink.Overrun) == netlink.Overrun && (re&netlink.Done) != netlink.Done; re = last.Header.Type {
--			// we are not finished, the message is overrun
--			r, err := nlconn.Receive()
--			if err != nil {
--				return nil, err
--			}
--			reply = append(reply, r...)
--			last = reply[len(reply)-1]
--		}
--
--		if last.Header.Type == netlink.Error && binaryutil.BigEndian.Uint32(last.Data[:4]) == 0 {
--			// we have already collected an ack
--			return reply, nil
--		}
--	}
--
- 	// Now we expect an ack
- 	ack, err := nlconn.Receive()
- 	if err != nil {
-@@ -193,8 +184,7 @@ func receiveAckAware(nlconn *netlink.Conn, sentMsgFlags netlink.HeaderFlags) ([]
- 	}
- 
- 	if len(ack) == 0 {
--		// received an empty ack?
--		return reply, nil
-+		return nil, errors.New("received an empty ack")
- 	}
- 
- 	msg := ack[0]
-@@ -244,6 +234,7 @@ func (cc *Conn) Flush() error {
- 	cc.mu.Lock()
- 	defer func() {
- 		cc.messages = nil
-+		cc.allocatedIDs = 0
- 		cc.mu.Unlock()
- 	}()
- 	if len(cc.messages) == 0 {
-@@ -259,15 +250,53 @@ func (cc *Conn) Flush() error {
- 	}
- 	defer func() { _ = closer() }()
- 
--	if _, err := conn.SendMessages(batch(cc.messages)); err != nil {
-+	messages, err := conn.SendMessages(batch(cc.messages))
-+	if err != nil {
- 		return fmt.Errorf("SendMessages: %w", err)
- 	}
- 
- 	var errs error
-+
-+	// Fetch replies. Each message with the Echo flag triggers a reply of the same
-+	// type. Additionally, if the first message of the batch has the Echo flag, we
-+	// get a reply of type NFT_MSG_NEWGEN, which we ignore.
-+	replyIndex := 0
-+	for replyIndex < len(cc.messages) && cc.messages[replyIndex].Header.Flags&netlink.Echo == 0 {
-+		replyIndex++
-+	}
-+	replies, err := conn.Receive()
-+	for err == nil && len(replies) != 0 {
-+		reply := replies[0]
-+		if reply.Header.Type == netlink.Error && reply.Header.Sequence == messages[1].Header.Sequence {
-+			// The next message is the acknowledgement for the first message in the
-+			// batch; stop looking for replies.
-+			break
-+		} else if replyIndex < len(cc.messages) {
-+			msg := messages[replyIndex+1]
-+			if msg.Header.Sequence == reply.Header.Sequence && msg.Header.Type == reply.Header.Type {
-+				err := cc.messages[replyIndex].handleReply(reply)
-+				if err != nil {
-+					errs = errors.Join(errs, err)
-+				}
-+				replyIndex++
-+				for replyIndex < len(cc.messages) && cc.messages[replyIndex].Header.Flags&netlink.Echo == 0 {
-+					replyIndex++
-+				}
-+			}
-+		}
-+		replies = replies[1:]
-+		if len(replies) == 0 {
-+			replies, err = conn.Receive()
-+		}
-+	}
-+
- 	// Fetch the requested acknowledgement for each message we sent.
--	for _, msg := range cc.messages {
--		if _, err := receiveAckAware(conn, msg.Header.Flags); err != nil {
--			if errors.Is(err, os.ErrPermission) || errors.Is(err, syscall.ENOBUFS) {
-+	for i := range cc.messages {
-+		if i != 0 {
-+			_, err = conn.Receive()
-+		}
-+		if err != nil {
-+			if errors.Is(err, os.ErrPermission) || errors.Is(err, syscall.ENOBUFS) || errors.Is(err, syscall.ENOMEM) {
- 				// Kernel will only send one error to user space.
- 				return err
- 			}
-@@ -278,6 +307,9 @@ func (cc *Conn) Flush() error {
- 	if errs != nil {
- 		return fmt.Errorf("conn.Receive: %w", errs)
- 	}
-+	if replyIndex < len(cc.messages) {
-+		return fmt.Errorf("missing reply for message %d in batch", replyIndex)
-+	}
- 
- 	return nil
- }
-@@ -287,7 +319,7 @@ func (cc *Conn) Flush() error {
- func (cc *Conn) FlushRuleset() {
- 	cc.mu.Lock()
- 	defer cc.mu.Unlock()
--	cc.messages = append(cc.messages, netlink.Message{
-+	cc.messages = append(cc.messages, netlinkMessage{
- 		Header: netlink.Header{
- 			Type:  netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_DELTABLE),
- 			Flags: netlink.Request | netlink.Acknowledge | netlink.Create,
-@@ -346,26 +378,47 @@ func (cc *Conn) marshalExpr(fam byte, e expr.Any) []byte {
- 	return b
- }
- 
--func batch(messages []netlink.Message) []netlink.Message {
--	batch := []netlink.Message{
--		{
--			Header: netlink.Header{
--				Type:  netlink.HeaderType(unix.NFNL_MSG_BATCH_BEGIN),
--				Flags: netlink.Request,
--			},
--			Data: extraHeader(0, unix.NFNL_SUBSYS_NFTABLES),
-+func batch(messages []netlinkMessage) []netlink.Message {
-+	batch := make([]netlink.Message, len(messages)+2)
-+	batch[0] = netlink.Message{
-+		Header: netlink.Header{
-+			Type:  netlink.HeaderType(unix.NFNL_MSG_BATCH_BEGIN),
-+			Flags: netlink.Request,
- 		},
-+		Data: extraHeader(0, unix.NFNL_SUBSYS_NFTABLES),
- 	}
- 
--	batch = append(batch, messages...)
-+	for i, msg := range messages {
-+		batch[i+1] = netlink.Message{
-+			Header: msg.Header,
-+			Data:   msg.Data,
-+		}
-+	}
- 
--	batch = append(batch, netlink.Message{
-+	batch[len(messages)+1] = netlink.Message{
- 		Header: netlink.Header{
- 			Type:  netlink.HeaderType(unix.NFNL_MSG_BATCH_END),
- 			Flags: netlink.Request,
- 		},
- 		Data: extraHeader(0, unix.NFNL_SUBSYS_NFTABLES),
--	})
-+	}
- 
- 	return batch
- }
-+
-+// allocateTransactionID allocates an identifier which is only valid in the
-+// current transaction.
-+func (cc *Conn) allocateTransactionID() uint32 {
-+	if cc.allocatedIDs == math.MaxUint32 {
-+		panic(fmt.Sprintf("trying to allocate more than %d IDs in a single nftables transaction", math.MaxUint32))
-+	}
-+	// To make it more likely to catch when a transaction ID is erroneously used
-+	// in a later transaction, cc.lastID is not reset after each transaction;
-+	// instead it is only reset once it rolls over from math.MaxUint32 to 0.
-+	cc.allocatedIDs++
-+	cc.lastID++
-+	if cc.lastID == 0 {
-+		cc.lastID = 1
-+	}
-+	return cc.lastID
-+}
-diff --git a/flowtable.go b/flowtable.go
-index 93dbcb5..a35712f 100644
---- a/flowtable.go
-+++ b/flowtable.go
-@@ -142,7 +142,7 @@ func (cc *Conn) AddFlowtable(f *Flowtable) *Flowtable {
- 		{Type: unix.NLA_F_NESTED | NFTA_FLOWTABLE_HOOK, Data: cc.marshalAttr(hookAttr)},
- 	})...)
- 
--	cc.messages = append(cc.messages, netlink.Message{
-+	cc.messages = append(cc.messages, netlinkMessage{
- 		Header: netlink.Header{
- 			Type:  netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | NFT_MSG_NEWFLOWTABLE),
- 			Flags: netlink.Request | netlink.Acknowledge | netlink.Create,
-@@ -162,7 +162,7 @@ func (cc *Conn) DelFlowtable(f *Flowtable) {
- 		{Type: NFTA_FLOWTABLE_NAME, Data: []byte(f.Name)},
- 	})
- 
--	cc.messages = append(cc.messages, netlink.Message{
-+	cc.messages = append(cc.messages, netlinkMessage{
- 		Header: netlink.Header{
- 			Type:  netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | NFT_MSG_DELFLOWTABLE),
- 			Flags: netlink.Request | netlink.Acknowledge,
-diff --git a/obj.go b/obj.go
-index 3fcd6d7..60d6f76 100644
---- a/obj.go
-+++ b/obj.go
-@@ -124,7 +124,7 @@ func (cc *Conn) AddObj(o Obj) Obj {
- 		attrs = append(attrs, netlink.Attribute{Type: unix.NLA_F_NESTED | unix.NFTA_OBJ_DATA, Data: data})
- 	}
- 
--	cc.messages = append(cc.messages, netlink.Message{
-+	cc.messages = append(cc.messages, netlinkMessage{
- 		Header: netlink.Header{
- 			Type:  netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_NEWOBJ),
- 			Flags: netlink.Request | netlink.Acknowledge | netlink.Create,
-@@ -146,7 +146,7 @@ func (cc *Conn) DeleteObject(o Obj) {
- 	data := cc.marshalAttr(attrs)
- 	data = append(data, cc.marshalAttr([]netlink.Attribute{{Type: unix.NLA_F_NESTED | unix.NFTA_OBJ_DATA}})...)
- 
--	cc.messages = append(cc.messages, netlink.Message{
-+	cc.messages = append(cc.messages, netlinkMessage{
- 		Header: netlink.Header{
- 			Type:  netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_DELOBJ),
- 			Flags: netlink.Request | netlink.Acknowledge,
-diff --git a/rule.go b/rule.go
-index 0706834..7798150 100644
---- a/rule.go
-+++ b/rule.go
-@@ -30,6 +30,9 @@ var (
- 	delRuleHeaderType = netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_DELRULE)
- )
- 
-+// This constant is missing at unix.NFTA_RULE_POSITION_ID.
-+const nfta_rule_position_id = 0xa
-+
- type ruleOperation uint32
- 
- // Possible PayloadOperationType values.
-@@ -42,15 +45,27 @@ const (
- // A Rule does something with a packet. See also
- // https://wiki.nftables.org/wiki-nftables/index.php/Simple_rule_management
- type Rule struct {
--	Table    *Table
--	Chain    *Chain
-+	Table *Table
-+	Chain *Chain
-+	// Position can be set to the Handle of another Rule to insert the new Rule
-+	// before (InsertRule) or after (AddRule) the existing rule.
- 	Position uint64
--	Handle   uint64
- 	// The list of possible flags are specified by nftnl_rule_attr, see
- 	// https://git.netfilter.org/libnftnl/tree/include/libnftnl/rule.h#n21
- 	// Current nftables go implementation supports only
- 	// NFTNL_RULE_POSITION flag for setting rule at position 0
--	Flags    uint32
-+	Flags uint32
-+	// PositionID can be set to the ID of another Rule, same as Position, for when
-+	// the existing rule is not yet committed.
-+	PositionID uint32
-+	// Handle identifies an existing Rule. For a new Rule, this field is set
-+	// during the Flush() in which the rule is committed. Make sure to not access
-+	// this field concurrently with this Flush() to avoid data races.
-+	Handle uint64
-+	// ID is an identifier for a new Rule, which is assigned by
-+	// AddRule/InsertRule, and only valid before the rule is committed by Flush().
-+	// The field is set to 0 during Flush().
-+	ID       uint32
- 	Exprs    []expr.Any
- 	UserData []byte
- }
-@@ -81,7 +96,7 @@ func (cc *Conn) GetRules(t *Table, c *Chain) ([]*Rule, error) {
- 	message := netlink.Message{
- 		Header: netlink.Header{
- 			Type:  netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_GETRULE),
--			Flags: netlink.Request | netlink.Acknowledge | netlink.Dump | unix.NLM_F_ECHO,
-+			Flags: netlink.Request | netlink.Acknowledge | netlink.Dump,
- 		},
- 		Data: append(extraHeader(uint8(t.Family), 0), data...),
- 	}
-@@ -106,7 +121,6 @@ func (cc *Conn) GetRules(t *Table, c *Chain) ([]*Rule, error) {
- 	return rules, nil
- }
- 
--// AddRule adds the specified Rule
- func (cc *Conn) newRule(r *Rule, op ruleOperation) *Rule {
- 	cc.mu.Lock()
- 	defer cc.mu.Unlock()
-@@ -127,6 +141,11 @@ func (cc *Conn) newRule(r *Rule, op ruleOperation) *Rule {
- 		data = append(data, cc.marshalAttr([]netlink.Attribute{
- 			{Type: unix.NFTA_RULE_HANDLE, Data: binaryutil.BigEndian.PutUint64(r.Handle)},
- 		})...)
-+	} else {
-+		r.ID = cc.allocateTransactionID()
-+		data = append(data, cc.marshalAttr([]netlink.Attribute{
-+			{Type: unix.NFTA_RULE_ID, Data: binaryutil.BigEndian.PutUint32(r.ID)},
-+		})...)
- 	}
- 
- 	data = append(data, cc.marshalAttr([]netlink.Attribute{
-@@ -147,43 +166,77 @@ func (cc *Conn) newRule(r *Rule, op ruleOperation) *Rule {
- 	msgData := []byte{}
- 
- 	msgData = append(msgData, data...)
--	var flags netlink.HeaderFlags
- 	if r.UserData != nil {
- 		msgData = append(msgData, cc.marshalAttr([]netlink.Attribute{
- 			{Type: unix.NFTA_RULE_USERDATA, Data: r.UserData},
- 		})...)
- 	}
- 
-+	var flags netlink.HeaderFlags
-+	var handleReply func(reply netlink.Message) error
- 	switch op {
- 	case operationAdd:
--		flags = netlink.Request | netlink.Acknowledge | netlink.Create | unix.NLM_F_ECHO | unix.NLM_F_APPEND
-+		flags = netlink.Request | netlink.Acknowledge | netlink.Create | netlink.Echo | netlink.Append
-+		handleReply = r.handleCreateReply
- 	case operationInsert:
--		flags = netlink.Request | netlink.Acknowledge | netlink.Create | unix.NLM_F_ECHO
-+		flags = netlink.Request | netlink.Acknowledge | netlink.Create | netlink.Echo
-+		handleReply = r.handleCreateReply
- 	case operationReplace:
--		flags = netlink.Request | netlink.Acknowledge | netlink.Replace | unix.NLM_F_ECHO | unix.NLM_F_REPLACE
-+		flags = netlink.Request | netlink.Acknowledge | netlink.Replace
- 	}
- 
- 	if r.Position != 0 || (r.Flags&(1<<unix.NFTA_RULE_POSITION)) != 0 {
- 		msgData = append(msgData, cc.marshalAttr([]netlink.Attribute{
- 			{Type: unix.NFTA_RULE_POSITION, Data: binaryutil.BigEndian.PutUint64(r.Position)},
- 		})...)
-+	} else if r.PositionID != 0 {
-+		msgData = append(msgData, cc.marshalAttr([]netlink.Attribute{
-+			{Type: nfta_rule_position_id, Data: binaryutil.BigEndian.PutUint32(r.PositionID)},
-+		})...)
- 	}
- 
--	cc.messages = append(cc.messages, netlink.Message{
-+	cc.messages = append(cc.messages, netlinkMessage{
- 		Header: netlink.Header{
- 			Type:  newRuleHeaderType,
- 			Flags: flags,
- 		},
--		Data: append(extraHeader(uint8(r.Table.Family), 0), msgData...),
-+		Data:        append(extraHeader(uint8(r.Table.Family), 0), msgData...),
-+		handleReply: handleReply,
- 	})
- 
- 	return r
- }
- 
-+func (r *Rule) handleCreateReply(reply netlink.Message) error {
-+	ad, err := netlink.NewAttributeDecoder(reply.Data[4:])
-+	if err != nil {
-+		return err
-+	}
-+	ad.ByteOrder = binary.BigEndian
-+	var handle uint64
-+	for ad.Next() {
-+		switch ad.Type() {
-+		case unix.NFTA_RULE_HANDLE:
-+			handle = ad.Uint64()
-+		}
-+	}
-+	if ad.Err() != nil {
-+		return ad.Err()
-+	}
-+	if handle == 0 {
-+		return fmt.Errorf("missing rule handle in create reply")
-+	}
-+	r.Handle = handle
-+	r.ID = 0
-+	return nil
-+}
-+
- func (cc *Conn) ReplaceRule(r *Rule) *Rule {
- 	return cc.newRule(r, operationReplace)
- }
- 
-+// AddRule inserts the specified Rule after the existing Rule referenced by
-+// Position/PositionID if set, otherwise at the end of the chain.
- func (cc *Conn) AddRule(r *Rule) *Rule {
- 	if r.Handle != 0 {
- 		return cc.newRule(r, operationReplace)
-@@ -192,6 +245,8 @@ func (cc *Conn) AddRule(r *Rule) *Rule {
- 	return cc.newRule(r, operationAdd)
- }
- 
-+// InsertRule inserts the specified Rule before the existing Rule referenced by
-+// Position/PositionID if set, otherwise at the beginning of the chain.
- func (cc *Conn) InsertRule(r *Rule) *Rule {
- 	if r.Handle != 0 {
- 		return cc.newRule(r, operationReplace)
-@@ -200,7 +255,8 @@ func (cc *Conn) InsertRule(r *Rule) *Rule {
- 	return cc.newRule(r, operationInsert)
- }
- 
--// DelRule deletes the specified Rule, rule's handle cannot be 0
-+// DelRule deletes the specified Rule. Either the Handle or ID of the
-+// rule must be set.
- func (cc *Conn) DelRule(r *Rule) error {
- 	cc.mu.Lock()
- 	defer cc.mu.Unlock()
-@@ -208,15 +264,20 @@ func (cc *Conn) DelRule(r *Rule) error {
- 		{Type: unix.NFTA_RULE_TABLE, Data: []byte(r.Table.Name + "\x00")},
- 		{Type: unix.NFTA_RULE_CHAIN, Data: []byte(r.Chain.Name + "\x00")},
- 	})
--	if r.Handle == 0 {
--		return fmt.Errorf("rule's handle cannot be 0")
-+	if r.Handle != 0 {
-+		data = append(data, cc.marshalAttr([]netlink.Attribute{
-+			{Type: unix.NFTA_RULE_HANDLE, Data: binaryutil.BigEndian.PutUint64(r.Handle)},
-+		})...)
-+	} else if r.ID != 0 {
-+		data = append(data, cc.marshalAttr([]netlink.Attribute{
-+			{Type: unix.NFTA_RULE_ID, Data: binaryutil.BigEndian.PutUint32(r.ID)},
-+		})...)
-+	} else {
-+		return fmt.Errorf("rule must have a handle or ID")
- 	}
--	data = append(data, cc.marshalAttr([]netlink.Attribute{
--		{Type: unix.NFTA_RULE_HANDLE, Data: binaryutil.BigEndian.PutUint64(r.Handle)},
--	})...)
- 	flags := netlink.Request | netlink.Acknowledge
- 
--	cc.messages = append(cc.messages, netlink.Message{
-+	cc.messages = append(cc.messages, netlinkMessage{
- 		Header: netlink.Header{
- 			Type:  delRuleHeaderType,
- 			Flags: flags,
-diff --git a/set.go b/set.go
-index a7441d9..412d75a 100644
---- a/set.go
-+++ b/set.go
-@@ -45,8 +45,6 @@ const (
- 	NFTA_SET_ELEM_EXPRESSIONS = 0x11
- )
- 
--var allocSetID uint32
--
- // SetDatatype represents a datatype declared by nft.
- type SetDatatype struct {
- 	Name  string
-@@ -382,7 +380,7 @@ func (cc *Conn) SetAddElements(s *Set, vals []SetElement) error {
- 	if err != nil {
- 		return err
- 	}
--	cc.messages = append(cc.messages, netlink.Message{
-+	cc.messages = append(cc.messages, netlinkMessage{
- 		Header: netlink.Header{
- 			Type:  netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_NEWSETELEM),
- 			Flags: netlink.Request | netlink.Acknowledge | netlink.Create,
-@@ -502,8 +500,7 @@ func (cc *Conn) AddSet(s *Set, vals []SetElement) error {
- 	}
- 
- 	if s.ID == 0 {
--		allocSetID++
--		s.ID = allocSetID
-+		s.ID = cc.allocateTransactionID()
- 		if s.Anonymous {
- 			s.Name = "__set%d"
- 			if s.IsMap {
-@@ -653,7 +650,7 @@ func (cc *Conn) AddSet(s *Set, vals []SetElement) error {
- 		tableInfo = append(tableInfo, netlink.Attribute{Type: unix.NLA_F_NESTED | NFTA_SET_ELEM_EXPRESSIONS, Data: data})
- 	}
- 
--	cc.messages = append(cc.messages, netlink.Message{
-+	cc.messages = append(cc.messages, netlinkMessage{
- 		Header: netlink.Header{
- 			Type:  netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_NEWSET),
- 			Flags: netlink.Request | netlink.Acknowledge | netlink.Create,
-@@ -668,7 +665,7 @@ func (cc *Conn) AddSet(s *Set, vals []SetElement) error {
- 		if err != nil {
- 			return err
- 		}
--		cc.messages = append(cc.messages, netlink.Message{
-+		cc.messages = append(cc.messages, netlinkMessage{
- 			Header: netlink.Header{
- 				Type:  netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | hdrType),
- 				Flags: netlink.Request | netlink.Acknowledge | netlink.Create,
-@@ -688,7 +685,7 @@ func (cc *Conn) DelSet(s *Set) {
- 		{Type: unix.NFTA_SET_TABLE, Data: []byte(s.Table.Name + "\x00")},
- 		{Type: unix.NFTA_SET_NAME, Data: []byte(s.Name + "\x00")},
- 	})
--	cc.messages = append(cc.messages, netlink.Message{
-+	cc.messages = append(cc.messages, netlinkMessage{
- 		Header: netlink.Header{
- 			Type:  netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_DELSET),
- 			Flags: netlink.Request | netlink.Acknowledge,
-@@ -709,7 +706,7 @@ func (cc *Conn) SetDeleteElements(s *Set, vals []SetElement) error {
- 	if err != nil {
- 		return err
- 	}
--	cc.messages = append(cc.messages, netlink.Message{
-+	cc.messages = append(cc.messages, netlinkMessage{
- 		Header: netlink.Header{
- 			Type:  netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_DELSETELEM),
- 			Flags: netlink.Request | netlink.Acknowledge | netlink.Create,
-@@ -728,7 +725,7 @@ func (cc *Conn) FlushSet(s *Set) {
- 		{Type: unix.NFTA_SET_TABLE, Data: []byte(s.Table.Name + "\x00")},
- 		{Type: unix.NFTA_SET_NAME, Data: []byte(s.Name + "\x00")},
- 	})
--	cc.messages = append(cc.messages, netlink.Message{
-+	cc.messages = append(cc.messages, netlinkMessage{
- 		Header: netlink.Header{
- 			Type:  netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_DELSETELEM),
- 			Flags: netlink.Request | netlink.Acknowledge,
-diff --git a/set_test.go b/set_test.go
-index 65a8e00..dd30f45 100644
---- a/set_test.go
-+++ b/set_test.go
-@@ -254,7 +254,10 @@ func TestMarshalSet(t *testing.T) {
- 			}
- 			msg := c.messages[connMsgSetIdx]
- 
--			nset, err := setsFromMsg(msg)
-+			nset, err := setsFromMsg(netlink.Message{
-+				Header: msg.Header,
-+				Data:   msg.Data,
-+			})
- 			if err != nil {
- 				t.Fatalf("setsFromMsg() error: %+v", err)
- 			}
-diff --git a/table.go b/table.go
-index c391b7b..f7ed1ca 100644
---- a/table.go
-+++ b/table.go
-@@ -57,7 +57,7 @@ func (cc *Conn) DelTable(t *Table) {
- 		{Type: unix.NFTA_TABLE_NAME, Data: []byte(t.Name + "\x00")},
- 		{Type: unix.NFTA_TABLE_FLAGS, Data: []byte{0, 0, 0, 0}},
- 	})
--	cc.messages = append(cc.messages, netlink.Message{
-+	cc.messages = append(cc.messages, netlinkMessage{
- 		Header: netlink.Header{
- 			Type:  netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_DELTABLE),
- 			Flags: netlink.Request | netlink.Acknowledge,
-@@ -73,7 +73,7 @@ func (cc *Conn) addTable(t *Table, flag netlink.HeaderFlags) *Table {
- 		{Type: unix.NFTA_TABLE_NAME, Data: []byte(t.Name + "\x00")},
- 		{Type: unix.NFTA_TABLE_FLAGS, Data: []byte{0, 0, 0, 0}},
- 	})
--	cc.messages = append(cc.messages, netlink.Message{
-+	cc.messages = append(cc.messages, netlinkMessage{
- 		Header: netlink.Header{
- 			Type:  netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_NEWTABLE),
- 			Flags: netlink.Request | netlink.Acknowledge | flag,
-@@ -103,7 +103,7 @@ func (cc *Conn) FlushTable(t *Table) {
- 	data := cc.marshalAttr([]netlink.Attribute{
- 		{Type: unix.NFTA_RULE_TABLE, Data: []byte(t.Name + "\x00")},
- 	})
--	cc.messages = append(cc.messages, netlink.Message{
-+	cc.messages = append(cc.messages, netlinkMessage{
- 		Header: netlink.Header{
- 			Type:  netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_DELRULE),
- 			Flags: netlink.Request | netlink.Acknowledge,