955 lines
32 KiB
Go
955 lines
32 KiB
Go
// 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"
|
|
|
|
"github.com/google/nftables/binaryutil"
|
|
"github.com/google/nftables/expr"
|
|
"github.com/google/nftables/internal/parseexprfunc"
|
|
"github.com/google/nftables/userdata"
|
|
"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
|
|
// below consts added because not found in go unix package
|
|
// https://git.netfilter.org/nftables/tree/include/linux/netfilter/nf_tables.h?id=d1289bff58e1878c3162f574c603da993e29b113#n306
|
|
NFT_SET_CONCAT = 0x80
|
|
// https://git.netfilter.org/nftables/tree/include/linux/netfilter/nf_tables.h?id=d1289bff58e1878c3162f574c603da993e29b113#n330
|
|
NFTA_SET_DESC_CONCAT = 2
|
|
// https://git.netfilter.org/nftables/tree/include/linux/netfilter/nf_tables.h?id=d1289bff58e1878c3162f574c603da993e29b113#n428
|
|
NFTA_SET_ELEM_KEY_END = 10
|
|
// https://git.netfilter.org/nftables/tree/include/linux/netfilter/nf_tables.h?id=d1289bff58e1878c3162f574c603da993e29b113#n429
|
|
NFTA_SET_ELEM_EXPRESSIONS = 0x11
|
|
)
|
|
|
|
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
|
|
}
|
|
|
|
// 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/include/datatype.h
|
|
var (
|
|
TypeInvalid = SetDatatype{Name: "invalid", nftMagic: 0}
|
|
TypeVerdict = SetDatatype{Name: "verdict", Bytes: 0, nftMagic: 1}
|
|
TypeNFProto = SetDatatype{Name: "nf_proto", Bytes: 1, nftMagic: 2}
|
|
TypeBitmask = SetDatatype{Name: "bitmask", Bytes: 0, nftMagic: 3}
|
|
TypeInteger = SetDatatype{Name: "integer", Bytes: 4, nftMagic: 4}
|
|
TypeString = SetDatatype{Name: "string", Bytes: 0, nftMagic: 5}
|
|
TypeLLAddr = SetDatatype{Name: "ll_addr", Bytes: 0, nftMagic: 6}
|
|
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}
|
|
TypeEtherType = SetDatatype{Name: "ether_type", Bytes: 2, nftMagic: 10}
|
|
TypeARPOp = SetDatatype{Name: "arp_op", Bytes: 2, nftMagic: 11}
|
|
TypeInetProto = SetDatatype{Name: "inet_proto", Bytes: 1, nftMagic: 12}
|
|
TypeInetService = SetDatatype{Name: "inet_service", Bytes: 2, nftMagic: 13}
|
|
TypeICMPType = SetDatatype{Name: "icmp_type", Bytes: 1, nftMagic: 14}
|
|
TypeTCPFlag = SetDatatype{Name: "tcp_flag", Bytes: 1, nftMagic: 15}
|
|
TypeDCCPPktType = SetDatatype{Name: "dccp_pkttype", Bytes: 1, nftMagic: 16}
|
|
TypeMHType = SetDatatype{Name: "mh_type", Bytes: 1, nftMagic: 17}
|
|
TypeTime = SetDatatype{Name: "time", Bytes: 8, nftMagic: 18}
|
|
TypeMark = SetDatatype{Name: "mark", Bytes: 4, nftMagic: 19}
|
|
TypeIFIndex = SetDatatype{Name: "iface_index", Bytes: 4, nftMagic: 20}
|
|
TypeARPHRD = SetDatatype{Name: "iface_type", Bytes: 2, nftMagic: 21}
|
|
TypeRealm = SetDatatype{Name: "realm", Bytes: 4, nftMagic: 22}
|
|
TypeClassID = SetDatatype{Name: "classid", Bytes: 4, nftMagic: 23}
|
|
TypeUID = SetDatatype{Name: "uid", Bytes: sizeOfUIDT, nftMagic: 24}
|
|
TypeGID = SetDatatype{Name: "gid", Bytes: sizeOfGIDT, nftMagic: 25}
|
|
TypeCTState = SetDatatype{Name: "ct_state", Bytes: 4, nftMagic: 26}
|
|
TypeCTDir = SetDatatype{Name: "ct_dir", Bytes: 1, nftMagic: 27}
|
|
TypeCTStatus = SetDatatype{Name: "ct_status", Bytes: 4, nftMagic: 28}
|
|
TypeICMP6Type = SetDatatype{Name: "icmpv6_type", Bytes: 1, nftMagic: 29}
|
|
TypeCTLabel = SetDatatype{Name: "ct_label", Bytes: ctLabelBitSize / 8, nftMagic: 30}
|
|
TypePktType = SetDatatype{Name: "pkt_type", Bytes: 1, nftMagic: 31}
|
|
TypeICMPCode = SetDatatype{Name: "icmp_code", Bytes: 1, nftMagic: 32}
|
|
TypeICMPV6Code = SetDatatype{Name: "icmpv6_code", Bytes: 1, nftMagic: 33}
|
|
TypeICMPXCode = SetDatatype{Name: "icmpx_code", Bytes: 1, nftMagic: 34}
|
|
TypeDevGroup = SetDatatype{Name: "devgroup", Bytes: 4, nftMagic: 35}
|
|
TypeDSCP = SetDatatype{Name: "dscp", Bytes: 1, nftMagic: 36}
|
|
TypeECN = SetDatatype{Name: "ecn", Bytes: 1, nftMagic: 37}
|
|
TypeFIBAddr = SetDatatype{Name: "fib_addrtype", Bytes: 4, nftMagic: 38}
|
|
TypeBoolean = SetDatatype{Name: "boolean", Bytes: 1, nftMagic: 39}
|
|
TypeCTEventBit = SetDatatype{Name: "ct_event", Bytes: 4, nftMagic: 40}
|
|
TypeIFName = SetDatatype{Name: "ifname", Bytes: ifNameSize, nftMagic: 41}
|
|
TypeIGMPType = SetDatatype{Name: "igmp_type", Bytes: 1, nftMagic: 42}
|
|
TypeTimeDate = SetDatatype{Name: "time", Bytes: 8, nftMagic: 43}
|
|
TypeTimeHour = SetDatatype{Name: "hour", Bytes: 4, nftMagic: 44}
|
|
TypeTimeDay = SetDatatype{Name: "day", Bytes: 1, nftMagic: 45}
|
|
TypeCGroupV2 = SetDatatype{Name: "cgroupsv2", Bytes: 8, nftMagic: 46}
|
|
|
|
nftDatatypes = []SetDatatype{
|
|
TypeVerdict,
|
|
TypeNFProto,
|
|
TypeBitmask,
|
|
TypeInteger,
|
|
TypeString,
|
|
TypeLLAddr,
|
|
TypeIPAddr,
|
|
TypeIP6Addr,
|
|
TypeEtherAddr,
|
|
TypeEtherType,
|
|
TypeARPOp,
|
|
TypeInetProto,
|
|
TypeInetService,
|
|
TypeICMPType,
|
|
TypeTCPFlag,
|
|
TypeDCCPPktType,
|
|
TypeMHType,
|
|
TypeTime,
|
|
TypeMark,
|
|
TypeIFIndex,
|
|
TypeARPHRD,
|
|
TypeRealm,
|
|
TypeClassID,
|
|
TypeUID,
|
|
TypeGID,
|
|
TypeCTState,
|
|
TypeCTDir,
|
|
TypeCTStatus,
|
|
TypeICMP6Type,
|
|
TypeCTLabel,
|
|
TypePktType,
|
|
TypeICMPCode,
|
|
TypeICMPV6Code,
|
|
TypeICMPXCode,
|
|
TypeDevGroup,
|
|
TypeDSCP,
|
|
TypeECN,
|
|
TypeFIBAddr,
|
|
TypeBoolean,
|
|
TypeCTEventBit,
|
|
TypeIFName,
|
|
TypeIGMPType,
|
|
TypeTimeDate,
|
|
TypeTimeHour,
|
|
TypeTimeDay,
|
|
TypeCGroupV2,
|
|
}
|
|
|
|
// ctLabelBitSize is defined in https://git.netfilter.org/nftables/tree/src/ct.c.
|
|
ctLabelBitSize uint32 = 128
|
|
|
|
// ifNameSize is called IFNAMSIZ in linux/if.h.
|
|
ifNameSize uint32 = 16
|
|
|
|
// bits/typesizes.h
|
|
sizeOfUIDT uint32 = 4
|
|
sizeOfGIDT uint32 = 4
|
|
)
|
|
|
|
var nftDatatypesByName map[string]SetDatatype
|
|
var nftDatatypesByMagic map[uint32]SetDatatype
|
|
|
|
// Create maps for efficient datatype lookup.
|
|
func init() {
|
|
nftDatatypesByName = make(map[string]SetDatatype, len(nftDatatypes))
|
|
nftDatatypesByMagic = make(map[uint32]SetDatatype, len(nftDatatypes))
|
|
for _, dt := range nftDatatypes {
|
|
nftDatatypesByName[dt.Name] = dt
|
|
nftDatatypesByMagic[dt.nftMagic] = dt
|
|
}
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
// ConcatSetTypeElements uses the ConcatSetType name to calculate and return
|
|
// a list of base types which were used to construct the concatenated type
|
|
func ConcatSetTypeElements(t SetDatatype) []SetDatatype {
|
|
names := strings.Split(t.Name, " . ")
|
|
types := make([]SetDatatype, len(names))
|
|
for i, n := range names {
|
|
types[i] = nftDatatypesByName[n]
|
|
}
|
|
return types
|
|
}
|
|
|
|
// 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
|
|
AutoMerge bool
|
|
IsMap bool
|
|
HasTimeout bool
|
|
Counter bool
|
|
// Can be updated per evaluation path, per `nft list ruleset`
|
|
// indicates that set contains "flags dynamic"
|
|
// https://git.netfilter.org/libnftnl/tree/include/linux/netfilter/nf_tables.h?id=84d12cfacf8ddd857a09435f3d982ab6250d250c#n298
|
|
Dynamic bool
|
|
// Indicates that the set contains a concatenation
|
|
// https://git.netfilter.org/nftables/tree/include/linux/netfilter/nf_tables.h?id=d1289bff58e1878c3162f574c603da993e29b113#n306
|
|
Concatenation bool
|
|
Timeout time.Duration
|
|
KeyType SetDatatype
|
|
DataType SetDatatype
|
|
// Either host (binaryutil.NativeEndian) or big (binaryutil.BigEndian) endian as per
|
|
// https://git.netfilter.org/nftables/tree/include/datatype.h?id=d486c9e626405e829221b82d7355558005b26d8a#n109
|
|
KeyByteOrder binaryutil.ByteOrder
|
|
}
|
|
|
|
// SetElement represents a data point within a set.
|
|
type SetElement struct {
|
|
Key []byte
|
|
Val []byte
|
|
// Field used for definition of ending interval value in concatenated types
|
|
// https://git.netfilter.org/libnftnl/tree/include/set_elem.h?id=e2514c0eff4da7e8e0aabd410f7b7d0b7564c880#n11
|
|
KeyEnd []byte
|
|
IntervalEnd bool
|
|
// 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
|
|
|
|
// Life left of the "timeout" elements
|
|
Expires time.Duration
|
|
|
|
Counter *expr.Counter
|
|
}
|
|
|
|
func (s *SetElement) decode(fam byte) 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 NFTA_SET_ELEM_KEY_END:
|
|
s.KeyEnd, 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.Millisecond * time.Duration(ad.Uint64())
|
|
case unix.NFTA_SET_ELEM_EXPIRATION:
|
|
s.Expires = time.Millisecond * time.Duration(ad.Uint64())
|
|
case unix.NFTA_SET_ELEM_EXPR:
|
|
elems, err := parseexprfunc.ParseExprBytesFunc(fam, ad)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, elem := range elems {
|
|
switch item := elem.(type) {
|
|
case *expr.Counter:
|
|
s.Counter = item
|
|
}
|
|
}
|
|
}
|
|
}
|
|
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.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, netlink.Message{
|
|
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
|
|
}
|
|
|
|
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
|
|
item = append(item, netlink.Attribute{Type: unix.NFTA_SET_ELEM_FLAGS | unix.NLA_F_NESTED, Data: binaryutil.BigEndian.PutUint32(flags)})
|
|
}
|
|
|
|
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 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)
|
|
}
|
|
item = append(item, netlink.Attribute{Type: NFTA_SET_ELEM_KEY_END | unix.NLA_F_NESTED, Data: encodedKeyEnd})
|
|
}
|
|
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()))})
|
|
}
|
|
// 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
|
|
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)
|
|
}
|
|
|
|
item = append(item, netlink.Attribute{Type: unix.NFTA_SET_ELEM_DATA | unix.NLA_F_NESTED, Data: encodedVal})
|
|
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.mu.Lock()
|
|
defer cc.mu.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"
|
|
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
|
|
}
|
|
if s.IsMap {
|
|
flags |= unix.NFT_SET_MAP
|
|
}
|
|
if s.HasTimeout {
|
|
flags |= unix.NFT_SET_TIMEOUT
|
|
}
|
|
if s.Dynamic {
|
|
flags |= unix.NFT_SET_EVAL
|
|
}
|
|
if s.Concatenation {
|
|
flags |= NFT_SET_CONCAT
|
|
}
|
|
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)},
|
|
}
|
|
if s.IsMap {
|
|
// 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)})
|
|
}
|
|
}
|
|
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()))})
|
|
}
|
|
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.Concatenation {
|
|
// Length of concatenated types is a must, otherwise segfaults when executing nft list ruleset
|
|
var concatDefinition []byte
|
|
elements := ConcatSetTypeElements(s.KeyType)
|
|
for i, v := range elements {
|
|
// Marshal base type size value
|
|
valData, err := netlink.MarshalAttributes([]netlink.Attribute{
|
|
{Type: unix.NFTA_DATA_VALUE, Data: binaryutil.BigEndian.PutUint32(v.Bytes)},
|
|
})
|
|
if err != nil {
|
|
return fmt.Errorf("fail to marshal element key size %d: %v", i, err)
|
|
}
|
|
// Marshal base type size description
|
|
descSize, err := netlink.MarshalAttributes([]netlink.Attribute{
|
|
{Type: unix.NFTA_SET_DESC_SIZE, Data: valData},
|
|
})
|
|
if err != nil {
|
|
return fmt.Errorf("fail to marshal base type size description: %w", err)
|
|
}
|
|
concatDefinition = append(concatDefinition, descSize...)
|
|
}
|
|
// Marshal all base type descriptions into concatenation size description
|
|
concatBytes, err := netlink.MarshalAttributes([]netlink.Attribute{{Type: unix.NLA_F_NESTED | NFTA_SET_DESC_CONCAT, Data: concatDefinition}})
|
|
if err != nil {
|
|
return fmt.Errorf("fail to marshal concat definition %v", err)
|
|
}
|
|
// Marshal concat size description as set description
|
|
tableInfo = append(tableInfo, netlink.Attribute{Type: unix.NLA_F_NESTED | unix.NFTA_SET_DESC, Data: concatBytes})
|
|
}
|
|
|
|
// https://git.netfilter.org/libnftnl/tree/include/udata.h#n17
|
|
var userData []byte
|
|
|
|
if s.Anonymous || s.Constant || s.Interval || s.KeyByteOrder == binaryutil.BigEndian {
|
|
// Semantically useless - kept for binary compatability with nft
|
|
userData = userdata.AppendUint32(userData, userdata.NFTNL_UDATA_SET_KEYBYTEORDER, 2)
|
|
} else if s.KeyByteOrder == binaryutil.NativeEndian {
|
|
// Per https://git.netfilter.org/nftables/tree/src/mnl.c?id=187c6d01d35722618c2711bbc49262c286472c8f#n1165
|
|
userData = userdata.AppendUint32(userData, userdata.NFTNL_UDATA_SET_KEYBYTEORDER, 1)
|
|
}
|
|
|
|
if s.Interval && s.AutoMerge {
|
|
// https://git.netfilter.org/nftables/tree/src/mnl.c?id=187c6d01d35722618c2711bbc49262c286472c8f#n1174
|
|
userData = userdata.AppendUint32(userData, userdata.NFTNL_UDATA_SET_MERGE_ELEMENTS, 1)
|
|
}
|
|
|
|
if len(userData) > 0 {
|
|
tableInfo = append(tableInfo, netlink.Attribute{Type: unix.NFTA_SET_USERDATA, Data: userData})
|
|
}
|
|
|
|
if s.Counter {
|
|
data, err := netlink.MarshalAttributes([]netlink.Attribute{
|
|
{Type: unix.NFTA_LIST_ELEM, Data: []byte("counter\x00")},
|
|
{Type: unix.NFTA_SET_ELEM_PAD | unix.NFTA_SET_ELEM_DATA, Data: []byte{}},
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
tableInfo = append(tableInfo, netlink.Attribute{Type: unix.NLA_F_NESTED | NFTA_SET_ELEM_EXPRESSIONS, Data: data})
|
|
}
|
|
|
|
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.mu.Lock()
|
|
defer cc.mu.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.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, 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
|
|
}
|
|
|
|
// FlushSet deletes all data points from an nftables set.
|
|
func (cc *Conn) FlushSet(s *Set) {
|
|
cc.mu.Lock()
|
|
defer cc.mu.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_DELSETELEM),
|
|
Flags: netlink.Request | netlink.Acknowledge,
|
|
},
|
|
Data: append(extraHeader(uint8(s.Table.Family), 0), data...),
|
|
})
|
|
}
|
|
|
|
var (
|
|
newSetHeaderType = netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_NEWSET)
|
|
delSetHeaderType = netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_DELSET)
|
|
)
|
|
|
|
func setsFromMsg(msg netlink.Message) (*Set, error) {
|
|
if got, want1, want2 := msg.Header.Type, newSetHeaderType, delSetHeaderType; got != want1 && got != want2 {
|
|
return nil, fmt.Errorf("unexpected header type: got %v, want %v or %v", got, want1, want2)
|
|
}
|
|
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
|
|
set.IsMap = (flags & unix.NFT_SET_MAP) != 0
|
|
set.HasTimeout = (flags & unix.NFT_SET_TIMEOUT) != 0
|
|
set.Concatenation = (flags & NFT_SET_CONCAT) != 0
|
|
case unix.NFTA_SET_KEY_TYPE:
|
|
nftMagic := ad.Uint32()
|
|
dt, err := parseSetDatatype(nftMagic)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("could not determine data type: %w", err)
|
|
}
|
|
set.KeyType = dt
|
|
case unix.NFTA_SET_KEY_LEN:
|
|
set.KeyType.Bytes = binary.BigEndian.Uint32(ad.Bytes())
|
|
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
|
|
}
|
|
dt, err := parseSetDatatype(nftMagic)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("could not determine data type: %w", err)
|
|
}
|
|
set.DataType = dt
|
|
case unix.NFTA_SET_DATA_LEN:
|
|
set.DataType.Bytes = binary.BigEndian.Uint32(ad.Bytes())
|
|
case unix.NFTA_SET_USERDATA:
|
|
data := ad.Bytes()
|
|
value, ok := userdata.GetUint32(data, userdata.NFTNL_UDATA_SET_MERGE_ELEMENTS)
|
|
set.AutoMerge = ok && value == 1
|
|
}
|
|
}
|
|
return &set, nil
|
|
}
|
|
|
|
func parseSetDatatype(magic uint32) (SetDatatype, error) {
|
|
types := make([]SetDatatype, 0, 32/SetConcatTypeBits)
|
|
for magic != 0 {
|
|
t := magic & SetConcatTypeMask
|
|
magic = magic >> SetConcatTypeBits
|
|
dt, ok := nftDatatypesByMagic[t]
|
|
if !ok {
|
|
return TypeInvalid, fmt.Errorf("could not determine data type %+v", dt)
|
|
}
|
|
// Because we start with the last type, we insert the later types at the front.
|
|
types = append([]SetDatatype{dt}, types...)
|
|
}
|
|
|
|
dt, err := ConcatSetType(types...)
|
|
if err != nil {
|
|
return TypeInvalid, fmt.Errorf("could not create data type: %w", err)
|
|
}
|
|
return dt, nil
|
|
}
|
|
|
|
var (
|
|
newElemHeaderType = netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_NEWSETELEM)
|
|
delElemHeaderType = netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_DELSETELEM)
|
|
)
|
|
|
|
func elementsFromMsg(fam byte, msg netlink.Message) ([]SetElement, error) {
|
|
if got, want1, want2 := msg.Header.Type, newElemHeaderType, delElemHeaderType; got != want1 && got != want2 {
|
|
return nil, fmt.Errorf("unexpected header type: got %v, want %v or %v", got, want1, want2)
|
|
}
|
|
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()
|
|
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() {
|
|
var elem SetElement
|
|
switch ad.Type() {
|
|
case unix.NFTA_LIST_ELEM:
|
|
ad.Do(elem.decode(fam))
|
|
}
|
|
elements = append(elements, elem)
|
|
}
|
|
}
|
|
}
|
|
return elements, nil
|
|
}
|
|
|
|
// GetSets returns the sets in the specified table.
|
|
func (cc *Conn) GetSets(t *Table) ([]*Set, error) {
|
|
conn, closer, err := cc.netlinkConn()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer func() { _ = closer() }()
|
|
|
|
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 := receiveAckAware(conn, message.Header.Flags)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("receiveAckAware: %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, closer, err := cc.netlinkConn()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer func() { _ = closer() }()
|
|
|
|
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 := receiveAckAware(conn, message.Header.Flags)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("receiveAckAware: %w", err)
|
|
}
|
|
|
|
if len(reply) != 1 {
|
|
return nil, fmt.Errorf("receiveAckAware: 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, closer, err := cc.netlinkConn()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
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")},
|
|
})
|
|
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 := receiveAckAware(conn, message.Header.Flags)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("receiveAckAware: %v", err)
|
|
}
|
|
var elems []SetElement
|
|
for _, msg := range reply {
|
|
s, err := elementsFromMsg(uint8(s.Table.Family), msg)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
elems = append(elems, s...)
|
|
}
|
|
|
|
return elems, nil
|
|
}
|