| Jan Schär | ec03df4 | 2025-02-27 14:30:45 +0100 | [diff] [blame^] | 1 | commit b230daafa27f7cf22c9b9795aee2f0116f108a70 |
| 2 | Author: Jan Schär <jan@monogon.tech> |
| 3 | Date: 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 | |
| 15 | diff --git a/chain.go b/chain.go |
| 16 | index 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, |
| 46 | diff --git a/conn.go b/conn.go |
| 47 | index 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 | +} |
| 264 | diff --git a/flowtable.go b/flowtable.go |
| 265 | index 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, |
| 286 | diff --git a/obj.go b/obj.go |
| 287 | index 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, |
| 308 | diff --git a/rule.go b/rule.go |
| 309 | index 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, |
| 513 | diff --git a/set.go b/set.go |
| 514 | index 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, |
| 590 | diff --git a/set_test.go b/set_test.go |
| 591 | index 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 | } |
| 606 | diff --git a/table.go b/table.go |
| 607 | index 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, |