blob: df00fef341f50a918d115446bfc325bf2b92a1b7 [file] [log] [blame]
Jan Schärec03df42025-02-27 14:30:45 +01001commit b230daafa27f7cf22c9b9795aee2f0116f108a70
2Author: Jan Schär <jan@monogon.tech>
3Date: Mon Feb 24 10:52:11 2025 +0100
4
5 Set rule handle during flush
6
7 This change makes it possible to delete rules after inserting them,
8 without needing to query the rules first. Rules can be deleted both
9 before and after they are flushed. Additionally, this allows positioning
10 a new rule next to an existing rule, both before and after the existing
11 rule is flushed.
12
13 Upstream PR: https://github.com/google/nftables/pull/299
14
15diff --git a/chain.go b/chain.go
16index 4f4c0a5..f1853cf 100644
17--- a/chain.go
18+++ b/chain.go
19@@ -140,7 +140,7 @@ func (cc *Conn) AddChain(c *Chain) *Chain {
20 {Type: unix.NFTA_CHAIN_TYPE, Data: []byte(c.Type + "\x00")},
21 })...)
22 }
23- cc.messages = append(cc.messages, netlink.Message{
24+ cc.messages = append(cc.messages, netlinkMessage{
25 Header: netlink.Header{
26 Type: netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_NEWCHAIN),
27 Flags: netlink.Request | netlink.Acknowledge | netlink.Create,
28@@ -161,7 +161,7 @@ func (cc *Conn) DelChain(c *Chain) {
29 {Type: unix.NFTA_CHAIN_NAME, Data: []byte(c.Name + "\x00")},
30 })
31
32- cc.messages = append(cc.messages, netlink.Message{
33+ cc.messages = append(cc.messages, netlinkMessage{
34 Header: netlink.Header{
35 Type: netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_DELCHAIN),
36 Flags: netlink.Request | netlink.Acknowledge,
37@@ -179,7 +179,7 @@ func (cc *Conn) FlushChain(c *Chain) {
38 {Type: unix.NFTA_RULE_TABLE, Data: []byte(c.Table.Name + "\x00")},
39 {Type: unix.NFTA_RULE_CHAIN, Data: []byte(c.Name + "\x00")},
40 })
41- cc.messages = append(cc.messages, netlink.Message{
42+ cc.messages = append(cc.messages, netlinkMessage{
43 Header: netlink.Header{
44 Type: netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_DELRULE),
45 Flags: netlink.Request | netlink.Acknowledge,
46diff --git a/conn.go b/conn.go
47index fef9c2a..6b10844 100644
48--- a/conn.go
49+++ b/conn.go
50@@ -17,6 +17,7 @@ package nftables
51 import (
52 "errors"
53 "fmt"
54+ "math"
55 "os"
56 "sync"
57 "syscall"
58@@ -38,12 +39,20 @@ type Conn struct {
59 TestDial nltest.Func // for testing only; passed to nltest.Dial
60 NetNS int // fd referencing the network namespace netlink will interact with.
61
62- lasting bool // establish a lasting connection to be used across multiple netlink operations.
63- mu sync.Mutex // protects the following state
64- messages []netlink.Message
65- err error
66- nlconn *netlink.Conn // netlink socket using NETLINK_NETFILTER protocol.
67- sockOptions []SockOption
68+ lasting bool // establish a lasting connection to be used across multiple netlink operations.
69+ mu sync.Mutex // protects the following state
70+ messages []netlinkMessage
71+ err error
72+ nlconn *netlink.Conn // netlink socket using NETLINK_NETFILTER protocol.
73+ sockOptions []SockOption
74+ lastID uint32
75+ allocatedIDs uint32
76+}
77+
78+type netlinkMessage struct {
79+ Header netlink.Header
80+ Data []byte
81+ handleReply func(reply netlink.Message) error
82 }
83
84 // ConnOption is an option to change the behavior of the nftables Conn returned by Open.
85@@ -168,24 +177,6 @@ func receiveAckAware(nlconn *netlink.Conn, sentMsgFlags netlink.HeaderFlags) ([]
86 return reply, nil
87 }
88
89- if len(reply) != 0 {
90- last := reply[len(reply)-1]
91- for re := last.Header.Type; (re&netlink.Overrun) == netlink.Overrun && (re&netlink.Done) != netlink.Done; re = last.Header.Type {
92- // we are not finished, the message is overrun
93- r, err := nlconn.Receive()
94- if err != nil {
95- return nil, err
96- }
97- reply = append(reply, r...)
98- last = reply[len(reply)-1]
99- }
100-
101- if last.Header.Type == netlink.Error && binaryutil.BigEndian.Uint32(last.Data[:4]) == 0 {
102- // we have already collected an ack
103- return reply, nil
104- }
105- }
106-
107 // Now we expect an ack
108 ack, err := nlconn.Receive()
109 if err != nil {
110@@ -193,8 +184,7 @@ func receiveAckAware(nlconn *netlink.Conn, sentMsgFlags netlink.HeaderFlags) ([]
111 }
112
113 if len(ack) == 0 {
114- // received an empty ack?
115- return reply, nil
116+ return nil, errors.New("received an empty ack")
117 }
118
119 msg := ack[0]
120@@ -244,6 +234,7 @@ func (cc *Conn) Flush() error {
121 cc.mu.Lock()
122 defer func() {
123 cc.messages = nil
124+ cc.allocatedIDs = 0
125 cc.mu.Unlock()
126 }()
127 if len(cc.messages) == 0 {
128@@ -259,15 +250,53 @@ func (cc *Conn) Flush() error {
129 }
130 defer func() { _ = closer() }()
131
132- if _, err := conn.SendMessages(batch(cc.messages)); err != nil {
133+ messages, err := conn.SendMessages(batch(cc.messages))
134+ if err != nil {
135 return fmt.Errorf("SendMessages: %w", err)
136 }
137
138 var errs error
139+
140+ // Fetch replies. Each message with the Echo flag triggers a reply of the same
141+ // type. Additionally, if the first message of the batch has the Echo flag, we
142+ // get a reply of type NFT_MSG_NEWGEN, which we ignore.
143+ replyIndex := 0
144+ for replyIndex < len(cc.messages) && cc.messages[replyIndex].Header.Flags&netlink.Echo == 0 {
145+ replyIndex++
146+ }
147+ replies, err := conn.Receive()
148+ for err == nil && len(replies) != 0 {
149+ reply := replies[0]
150+ if reply.Header.Type == netlink.Error && reply.Header.Sequence == messages[1].Header.Sequence {
151+ // The next message is the acknowledgement for the first message in the
152+ // batch; stop looking for replies.
153+ break
154+ } else if replyIndex < len(cc.messages) {
155+ msg := messages[replyIndex+1]
156+ if msg.Header.Sequence == reply.Header.Sequence && msg.Header.Type == reply.Header.Type {
157+ err := cc.messages[replyIndex].handleReply(reply)
158+ if err != nil {
159+ errs = errors.Join(errs, err)
160+ }
161+ replyIndex++
162+ for replyIndex < len(cc.messages) && cc.messages[replyIndex].Header.Flags&netlink.Echo == 0 {
163+ replyIndex++
164+ }
165+ }
166+ }
167+ replies = replies[1:]
168+ if len(replies) == 0 {
169+ replies, err = conn.Receive()
170+ }
171+ }
172+
173 // Fetch the requested acknowledgement for each message we sent.
174- for _, msg := range cc.messages {
175- if _, err := receiveAckAware(conn, msg.Header.Flags); err != nil {
176- if errors.Is(err, os.ErrPermission) || errors.Is(err, syscall.ENOBUFS) {
177+ for i := range cc.messages {
178+ if i != 0 {
179+ _, err = conn.Receive()
180+ }
181+ if err != nil {
182+ if errors.Is(err, os.ErrPermission) || errors.Is(err, syscall.ENOBUFS) || errors.Is(err, syscall.ENOMEM) {
183 // Kernel will only send one error to user space.
184 return err
185 }
186@@ -278,6 +307,9 @@ func (cc *Conn) Flush() error {
187 if errs != nil {
188 return fmt.Errorf("conn.Receive: %w", errs)
189 }
190+ if replyIndex < len(cc.messages) {
191+ return fmt.Errorf("missing reply for message %d in batch", replyIndex)
192+ }
193
194 return nil
195 }
196@@ -287,7 +319,7 @@ func (cc *Conn) Flush() error {
197 func (cc *Conn) FlushRuleset() {
198 cc.mu.Lock()
199 defer cc.mu.Unlock()
200- cc.messages = append(cc.messages, netlink.Message{
201+ cc.messages = append(cc.messages, netlinkMessage{
202 Header: netlink.Header{
203 Type: netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_DELTABLE),
204 Flags: netlink.Request | netlink.Acknowledge | netlink.Create,
205@@ -346,26 +378,47 @@ func (cc *Conn) marshalExpr(fam byte, e expr.Any) []byte {
206 return b
207 }
208
209-func batch(messages []netlink.Message) []netlink.Message {
210- batch := []netlink.Message{
211- {
212- Header: netlink.Header{
213- Type: netlink.HeaderType(unix.NFNL_MSG_BATCH_BEGIN),
214- Flags: netlink.Request,
215- },
216- Data: extraHeader(0, unix.NFNL_SUBSYS_NFTABLES),
217+func batch(messages []netlinkMessage) []netlink.Message {
218+ batch := make([]netlink.Message, len(messages)+2)
219+ batch[0] = netlink.Message{
220+ Header: netlink.Header{
221+ Type: netlink.HeaderType(unix.NFNL_MSG_BATCH_BEGIN),
222+ Flags: netlink.Request,
223 },
224+ Data: extraHeader(0, unix.NFNL_SUBSYS_NFTABLES),
225 }
226
227- batch = append(batch, messages...)
228+ for i, msg := range messages {
229+ batch[i+1] = netlink.Message{
230+ Header: msg.Header,
231+ Data: msg.Data,
232+ }
233+ }
234
235- batch = append(batch, netlink.Message{
236+ batch[len(messages)+1] = netlink.Message{
237 Header: netlink.Header{
238 Type: netlink.HeaderType(unix.NFNL_MSG_BATCH_END),
239 Flags: netlink.Request,
240 },
241 Data: extraHeader(0, unix.NFNL_SUBSYS_NFTABLES),
242- })
243+ }
244
245 return batch
246 }
247+
248+// allocateTransactionID allocates an identifier which is only valid in the
249+// current transaction.
250+func (cc *Conn) allocateTransactionID() uint32 {
251+ if cc.allocatedIDs == math.MaxUint32 {
252+ panic(fmt.Sprintf("trying to allocate more than %d IDs in a single nftables transaction", math.MaxUint32))
253+ }
254+ // To make it more likely to catch when a transaction ID is erroneously used
255+ // in a later transaction, cc.lastID is not reset after each transaction;
256+ // instead it is only reset once it rolls over from math.MaxUint32 to 0.
257+ cc.allocatedIDs++
258+ cc.lastID++
259+ if cc.lastID == 0 {
260+ cc.lastID = 1
261+ }
262+ return cc.lastID
263+}
264diff --git a/flowtable.go b/flowtable.go
265index 93dbcb5..a35712f 100644
266--- a/flowtable.go
267+++ b/flowtable.go
268@@ -142,7 +142,7 @@ func (cc *Conn) AddFlowtable(f *Flowtable) *Flowtable {
269 {Type: unix.NLA_F_NESTED | NFTA_FLOWTABLE_HOOK, Data: cc.marshalAttr(hookAttr)},
270 })...)
271
272- cc.messages = append(cc.messages, netlink.Message{
273+ cc.messages = append(cc.messages, netlinkMessage{
274 Header: netlink.Header{
275 Type: netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | NFT_MSG_NEWFLOWTABLE),
276 Flags: netlink.Request | netlink.Acknowledge | netlink.Create,
277@@ -162,7 +162,7 @@ func (cc *Conn) DelFlowtable(f *Flowtable) {
278 {Type: NFTA_FLOWTABLE_NAME, Data: []byte(f.Name)},
279 })
280
281- cc.messages = append(cc.messages, netlink.Message{
282+ cc.messages = append(cc.messages, netlinkMessage{
283 Header: netlink.Header{
284 Type: netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | NFT_MSG_DELFLOWTABLE),
285 Flags: netlink.Request | netlink.Acknowledge,
286diff --git a/obj.go b/obj.go
287index 3fcd6d7..60d6f76 100644
288--- a/obj.go
289+++ b/obj.go
290@@ -124,7 +124,7 @@ func (cc *Conn) AddObj(o Obj) Obj {
291 attrs = append(attrs, netlink.Attribute{Type: unix.NLA_F_NESTED | unix.NFTA_OBJ_DATA, Data: data})
292 }
293
294- cc.messages = append(cc.messages, netlink.Message{
295+ cc.messages = append(cc.messages, netlinkMessage{
296 Header: netlink.Header{
297 Type: netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_NEWOBJ),
298 Flags: netlink.Request | netlink.Acknowledge | netlink.Create,
299@@ -146,7 +146,7 @@ func (cc *Conn) DeleteObject(o Obj) {
300 data := cc.marshalAttr(attrs)
301 data = append(data, cc.marshalAttr([]netlink.Attribute{{Type: unix.NLA_F_NESTED | unix.NFTA_OBJ_DATA}})...)
302
303- cc.messages = append(cc.messages, netlink.Message{
304+ cc.messages = append(cc.messages, netlinkMessage{
305 Header: netlink.Header{
306 Type: netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_DELOBJ),
307 Flags: netlink.Request | netlink.Acknowledge,
308diff --git a/rule.go b/rule.go
309index 0706834..7798150 100644
310--- a/rule.go
311+++ b/rule.go
312@@ -30,6 +30,9 @@ var (
313 delRuleHeaderType = netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_DELRULE)
314 )
315
316+// This constant is missing at unix.NFTA_RULE_POSITION_ID.
317+const nfta_rule_position_id = 0xa
318+
319 type ruleOperation uint32
320
321 // Possible PayloadOperationType values.
322@@ -42,15 +45,27 @@ const (
323 // A Rule does something with a packet. See also
324 // https://wiki.nftables.org/wiki-nftables/index.php/Simple_rule_management
325 type Rule struct {
326- Table *Table
327- Chain *Chain
328+ Table *Table
329+ Chain *Chain
330+ // Position can be set to the Handle of another Rule to insert the new Rule
331+ // before (InsertRule) or after (AddRule) the existing rule.
332 Position uint64
333- Handle uint64
334 // The list of possible flags are specified by nftnl_rule_attr, see
335 // https://git.netfilter.org/libnftnl/tree/include/libnftnl/rule.h#n21
336 // Current nftables go implementation supports only
337 // NFTNL_RULE_POSITION flag for setting rule at position 0
338- Flags uint32
339+ Flags uint32
340+ // PositionID can be set to the ID of another Rule, same as Position, for when
341+ // the existing rule is not yet committed.
342+ PositionID uint32
343+ // Handle identifies an existing Rule. For a new Rule, this field is set
344+ // during the Flush() in which the rule is committed. Make sure to not access
345+ // this field concurrently with this Flush() to avoid data races.
346+ Handle uint64
347+ // ID is an identifier for a new Rule, which is assigned by
348+ // AddRule/InsertRule, and only valid before the rule is committed by Flush().
349+ // The field is set to 0 during Flush().
350+ ID uint32
351 Exprs []expr.Any
352 UserData []byte
353 }
354@@ -81,7 +96,7 @@ func (cc *Conn) GetRules(t *Table, c *Chain) ([]*Rule, error) {
355 message := netlink.Message{
356 Header: netlink.Header{
357 Type: netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_GETRULE),
358- Flags: netlink.Request | netlink.Acknowledge | netlink.Dump | unix.NLM_F_ECHO,
359+ Flags: netlink.Request | netlink.Acknowledge | netlink.Dump,
360 },
361 Data: append(extraHeader(uint8(t.Family), 0), data...),
362 }
363@@ -106,7 +121,6 @@ func (cc *Conn) GetRules(t *Table, c *Chain) ([]*Rule, error) {
364 return rules, nil
365 }
366
367-// AddRule adds the specified Rule
368 func (cc *Conn) newRule(r *Rule, op ruleOperation) *Rule {
369 cc.mu.Lock()
370 defer cc.mu.Unlock()
371@@ -127,6 +141,11 @@ func (cc *Conn) newRule(r *Rule, op ruleOperation) *Rule {
372 data = append(data, cc.marshalAttr([]netlink.Attribute{
373 {Type: unix.NFTA_RULE_HANDLE, Data: binaryutil.BigEndian.PutUint64(r.Handle)},
374 })...)
375+ } else {
376+ r.ID = cc.allocateTransactionID()
377+ data = append(data, cc.marshalAttr([]netlink.Attribute{
378+ {Type: unix.NFTA_RULE_ID, Data: binaryutil.BigEndian.PutUint32(r.ID)},
379+ })...)
380 }
381
382 data = append(data, cc.marshalAttr([]netlink.Attribute{
383@@ -147,43 +166,77 @@ func (cc *Conn) newRule(r *Rule, op ruleOperation) *Rule {
384 msgData := []byte{}
385
386 msgData = append(msgData, data...)
387- var flags netlink.HeaderFlags
388 if r.UserData != nil {
389 msgData = append(msgData, cc.marshalAttr([]netlink.Attribute{
390 {Type: unix.NFTA_RULE_USERDATA, Data: r.UserData},
391 })...)
392 }
393
394+ var flags netlink.HeaderFlags
395+ var handleReply func(reply netlink.Message) error
396 switch op {
397 case operationAdd:
398- flags = netlink.Request | netlink.Acknowledge | netlink.Create | unix.NLM_F_ECHO | unix.NLM_F_APPEND
399+ flags = netlink.Request | netlink.Acknowledge | netlink.Create | netlink.Echo | netlink.Append
400+ handleReply = r.handleCreateReply
401 case operationInsert:
402- flags = netlink.Request | netlink.Acknowledge | netlink.Create | unix.NLM_F_ECHO
403+ flags = netlink.Request | netlink.Acknowledge | netlink.Create | netlink.Echo
404+ handleReply = r.handleCreateReply
405 case operationReplace:
406- flags = netlink.Request | netlink.Acknowledge | netlink.Replace | unix.NLM_F_ECHO | unix.NLM_F_REPLACE
407+ flags = netlink.Request | netlink.Acknowledge | netlink.Replace
408 }
409
410 if r.Position != 0 || (r.Flags&(1<<unix.NFTA_RULE_POSITION)) != 0 {
411 msgData = append(msgData, cc.marshalAttr([]netlink.Attribute{
412 {Type: unix.NFTA_RULE_POSITION, Data: binaryutil.BigEndian.PutUint64(r.Position)},
413 })...)
414+ } else if r.PositionID != 0 {
415+ msgData = append(msgData, cc.marshalAttr([]netlink.Attribute{
416+ {Type: nfta_rule_position_id, Data: binaryutil.BigEndian.PutUint32(r.PositionID)},
417+ })...)
418 }
419
420- cc.messages = append(cc.messages, netlink.Message{
421+ cc.messages = append(cc.messages, netlinkMessage{
422 Header: netlink.Header{
423 Type: newRuleHeaderType,
424 Flags: flags,
425 },
426- Data: append(extraHeader(uint8(r.Table.Family), 0), msgData...),
427+ Data: append(extraHeader(uint8(r.Table.Family), 0), msgData...),
428+ handleReply: handleReply,
429 })
430
431 return r
432 }
433
434+func (r *Rule) handleCreateReply(reply netlink.Message) error {
435+ ad, err := netlink.NewAttributeDecoder(reply.Data[4:])
436+ if err != nil {
437+ return err
438+ }
439+ ad.ByteOrder = binary.BigEndian
440+ var handle uint64
441+ for ad.Next() {
442+ switch ad.Type() {
443+ case unix.NFTA_RULE_HANDLE:
444+ handle = ad.Uint64()
445+ }
446+ }
447+ if ad.Err() != nil {
448+ return ad.Err()
449+ }
450+ if handle == 0 {
451+ return fmt.Errorf("missing rule handle in create reply")
452+ }
453+ r.Handle = handle
454+ r.ID = 0
455+ return nil
456+}
457+
458 func (cc *Conn) ReplaceRule(r *Rule) *Rule {
459 return cc.newRule(r, operationReplace)
460 }
461
462+// AddRule inserts the specified Rule after the existing Rule referenced by
463+// Position/PositionID if set, otherwise at the end of the chain.
464 func (cc *Conn) AddRule(r *Rule) *Rule {
465 if r.Handle != 0 {
466 return cc.newRule(r, operationReplace)
467@@ -192,6 +245,8 @@ func (cc *Conn) AddRule(r *Rule) *Rule {
468 return cc.newRule(r, operationAdd)
469 }
470
471+// InsertRule inserts the specified Rule before the existing Rule referenced by
472+// Position/PositionID if set, otherwise at the beginning of the chain.
473 func (cc *Conn) InsertRule(r *Rule) *Rule {
474 if r.Handle != 0 {
475 return cc.newRule(r, operationReplace)
476@@ -200,7 +255,8 @@ func (cc *Conn) InsertRule(r *Rule) *Rule {
477 return cc.newRule(r, operationInsert)
478 }
479
480-// DelRule deletes the specified Rule, rule's handle cannot be 0
481+// DelRule deletes the specified Rule. Either the Handle or ID of the
482+// rule must be set.
483 func (cc *Conn) DelRule(r *Rule) error {
484 cc.mu.Lock()
485 defer cc.mu.Unlock()
486@@ -208,15 +264,20 @@ func (cc *Conn) DelRule(r *Rule) error {
487 {Type: unix.NFTA_RULE_TABLE, Data: []byte(r.Table.Name + "\x00")},
488 {Type: unix.NFTA_RULE_CHAIN, Data: []byte(r.Chain.Name + "\x00")},
489 })
490- if r.Handle == 0 {
491- return fmt.Errorf("rule's handle cannot be 0")
492+ if r.Handle != 0 {
493+ data = append(data, cc.marshalAttr([]netlink.Attribute{
494+ {Type: unix.NFTA_RULE_HANDLE, Data: binaryutil.BigEndian.PutUint64(r.Handle)},
495+ })...)
496+ } else if r.ID != 0 {
497+ data = append(data, cc.marshalAttr([]netlink.Attribute{
498+ {Type: unix.NFTA_RULE_ID, Data: binaryutil.BigEndian.PutUint32(r.ID)},
499+ })...)
500+ } else {
501+ return fmt.Errorf("rule must have a handle or ID")
502 }
503- data = append(data, cc.marshalAttr([]netlink.Attribute{
504- {Type: unix.NFTA_RULE_HANDLE, Data: binaryutil.BigEndian.PutUint64(r.Handle)},
505- })...)
506 flags := netlink.Request | netlink.Acknowledge
507
508- cc.messages = append(cc.messages, netlink.Message{
509+ cc.messages = append(cc.messages, netlinkMessage{
510 Header: netlink.Header{
511 Type: delRuleHeaderType,
512 Flags: flags,
513diff --git a/set.go b/set.go
514index a7441d9..412d75a 100644
515--- a/set.go
516+++ b/set.go
517@@ -45,8 +45,6 @@ const (
518 NFTA_SET_ELEM_EXPRESSIONS = 0x11
519 )
520
521-var allocSetID uint32
522-
523 // SetDatatype represents a datatype declared by nft.
524 type SetDatatype struct {
525 Name string
526@@ -382,7 +380,7 @@ func (cc *Conn) SetAddElements(s *Set, vals []SetElement) error {
527 if err != nil {
528 return err
529 }
530- cc.messages = append(cc.messages, netlink.Message{
531+ cc.messages = append(cc.messages, netlinkMessage{
532 Header: netlink.Header{
533 Type: netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_NEWSETELEM),
534 Flags: netlink.Request | netlink.Acknowledge | netlink.Create,
535@@ -502,8 +500,7 @@ func (cc *Conn) AddSet(s *Set, vals []SetElement) error {
536 }
537
538 if s.ID == 0 {
539- allocSetID++
540- s.ID = allocSetID
541+ s.ID = cc.allocateTransactionID()
542 if s.Anonymous {
543 s.Name = "__set%d"
544 if s.IsMap {
545@@ -653,7 +650,7 @@ func (cc *Conn) AddSet(s *Set, vals []SetElement) error {
546 tableInfo = append(tableInfo, netlink.Attribute{Type: unix.NLA_F_NESTED | NFTA_SET_ELEM_EXPRESSIONS, Data: data})
547 }
548
549- cc.messages = append(cc.messages, netlink.Message{
550+ cc.messages = append(cc.messages, netlinkMessage{
551 Header: netlink.Header{
552 Type: netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_NEWSET),
553 Flags: netlink.Request | netlink.Acknowledge | netlink.Create,
554@@ -668,7 +665,7 @@ func (cc *Conn) AddSet(s *Set, vals []SetElement) error {
555 if err != nil {
556 return err
557 }
558- cc.messages = append(cc.messages, netlink.Message{
559+ cc.messages = append(cc.messages, netlinkMessage{
560 Header: netlink.Header{
561 Type: netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | hdrType),
562 Flags: netlink.Request | netlink.Acknowledge | netlink.Create,
563@@ -688,7 +685,7 @@ func (cc *Conn) DelSet(s *Set) {
564 {Type: unix.NFTA_SET_TABLE, Data: []byte(s.Table.Name + "\x00")},
565 {Type: unix.NFTA_SET_NAME, Data: []byte(s.Name + "\x00")},
566 })
567- cc.messages = append(cc.messages, netlink.Message{
568+ cc.messages = append(cc.messages, netlinkMessage{
569 Header: netlink.Header{
570 Type: netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_DELSET),
571 Flags: netlink.Request | netlink.Acknowledge,
572@@ -709,7 +706,7 @@ func (cc *Conn) SetDeleteElements(s *Set, vals []SetElement) error {
573 if err != nil {
574 return err
575 }
576- cc.messages = append(cc.messages, netlink.Message{
577+ cc.messages = append(cc.messages, netlinkMessage{
578 Header: netlink.Header{
579 Type: netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_DELSETELEM),
580 Flags: netlink.Request | netlink.Acknowledge | netlink.Create,
581@@ -728,7 +725,7 @@ func (cc *Conn) FlushSet(s *Set) {
582 {Type: unix.NFTA_SET_TABLE, Data: []byte(s.Table.Name + "\x00")},
583 {Type: unix.NFTA_SET_NAME, Data: []byte(s.Name + "\x00")},
584 })
585- cc.messages = append(cc.messages, netlink.Message{
586+ cc.messages = append(cc.messages, netlinkMessage{
587 Header: netlink.Header{
588 Type: netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_DELSETELEM),
589 Flags: netlink.Request | netlink.Acknowledge,
590diff --git a/set_test.go b/set_test.go
591index 65a8e00..dd30f45 100644
592--- a/set_test.go
593+++ b/set_test.go
594@@ -254,7 +254,10 @@ func TestMarshalSet(t *testing.T) {
595 }
596 msg := c.messages[connMsgSetIdx]
597
598- nset, err := setsFromMsg(msg)
599+ nset, err := setsFromMsg(netlink.Message{
600+ Header: msg.Header,
601+ Data: msg.Data,
602+ })
603 if err != nil {
604 t.Fatalf("setsFromMsg() error: %+v", err)
605 }
606diff --git a/table.go b/table.go
607index c391b7b..f7ed1ca 100644
608--- a/table.go
609+++ b/table.go
610@@ -57,7 +57,7 @@ func (cc *Conn) DelTable(t *Table) {
611 {Type: unix.NFTA_TABLE_NAME, Data: []byte(t.Name + "\x00")},
612 {Type: unix.NFTA_TABLE_FLAGS, Data: []byte{0, 0, 0, 0}},
613 })
614- cc.messages = append(cc.messages, netlink.Message{
615+ cc.messages = append(cc.messages, netlinkMessage{
616 Header: netlink.Header{
617 Type: netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_DELTABLE),
618 Flags: netlink.Request | netlink.Acknowledge,
619@@ -73,7 +73,7 @@ func (cc *Conn) addTable(t *Table, flag netlink.HeaderFlags) *Table {
620 {Type: unix.NFTA_TABLE_NAME, Data: []byte(t.Name + "\x00")},
621 {Type: unix.NFTA_TABLE_FLAGS, Data: []byte{0, 0, 0, 0}},
622 })
623- cc.messages = append(cc.messages, netlink.Message{
624+ cc.messages = append(cc.messages, netlinkMessage{
625 Header: netlink.Header{
626 Type: netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_NEWTABLE),
627 Flags: netlink.Request | netlink.Acknowledge | flag,
628@@ -103,7 +103,7 @@ func (cc *Conn) FlushTable(t *Table) {
629 data := cc.marshalAttr([]netlink.Attribute{
630 {Type: unix.NFTA_RULE_TABLE, Data: []byte(t.Name + "\x00")},
631 })
632- cc.messages = append(cc.messages, netlink.Message{
633+ cc.messages = append(cc.messages, netlinkMessage{
634 Header: netlink.Header{
635 Type: netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_DELRULE),
636 Flags: netlink.Request | netlink.Acknowledge,