nftables/set.go

720 lines
22 KiB
Go
Raw Normal View History

// Copyright 2018 Google LLC. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package nftables
import (
"encoding/binary"
"errors"
"fmt"
"strings"
"time"
2019-08-28 01:51:13 -05:00
"github.com/google/nftables/expr"
"github.com/google/nftables/binaryutil"
"github.com/mdlayher/netlink"
"golang.org/x/sys/unix"
)
// SetConcatTypeBits defines concatination bits, originally defined in
// https://git.netfilter.org/iptables/tree/iptables/nft.c?id=26753888720d8e7eb422ae4311348347f5a05cb4#n1002
const (
SetConcatTypeBits = 6
SetConcatTypeMask = (1 << SetConcatTypeBits) - 1
)
var allocSetID uint32
// SetDatatype represents a datatype declared by nft.
type SetDatatype struct {
Name string
Bytes uint32
// nftMagic represents the magic value that nft uses for
// certain types (ie: IP addresses). We populate SET_KEY_TYPE
// identically, so `nft list ...` commands produce correct output.
nftMagic uint32
}
2019-12-01 03:10:42 -06:00
// GetNFTMagic returns a custom datatype based on user's parameters
func (s *SetDatatype) GetNFTMagic() uint32 {
return s.nftMagic
}
// SetNFTMagic returns a custom datatype based on user's parameters
func (s *SetDatatype) SetNFTMagic(nftMagic uint32) {
s.nftMagic = nftMagic
}
// NFT datatypes. See: https://git.netfilter.org/nftables/tree/src/datatype.c
var (
2019-12-01 03:10:42 -06:00
TypeInvalid = SetDatatype{Name: "invalid", nftMagic: 0}
2019-08-28 01:51:13 -05:00
TypeVerdict = SetDatatype{Name: "verdict", Bytes: 0, nftMagic: 1}
TypeInteger = SetDatatype{Name: "integer", Bytes: 4, nftMagic: 4}
TypeIPAddr = SetDatatype{Name: "ipv4_addr", Bytes: 4, nftMagic: 7}
TypeIP6Addr = SetDatatype{Name: "ipv6_addr", Bytes: 16, nftMagic: 8}
TypeEtherAddr = SetDatatype{Name: "ether_addr", Bytes: 6, nftMagic: 9}
TypeInetProto = SetDatatype{Name: "inet_proto", Bytes: 1, nftMagic: 12}
TypeInetService = SetDatatype{Name: "inet_service", Bytes: 2, nftMagic: 13}
TypeMark = SetDatatype{Name: "mark", Bytes: 4, nftMagic: 19}
nftDatatypes = []SetDatatype{
2019-08-28 01:51:13 -05:00
TypeVerdict,
TypeInteger,
TypeIPAddr,
TypeIP6Addr,
TypeEtherAddr,
TypeInetProto,
TypeInetService,
TypeMark,
}
)
// ErrTooManyTypes is the error returned by ConcatSetType, if nftMagic would overflow.
var ErrTooManyTypes = errors.New("too many types to concat")
// MustConcatSetType does the same as ConcatSetType, but panics instead of an
// error. It simplifies safe initialization of global variables.
func MustConcatSetType(types ...SetDatatype) SetDatatype {
t, err := ConcatSetType(types...)
if err != nil {
panic(err)
}
return t
}
// ConcatSetType constructs a new SetDatatype which consists of a concatenation
// of the passed types. It returns ErrTooManyTypes, if nftMagic would overflow
// (more than 5 types).
func ConcatSetType(types ...SetDatatype) (SetDatatype, error) {
if len(types) > 32/SetConcatTypeBits {
return SetDatatype{}, ErrTooManyTypes
}
var magic, bytes uint32
names := make([]string, len(types))
for i, t := range types {
bytes += t.Bytes
// concatenated types pad the length to multiples of the register size (4 bytes)
// see https://git.netfilter.org/nftables/tree/src/datatype.c?id=488356b895024d0944b20feb1f930558726e0877#n1162
if t.Bytes%4 != 0 {
bytes += 4 - (t.Bytes % 4)
}
names[i] = t.Name
magic <<= SetConcatTypeBits
magic |= t.nftMagic & SetConcatTypeMask
}
return SetDatatype{Name: strings.Join(names, " . "), Bytes: bytes, nftMagic: magic}, nil
}
// Set represents an nftables set. Anonymous sets are only valid within the
// context of a single batch.
type Set struct {
Table *Table
ID uint32
Name string
Anonymous bool
Constant bool
Interval bool
IsMap bool
HasTimeout bool
Timeout time.Duration
KeyType SetDatatype
DataType SetDatatype
}
// SetElement represents a data point within a set.
type SetElement struct {
Key []byte
Val []byte
IntervalEnd bool
2019-08-28 01:51:13 -05:00
// To support vmap, a caller must be able to pass Verdict type of data.
// If IsMap is true and VerdictData is not nil, then Val of SetElement will be ignored
// and VerdictData will be wrapped into Attribute data.
VerdictData *expr.Verdict
// To support aging of set elements
Timeout time.Duration
}
2019-08-13 15:19:49 -05:00
func (s *SetElement) decode() func(b []byte) error {
return func(b []byte) error {
ad, err := netlink.NewAttributeDecoder(b)
if err != nil {
return fmt.Errorf("failed to create nested attribute decoder: %v", err)
}
ad.ByteOrder = binary.BigEndian
for ad.Next() {
switch ad.Type() {
case unix.NFTA_SET_ELEM_KEY:
s.Key, err = decodeElement(ad.Bytes())
if err != nil {
return err
}
case unix.NFTA_SET_ELEM_DATA:
s.Val, err = decodeElement(ad.Bytes())
if err != nil {
return err
}
case unix.NFTA_SET_ELEM_FLAGS:
flags := ad.Uint32()
s.IntervalEnd = (flags & unix.NFT_SET_ELEM_INTERVAL_END) != 0
case unix.NFTA_SET_ELEM_TIMEOUT:
s.Timeout = time.Duration(time.Millisecond * time.Duration(ad.Uint64()))
2019-08-13 15:19:49 -05:00
}
}
return ad.Err()
}
}
func decodeElement(d []byte) ([]byte, error) {
ad, err := netlink.NewAttributeDecoder(d)
if err != nil {
return nil, fmt.Errorf("failed to create nested attribute decoder: %v", err)
}
ad.ByteOrder = binary.BigEndian
var b []byte
for ad.Next() {
switch ad.Type() {
case unix.NFTA_SET_ELEM_KEY:
fallthrough
case unix.NFTA_SET_ELEM_DATA:
b = ad.Bytes()
}
}
if err := ad.Err(); err != nil {
return nil, err
}
return b, nil
}
// SetAddElements applies data points to an nftables set.
func (cc *Conn) SetAddElements(s *Set, vals []SetElement) error {
cc.Lock()
defer cc.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, netlink.Message{
Header: netlink.Header{
Type: netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_NEWSETELEM),
Flags: netlink.Request | netlink.Acknowledge | netlink.Create,
},
2020-01-12 09:07:37 -06:00
Data: append(extraHeader(uint8(s.Table.Family), 0), cc.marshalAttr(elements)...),
})
return nil
}
func (s *Set) makeElemList(vals []SetElement, id uint32) ([]netlink.Attribute, error) {
var elements []netlink.Attribute
for i, v := range vals {
item := make([]netlink.Attribute, 0)
var flags uint32
if v.IntervalEnd {
flags |= unix.NFT_SET_ELEM_INTERVAL_END
2019-08-13 15:19:49 -05:00
item = append(item, netlink.Attribute{Type: unix.NFTA_SET_ELEM_FLAGS | unix.NLA_F_NESTED, Data: binaryutil.BigEndian.PutUint32(flags)})
}
2019-08-27 10:52:21 -05:00
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)
}
item = append(item, netlink.Attribute{Type: unix.NFTA_SET_ELEM_KEY | unix.NLA_F_NESTED, Data: encodedKey})
if s.HasTimeout && v.Timeout != 0 {
// Set has Timeout flag set, which means an individual element can specify its own timeout.
item = append(item, netlink.Attribute{Type: unix.NFTA_SET_ELEM_TIMEOUT, Data: binaryutil.BigEndian.PutUint64(uint64(v.Timeout.Milliseconds()))})
}
2019-08-28 01:51:13 -05:00
// The following switch statement deal with 3 different types of elements.
// 1. v is an element of vmap
// 2. v is an element of a regular map
// 3. v is an element of a regular set (default)
switch {
case v.VerdictData != nil:
// Since VerdictData is not nil, v is vmap element, need to add to the attributes
encodedVal := []byte{}
encodedKind, err := netlink.MarshalAttributes([]netlink.Attribute{
{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)
}
encodedVal = append(encodedVal, encodedKind...)
if len(v.VerdictData.Chain) != 0 {
encodedChain, err := netlink.MarshalAttributes([]netlink.Attribute{
{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)
}
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)
}
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
2019-08-27 10:52:21 -05:00
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)
}
2019-08-28 01:51:13 -05:00
item = append(item, netlink.Attribute{Type: unix.NFTA_SET_ELEM_DATA | unix.NLA_F_NESTED, Data: encodedVal})
2019-08-28 01:51:13 -05:00
default:
// If niether of previous cases matche, it means 'e' is an element of a regular Set, no need to add to the attributes
}
encodedItem, err := netlink.MarshalAttributes(item)
if err != nil {
return nil, fmt.Errorf("marshal item %d: %v", i, err)
}
elements = append(elements, netlink.Attribute{Type: uint16(i+1) | unix.NLA_F_NESTED, Data: encodedItem})
}
encodedElem, err := netlink.MarshalAttributes(elements)
if err != nil {
return nil, 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
}
// AddSet adds the specified Set.
func (cc *Conn) AddSet(s *Set, vals []SetElement) error {
cc.Lock()
defer cc.Unlock()
// Based on nft implementation & linux source.
// Link: https://github.com/torvalds/linux/blob/49a57857aeea06ca831043acbb0fa5e0f50602fd/net/netfilter/nf_tables_api.c#L3395
// Another reference: https://git.netfilter.org/nftables/tree/src
if s.Anonymous && !s.Constant {
return errors.New("anonymous structs must be constant")
}
if s.ID == 0 {
allocSetID++
s.ID = allocSetID
if s.Anonymous {
s.Name = "__set%d"
2019-08-27 10:52:21 -05:00
if s.IsMap {
s.Name = "__map%d"
}
}
}
var flags uint32
if s.Anonymous {
flags |= unix.NFT_SET_ANONYMOUS
}
if s.Constant {
flags |= unix.NFT_SET_CONSTANT
}
if s.Interval {
flags |= unix.NFT_SET_INTERVAL
}
2019-08-27 10:52:21 -05:00
if s.IsMap {
flags |= unix.NFT_SET_MAP
}
if s.HasTimeout {
flags |= unix.NFT_SET_TIMEOUT
}
tableInfo := []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_FLAGS, Data: binaryutil.BigEndian.PutUint32(flags)},
{Type: unix.NFTA_SET_KEY_TYPE, Data: binaryutil.BigEndian.PutUint32(s.KeyType.nftMagic)},
{Type: unix.NFTA_SET_KEY_LEN, Data: binaryutil.BigEndian.PutUint32(s.KeyType.Bytes)},
{Type: unix.NFTA_SET_ID, Data: binaryutil.BigEndian.PutUint32(s.ID)},
}
2019-08-27 10:52:21 -05:00
if s.IsMap {
2019-08-28 01:51:13 -05:00
// Check if it is vmap case
if s.DataType.nftMagic == 1 {
// For Verdict data type, the expected magic is 0xfffff0
tableInfo = append(tableInfo, netlink.Attribute{Type: unix.NFTA_SET_DATA_TYPE, Data: binaryutil.BigEndian.PutUint32(uint32(unix.NFT_DATA_VERDICT))},
netlink.Attribute{Type: unix.NFTA_SET_DATA_LEN, Data: binaryutil.BigEndian.PutUint32(s.DataType.Bytes)})
} else {
tableInfo = append(tableInfo, netlink.Attribute{Type: unix.NFTA_SET_DATA_TYPE, Data: binaryutil.BigEndian.PutUint32(s.DataType.nftMagic)},
netlink.Attribute{Type: unix.NFTA_SET_DATA_LEN, Data: binaryutil.BigEndian.PutUint32(s.DataType.Bytes)})
}
2019-08-27 10:52:21 -05:00
}
if s.HasTimeout && s.Timeout != 0 {
// If Set's global timeout is specified, add it to set's attributes
tableInfo = append(tableInfo, netlink.Attribute{Type: unix.NFTA_SET_TIMEOUT, Data: binaryutil.BigEndian.PutUint64(uint64(s.Timeout.Milliseconds()))})
}
2019-08-27 10:52:21 -05:00
if s.Constant {
// nft cli tool adds the number of elements to set/map's descriptor
// It make sense to do only if a set or map are constant, otherwise skip NFTA_SET_DESC attribute
numberOfElements, err := netlink.MarshalAttributes([]netlink.Attribute{
{Type: unix.NFTA_DATA_VALUE, Data: binaryutil.BigEndian.PutUint32(uint32(len(vals)))},
})
if err != nil {
return fmt.Errorf("fail to marshal number of elements %d: %v", len(vals), err)
}
tableInfo = append(tableInfo, netlink.Attribute{Type: unix.NLA_F_NESTED | unix.NFTA_SET_DESC, Data: numberOfElements})
}
if s.Anonymous || s.Constant || s.Interval {
2019-08-27 10:52:21 -05:00
tableInfo = append(tableInfo,
// Semantically useless - kept for binary compatability with nft
netlink.Attribute{Type: unix.NFTA_SET_USERDATA, Data: []byte("\x00\x04\x02\x00\x00\x00")})
}
cc.messages = append(cc.messages, netlink.Message{
Header: netlink.Header{
Type: netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_NEWSET),
Flags: netlink.Request | netlink.Acknowledge | netlink.Create,
},
Data: append(extraHeader(uint8(s.Table.Family), 0), cc.marshalAttr(tableInfo)...),
})
// 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, netlink.Message{
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
}
// DelSet deletes a specific set, along with all elements it contains.
func (cc *Conn) DelSet(s *Set) {
cc.Lock()
defer cc.Unlock()
data := cc.marshalAttr([]netlink.Attribute{
{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{
Header: netlink.Header{
Type: netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_DELSET),
Flags: netlink.Request | netlink.Acknowledge,
},
Data: append(extraHeader(uint8(s.Table.Family), 0), data...),
})
}
// SetDeleteElements deletes data points from an nftables set.
func (cc *Conn) SetDeleteElements(s *Set, vals []SetElement) error {
cc.Lock()
defer cc.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, netlink.Message{
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
}
2019-10-23 08:10:52 -05:00
// FlushSet deletes all data points from an nftables set.
func (cc *Conn) FlushSet(s *Set) {
cc.Lock()
defer cc.Unlock()
2019-10-23 08:10:52 -05:00
data := cc.marshalAttr([]netlink.Attribute{
{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{
Header: netlink.Header{
Type: netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_DELSETELEM),
Flags: netlink.Request | netlink.Acknowledge,
},
Data: append(extraHeader(uint8(s.Table.Family), 0), data...),
})
}
var setHeaderType = netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_NEWSET)
func setsFromMsg(msg netlink.Message) (*Set, error) {
if got, want := msg.Header.Type, setHeaderType; got != want {
return nil, fmt.Errorf("unexpected header type: got %v, want %v", got, want)
}
ad, err := netlink.NewAttributeDecoder(msg.Data[4:])
if err != nil {
return nil, err
}
ad.ByteOrder = binary.BigEndian
var set Set
for ad.Next() {
switch ad.Type() {
case unix.NFTA_SET_NAME:
set.Name = ad.String()
case unix.NFTA_SET_ID:
set.ID = binary.BigEndian.Uint32(ad.Bytes())
case unix.NFTA_SET_TIMEOUT:
set.Timeout = time.Duration(time.Millisecond * time.Duration(binary.BigEndian.Uint64(ad.Bytes())))
set.HasTimeout = true
case unix.NFTA_SET_FLAGS:
flags := ad.Uint32()
set.Constant = (flags & unix.NFT_SET_CONSTANT) != 0
set.Anonymous = (flags & unix.NFT_SET_ANONYMOUS) != 0
set.Interval = (flags & unix.NFT_SET_INTERVAL) != 0
2019-08-27 10:52:21 -05:00
set.IsMap = (flags & unix.NFTA_SET_TABLE) != 0
set.HasTimeout = (flags & unix.NFTA_SET_TIMEOUT) != 0
case unix.NFTA_SET_KEY_TYPE:
nftMagic := ad.Uint32()
if invalidMagic, ok := validateKeyType(nftMagic); !ok {
return nil, fmt.Errorf("could not determine key type %+v", invalidMagic)
2019-08-27 10:52:21 -05:00
}
set.KeyType.nftMagic = nftMagic
2019-08-27 10:52:21 -05:00
case unix.NFTA_SET_DATA_TYPE:
nftMagic := ad.Uint32()
// Special case for the data type verdict, in the message it is stored as 0xffffff00 but it is defined as 1
if nftMagic == 0xffffff00 {
set.KeyType = TypeVerdict
break
}
2019-08-27 10:52:21 -05:00
for _, dt := range nftDatatypes {
if nftMagic == dt.nftMagic {
set.DataType = dt
break
}
}
if set.DataType.nftMagic == 0 {
return nil, fmt.Errorf("could not determine data type %x", nftMagic)
}
}
}
return &set, nil
}
func validateKeyType(bits uint32) ([]uint32, bool) {
var unpackTypes []uint32
var invalidTypes []uint32
found := false
valid := true
for bits != 0 {
unpackTypes = append(unpackTypes, bits&SetConcatTypeMask)
bits = bits >> SetConcatTypeBits
}
for _, t := range unpackTypes {
for _, dt := range nftDatatypes {
if t == dt.nftMagic {
found = true
}
}
if !found {
invalidTypes = append(invalidTypes, t)
valid = false
}
found = false
}
return invalidTypes, valid
}
var elemHeaderType = netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_NEWSETELEM)
func elementsFromMsg(msg netlink.Message) ([]SetElement, error) {
if got, want := msg.Header.Type, elemHeaderType; got != want {
return nil, fmt.Errorf("unexpected header type: got %v, want %v", got, want)
}
ad, err := netlink.NewAttributeDecoder(msg.Data[4:])
if err != nil {
return nil, err
}
ad.ByteOrder = binary.BigEndian
var elements []SetElement
for ad.Next() {
b := ad.Bytes()
2019-08-13 15:19:49 -05:00
if ad.Type() == unix.NFTA_SET_ELEM_LIST_ELEMENTS {
ad, err := netlink.NewAttributeDecoder(b)
if err != nil {
return nil, err
}
ad.ByteOrder = binary.BigEndian
for ad.Next() {
2019-08-13 15:19:49 -05:00
var elem SetElement
switch ad.Type() {
2019-08-13 15:19:49 -05:00
case unix.NFTA_LIST_ELEM:
ad.Do(elem.decode())
}
2019-08-13 15:19:49 -05:00
elements = append(elements, elem)
}
}
}
return elements, nil
}
// GetSets returns the sets in the specified table.
func (cc *Conn) GetSets(t *Table) ([]*Set, error) {
conn, err := cc.dialNetlink()
if err != nil {
return nil, err
}
defer conn.Close()
data, err := netlink.MarshalAttributes([]netlink.Attribute{
{Type: unix.NFTA_SET_TABLE, Data: []byte(t.Name + "\x00")},
})
if err != nil {
return nil, err
}
message := netlink.Message{
Header: netlink.Header{
Type: netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_GETSET),
Flags: netlink.Request | netlink.Acknowledge | netlink.Dump,
},
Data: append(extraHeader(uint8(t.Family), 0), data...),
}
if _, err := conn.SendMessages([]netlink.Message{message}); err != nil {
return nil, fmt.Errorf("SendMessages: %v", err)
}
reply, err := conn.Receive()
if err != nil {
return nil, fmt.Errorf("Receive: %v", err)
}
var sets []*Set
for _, msg := range reply {
s, err := setsFromMsg(msg)
if err != nil {
return nil, err
}
s.Table = &Table{Name: t.Name, Use: t.Use, Flags: t.Flags, Family: t.Family}
sets = append(sets, s)
}
return sets, nil
}
// GetSetByName returns the set in the specified table if matching name is found.
func (cc *Conn) GetSetByName(t *Table, name string) (*Set, error) {
conn, err := cc.dialNetlink()
if err != nil {
return nil, err
}
defer conn.Close()
data, err := netlink.MarshalAttributes([]netlink.Attribute{
{Type: unix.NFTA_SET_TABLE, Data: []byte(t.Name + "\x00")},
{Type: unix.NFTA_SET_NAME, Data: []byte(name + "\x00")},
})
if err != nil {
return nil, err
}
message := netlink.Message{
Header: netlink.Header{
Type: netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_GETSET),
Flags: netlink.Request | netlink.Acknowledge,
},
Data: append(extraHeader(uint8(t.Family), 0), data...),
}
if _, err := conn.SendMessages([]netlink.Message{message}); err != nil {
return nil, fmt.Errorf("SendMessages: %w", err)
}
reply, err := conn.Receive()
if err != nil {
return nil, fmt.Errorf("Receive: %w", err)
}
if len(reply) != 1 {
return nil, fmt.Errorf("Receive: expected to receive 1 message but got %d", len(reply))
}
rs, err := setsFromMsg(reply[0])
if err != nil {
return nil, err
}
rs.Table = &Table{Name: t.Name, Use: t.Use, Flags: t.Flags, Family: t.Family}
return rs, nil
}
// GetSetElements returns the elements in the specified set.
func (cc *Conn) GetSetElements(s *Set) ([]SetElement, error) {
conn, err := cc.dialNetlink()
if err != nil {
return nil, err
}
defer conn.Close()
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")},
})
if err != nil {
return nil, err
}
message := netlink.Message{
Header: netlink.Header{
Type: netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_GETSETELEM),
Flags: netlink.Request | netlink.Acknowledge | netlink.Dump,
},
Data: append(extraHeader(uint8(s.Table.Family), 0), data...),
}
if _, err := conn.SendMessages([]netlink.Message{message}); err != nil {
return nil, fmt.Errorf("SendMessages: %v", err)
}
reply, err := conn.Receive()
if err != nil {
return nil, fmt.Errorf("Receive: %v", err)
}
var elems []SetElement
for _, msg := range reply {
s, err := elementsFromMsg(msg)
if err != nil {
return nil, err
}
elems = append(elems, s...)
}
return elems, nil
}