Added dynset exprs support (#173)
fixes https://github.com/google/nftables/issues/172 - Rearranged `exprFromMsg` function - Rearranged limit expr marshaling logic - Added dynamic flag for sets - Implemented connlimit - Added missing constants - Added tests
This commit is contained in:
parent
a346d51f53
commit
ec1e802faf
|
@ -0,0 +1,70 @@
|
||||||
|
// Copyright 2019 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 expr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
|
||||||
|
"github.com/google/nftables/binaryutil"
|
||||||
|
"github.com/mdlayher/netlink"
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Per https://git.netfilter.org/libnftnl/tree/include/linux/netfilter/nf_tables.h?id=84d12cfacf8ddd857a09435f3d982ab6250d250c#n1167
|
||||||
|
NFTA_CONNLIMIT_UNSPEC = iota
|
||||||
|
NFTA_CONNLIMIT_COUNT
|
||||||
|
NFTA_CONNLIMIT_FLAGS
|
||||||
|
NFT_CONNLIMIT_F_INV = 1
|
||||||
|
)
|
||||||
|
|
||||||
|
// Per https://git.netfilter.org/libnftnl/tree/src/expr/connlimit.c?id=84d12cfacf8ddd857a09435f3d982ab6250d250c
|
||||||
|
type Connlimit struct {
|
||||||
|
Count uint32
|
||||||
|
Flags uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Connlimit) marshal(fam byte) ([]byte, error) {
|
||||||
|
data, err := netlink.MarshalAttributes([]netlink.Attribute{
|
||||||
|
{Type: NFTA_CONNLIMIT_COUNT, Data: binaryutil.BigEndian.PutUint32(e.Count)},
|
||||||
|
{Type: NFTA_CONNLIMIT_FLAGS, Data: binaryutil.BigEndian.PutUint32(e.Flags)},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return netlink.MarshalAttributes([]netlink.Attribute{
|
||||||
|
{Type: unix.NFTA_EXPR_NAME, Data: []byte("connlimit\x00")},
|
||||||
|
{Type: unix.NLA_F_NESTED | unix.NFTA_EXPR_DATA, Data: data},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Connlimit) unmarshal(fam byte, data []byte) error {
|
||||||
|
ad, err := netlink.NewAttributeDecoder(data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
ad.ByteOrder = binary.BigEndian
|
||||||
|
for ad.Next() {
|
||||||
|
switch ad.Type() {
|
||||||
|
case NFTA_CONNLIMIT_COUNT:
|
||||||
|
e.Count = binaryutil.BigEndian.Uint32(ad.Bytes())
|
||||||
|
case NFTA_CONNLIMIT_FLAGS:
|
||||||
|
e.Flags = binaryutil.BigEndian.Uint32(ad.Bytes())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ad.Err()
|
||||||
|
}
|
|
@ -19,10 +19,18 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/google/nftables/binaryutil"
|
"github.com/google/nftables/binaryutil"
|
||||||
|
"github.com/google/nftables/internal/parseexprfunc"
|
||||||
"github.com/mdlayher/netlink"
|
"github.com/mdlayher/netlink"
|
||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Not yet supported by unix package
|
||||||
|
// https://cs.opensource.google/go/x/sys/+/c6bc011c:unix/ztypes_linux.go;l=2027-2036
|
||||||
|
const (
|
||||||
|
NFTA_DYNSET_EXPRESSIONS = 0xa
|
||||||
|
NFT_DYNSET_F_EXPR = (1 << 1)
|
||||||
|
)
|
||||||
|
|
||||||
// Dynset represent a rule dynamically adding or updating a set or a map based on an incoming packet.
|
// Dynset represent a rule dynamically adding or updating a set or a map based on an incoming packet.
|
||||||
type Dynset struct {
|
type Dynset struct {
|
||||||
SrcRegKey uint32
|
SrcRegKey uint32
|
||||||
|
@ -32,6 +40,7 @@ type Dynset struct {
|
||||||
Operation uint32
|
Operation uint32
|
||||||
Timeout time.Duration
|
Timeout time.Duration
|
||||||
Invert bool
|
Invert bool
|
||||||
|
Exprs []Any
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *Dynset) marshal(fam byte) ([]byte, error) {
|
func (e *Dynset) marshal(fam byte) ([]byte, error) {
|
||||||
|
@ -45,12 +54,43 @@ func (e *Dynset) marshal(fam byte) ([]byte, error) {
|
||||||
if e.Timeout != 0 {
|
if e.Timeout != 0 {
|
||||||
opAttrs = append(opAttrs, netlink.Attribute{Type: unix.NFTA_DYNSET_TIMEOUT, Data: binaryutil.BigEndian.PutUint64(uint64(e.Timeout.Milliseconds()))})
|
opAttrs = append(opAttrs, netlink.Attribute{Type: unix.NFTA_DYNSET_TIMEOUT, Data: binaryutil.BigEndian.PutUint64(uint64(e.Timeout.Milliseconds()))})
|
||||||
}
|
}
|
||||||
|
var flags uint32
|
||||||
if e.Invert {
|
if e.Invert {
|
||||||
opAttrs = append(opAttrs, netlink.Attribute{Type: unix.NFTA_DYNSET_FLAGS, Data: binaryutil.BigEndian.PutUint32(unix.NFT_DYNSET_F_INV)})
|
flags |= unix.NFT_DYNSET_F_INV
|
||||||
}
|
}
|
||||||
|
|
||||||
opAttrs = append(opAttrs,
|
opAttrs = append(opAttrs,
|
||||||
netlink.Attribute{Type: unix.NFTA_DYNSET_SET_NAME, Data: []byte(e.SetName + "\x00")},
|
netlink.Attribute{Type: unix.NFTA_DYNSET_SET_NAME, Data: []byte(e.SetName + "\x00")},
|
||||||
netlink.Attribute{Type: unix.NFTA_DYNSET_SET_ID, Data: binaryutil.BigEndian.PutUint32(e.SetID)})
|
netlink.Attribute{Type: unix.NFTA_DYNSET_SET_ID, Data: binaryutil.BigEndian.PutUint32(e.SetID)})
|
||||||
|
|
||||||
|
// Per https://git.netfilter.org/libnftnl/tree/src/expr/dynset.c?id=84d12cfacf8ddd857a09435f3d982ab6250d250c#n170
|
||||||
|
if len(e.Exprs) > 0 {
|
||||||
|
flags |= NFT_DYNSET_F_EXPR
|
||||||
|
switch len(e.Exprs) {
|
||||||
|
case 1:
|
||||||
|
exprData, err := Marshal(fam, e.Exprs[0])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
opAttrs = append(opAttrs, netlink.Attribute{Type: unix.NFTA_DYNSET_EXPR, Data: exprData})
|
||||||
|
default:
|
||||||
|
var elemAttrs []netlink.Attribute
|
||||||
|
for _, ex := range e.Exprs {
|
||||||
|
exprData, err := Marshal(fam, ex)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
elemAttrs = append(elemAttrs, netlink.Attribute{Type: unix.NFTA_LIST_ELEM, Data: exprData})
|
||||||
|
}
|
||||||
|
elemData, err := netlink.MarshalAttributes(elemAttrs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
opAttrs = append(opAttrs, netlink.Attribute{Type: NFTA_DYNSET_EXPRESSIONS, Data: elemData})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
opAttrs = append(opAttrs, netlink.Attribute{Type: unix.NFTA_DYNSET_FLAGS, Data: binaryutil.BigEndian.PutUint32(flags)})
|
||||||
|
|
||||||
opData, err := netlink.MarshalAttributes(opAttrs)
|
opData, err := netlink.MarshalAttributes(opAttrs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -84,7 +124,26 @@ func (e *Dynset) unmarshal(fam byte, data []byte) error {
|
||||||
e.Timeout = time.Duration(time.Millisecond * time.Duration(ad.Uint64()))
|
e.Timeout = time.Duration(time.Millisecond * time.Duration(ad.Uint64()))
|
||||||
case unix.NFTA_DYNSET_FLAGS:
|
case unix.NFTA_DYNSET_FLAGS:
|
||||||
e.Invert = (ad.Uint32() & unix.NFT_DYNSET_F_INV) != 0
|
e.Invert = (ad.Uint32() & unix.NFT_DYNSET_F_INV) != 0
|
||||||
|
case unix.NFTA_DYNSET_EXPR:
|
||||||
|
exprs, err := parseexprfunc.ParseExprBytesFunc(fam, ad, ad.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
e.setInterfaceExprs(exprs)
|
||||||
|
case NFTA_DYNSET_EXPRESSIONS:
|
||||||
|
exprs, err := parseexprfunc.ParseExprMsgFunc(fam, ad.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
e.setInterfaceExprs(exprs)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ad.Err()
|
return ad.Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (e *Dynset) setInterfaceExprs(exprs []interface{}) {
|
||||||
|
e.Exprs = make([]Any, len(exprs))
|
||||||
|
for i := range exprs {
|
||||||
|
e.Exprs[i] = exprs[i].(Any)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
121
expr/expr.go
121
expr/expr.go
|
@ -19,10 +19,41 @@ import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
|
|
||||||
"github.com/google/nftables/binaryutil"
|
"github.com/google/nftables/binaryutil"
|
||||||
|
"github.com/google/nftables/internal/parseexprfunc"
|
||||||
"github.com/mdlayher/netlink"
|
"github.com/mdlayher/netlink"
|
||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
parseexprfunc.ParseExprBytesFunc = func(fam byte, ad *netlink.AttributeDecoder, b []byte) ([]interface{}, error) {
|
||||||
|
exprs, err := exprsFromBytes(fam, ad, b)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
result := make([]interface{}, len(exprs))
|
||||||
|
for idx, expr := range exprs {
|
||||||
|
result[idx] = expr
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
parseexprfunc.ParseExprMsgFunc = func(fam byte, b []byte) ([]interface{}, error) {
|
||||||
|
ad, err := netlink.NewAttributeDecoder(b)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ad.ByteOrder = binary.BigEndian
|
||||||
|
var exprs []interface{}
|
||||||
|
for ad.Next() {
|
||||||
|
e, err := parseexprfunc.ParseExprBytesFunc(fam, ad, b)
|
||||||
|
if err != nil {
|
||||||
|
return e, err
|
||||||
|
}
|
||||||
|
exprs = append(exprs, e...)
|
||||||
|
}
|
||||||
|
return exprs, ad.Err()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Marshal serializes the specified expression into a byte slice.
|
// Marshal serializes the specified expression into a byte slice.
|
||||||
func Marshal(fam byte, e Any) ([]byte, error) {
|
func Marshal(fam byte, e Any) ([]byte, error) {
|
||||||
return e.marshal(fam)
|
return e.marshal(fam)
|
||||||
|
@ -33,6 +64,96 @@ func Unmarshal(fam byte, data []byte, e Any) error {
|
||||||
return e.unmarshal(fam, data)
|
return e.unmarshal(fam, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// exprsFromBytes parses nested raw expressions bytes
|
||||||
|
// to construct nftables expressions
|
||||||
|
func exprsFromBytes(fam byte, ad *netlink.AttributeDecoder, b []byte) ([]Any, error) {
|
||||||
|
var exprs []Any
|
||||||
|
ad.Do(func(b []byte) error {
|
||||||
|
ad, err := netlink.NewAttributeDecoder(b)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
ad.ByteOrder = binary.BigEndian
|
||||||
|
var name string
|
||||||
|
for ad.Next() {
|
||||||
|
switch ad.Type() {
|
||||||
|
case unix.NFTA_EXPR_NAME:
|
||||||
|
name = ad.String()
|
||||||
|
if name == "notrack" {
|
||||||
|
e := &Notrack{}
|
||||||
|
exprs = append(exprs, e)
|
||||||
|
}
|
||||||
|
case unix.NFTA_EXPR_DATA:
|
||||||
|
var e Any
|
||||||
|
switch name {
|
||||||
|
case "ct":
|
||||||
|
e = &Ct{}
|
||||||
|
case "range":
|
||||||
|
e = &Range{}
|
||||||
|
case "meta":
|
||||||
|
e = &Meta{}
|
||||||
|
case "cmp":
|
||||||
|
e = &Cmp{}
|
||||||
|
case "counter":
|
||||||
|
e = &Counter{}
|
||||||
|
case "payload":
|
||||||
|
e = &Payload{}
|
||||||
|
case "lookup":
|
||||||
|
e = &Lookup{}
|
||||||
|
case "immediate":
|
||||||
|
e = &Immediate{}
|
||||||
|
case "bitwise":
|
||||||
|
e = &Bitwise{}
|
||||||
|
case "redir":
|
||||||
|
e = &Redir{}
|
||||||
|
case "nat":
|
||||||
|
e = &NAT{}
|
||||||
|
case "limit":
|
||||||
|
e = &Limit{}
|
||||||
|
case "quota":
|
||||||
|
e = &Quota{}
|
||||||
|
case "dynset":
|
||||||
|
e = &Dynset{}
|
||||||
|
case "log":
|
||||||
|
e = &Log{}
|
||||||
|
case "exthdr":
|
||||||
|
e = &Exthdr{}
|
||||||
|
case "match":
|
||||||
|
e = &Match{}
|
||||||
|
case "target":
|
||||||
|
e = &Target{}
|
||||||
|
case "connlimit":
|
||||||
|
e = &Connlimit{}
|
||||||
|
}
|
||||||
|
if e == nil {
|
||||||
|
// TODO: introduce an opaque expression type so that users know
|
||||||
|
// something is here.
|
||||||
|
continue // unsupported expression type
|
||||||
|
}
|
||||||
|
|
||||||
|
ad.Do(func(b []byte) error {
|
||||||
|
if err := Unmarshal(fam, b, e); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Verdict expressions are a special-case of immediate expressions, so
|
||||||
|
// if the expression is an immediate writing nothing into the verdict
|
||||||
|
// register (invalid), re-parse it as a verdict expression.
|
||||||
|
if imm, isImmediate := e.(*Immediate); isImmediate && imm.Register == unix.NFT_REG_VERDICT && len(imm.Data) == 0 {
|
||||||
|
e = &Verdict{}
|
||||||
|
if err := Unmarshal(fam, b, e); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exprs = append(exprs, e)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ad.Err()
|
||||||
|
})
|
||||||
|
return exprs, ad.Err()
|
||||||
|
}
|
||||||
|
|
||||||
// Any is an interface implemented by any expression type.
|
// Any is an interface implemented by any expression type.
|
||||||
type Any interface {
|
type Any interface {
|
||||||
marshal(fam byte) ([]byte, error)
|
marshal(fam byte) ([]byte, error)
|
||||||
|
|
|
@ -72,24 +72,16 @@ type Limit struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *Limit) marshal(fam byte) ([]byte, error) {
|
func (l *Limit) marshal(fam byte) ([]byte, error) {
|
||||||
|
var flags uint32
|
||||||
|
if l.Over {
|
||||||
|
flags = unix.NFT_LIMIT_F_INV
|
||||||
|
}
|
||||||
attrs := []netlink.Attribute{
|
attrs := []netlink.Attribute{
|
||||||
{Type: unix.NFTA_LIMIT_TYPE, Data: binaryutil.BigEndian.PutUint32(uint32(l.Type))},
|
|
||||||
{Type: unix.NFTA_LIMIT_RATE, Data: binaryutil.BigEndian.PutUint64(l.Rate)},
|
{Type: unix.NFTA_LIMIT_RATE, Data: binaryutil.BigEndian.PutUint64(l.Rate)},
|
||||||
{Type: unix.NFTA_LIMIT_UNIT, Data: binaryutil.BigEndian.PutUint64(uint64(l.Unit))},
|
{Type: unix.NFTA_LIMIT_UNIT, Data: binaryutil.BigEndian.PutUint64(uint64(l.Unit))},
|
||||||
}
|
{Type: unix.NFTA_LIMIT_BURST, Data: binaryutil.BigEndian.PutUint32(l.Burst)},
|
||||||
|
{Type: unix.NFTA_LIMIT_TYPE, Data: binaryutil.BigEndian.PutUint32(uint32(l.Type))},
|
||||||
if l.Over {
|
{Type: unix.NFTA_LIMIT_FLAGS, Data: binaryutil.BigEndian.PutUint32(flags)},
|
||||||
attrs = append(attrs, netlink.Attribute{
|
|
||||||
Type: unix.NFTA_LIMIT_FLAGS,
|
|
||||||
Data: binaryutil.BigEndian.PutUint32(unix.NFT_LIMIT_F_INV),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if l.Burst != 0 {
|
|
||||||
attrs = append(attrs, netlink.Attribute{
|
|
||||||
Type: unix.NFTA_LIMIT_BURST,
|
|
||||||
Data: binaryutil.BigEndian.PutUint32(l.Burst),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
data, err := netlink.MarshalAttributes(attrs)
|
data, err := netlink.MarshalAttributes(attrs)
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
package parseexprfunc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/mdlayher/netlink"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ParseExprBytesFunc func(fam byte, ad *netlink.AttributeDecoder, b []byte) ([]interface{}, error)
|
||||||
|
ParseExprMsgFunc func(fam byte, b []byte) ([]interface{}, error)
|
||||||
|
)
|
215
nftables_test.go
215
nftables_test.go
|
@ -3313,6 +3313,221 @@ func TestDynset(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDynsetWithOneExpression(t *testing.T) {
|
||||||
|
// Create a new network namespace to test these operations,
|
||||||
|
// and tear down the namespace at test completion.
|
||||||
|
c, newNS := openSystemNFTConn(t)
|
||||||
|
defer cleanupSystemNFTConn(t, newNS)
|
||||||
|
// Clear all rules at the beginning + end of the test.
|
||||||
|
c.FlushRuleset()
|
||||||
|
defer c.FlushRuleset()
|
||||||
|
table := &nftables.Table{
|
||||||
|
Name: "filter",
|
||||||
|
Family: nftables.TableFamilyIPv4,
|
||||||
|
}
|
||||||
|
chain := &nftables.Chain{
|
||||||
|
Name: "forward",
|
||||||
|
Hooknum: nftables.ChainHookForward,
|
||||||
|
Table: table,
|
||||||
|
Priority: 0,
|
||||||
|
Type: nftables.ChainTypeFilter,
|
||||||
|
}
|
||||||
|
set := &nftables.Set{
|
||||||
|
Table: table,
|
||||||
|
Name: "myMeter",
|
||||||
|
KeyType: nftables.TypeIPAddr,
|
||||||
|
Dynamic: true,
|
||||||
|
}
|
||||||
|
c.AddTable(table)
|
||||||
|
c.AddChain(chain)
|
||||||
|
if err := c.AddSet(set, nil); err != nil {
|
||||||
|
t.Errorf("c.AddSet(myMeter) failed: %v", err)
|
||||||
|
}
|
||||||
|
if err := c.Flush(); err != nil {
|
||||||
|
t.Errorf("c.Flush() failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
rule := &nftables.Rule{
|
||||||
|
Table: table,
|
||||||
|
Chain: chain,
|
||||||
|
Exprs: []expr.Any{
|
||||||
|
&expr.Payload{
|
||||||
|
DestRegister: 1,
|
||||||
|
Base: expr.PayloadBaseNetworkHeader,
|
||||||
|
Offset: uint32(12),
|
||||||
|
Len: uint32(4),
|
||||||
|
},
|
||||||
|
&expr.Dynset{
|
||||||
|
SrcRegKey: 1,
|
||||||
|
SetName: set.Name,
|
||||||
|
Operation: uint32(unix.NFT_DYNSET_OP_ADD),
|
||||||
|
Exprs: []expr.Any{
|
||||||
|
&expr.Limit{
|
||||||
|
Type: expr.LimitTypePkts,
|
||||||
|
Rate: 200,
|
||||||
|
Unit: expr.LimitTimeSecond,
|
||||||
|
Burst: 5,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&expr.Verdict{
|
||||||
|
Kind: expr.VerdictDrop,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
c.AddRule(rule)
|
||||||
|
if err := c.Flush(); err != nil {
|
||||||
|
t.Errorf("c.Flush() failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
rules, err := c.GetRules(table, chain)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if got, want := len(rules), 1; got != want {
|
||||||
|
t.Fatalf("unexpected number of rules: got %d, want %d", got, want)
|
||||||
|
}
|
||||||
|
if got, want := len(rules[0].Exprs), 3; got != want {
|
||||||
|
t.Fatalf("unexpected number of exprs: got %d, want %d", got, want)
|
||||||
|
}
|
||||||
|
|
||||||
|
dynset, dynsetOk := rules[0].Exprs[1].(*expr.Dynset)
|
||||||
|
if !dynsetOk {
|
||||||
|
t.Fatalf("Exprs[0] is type %T, want *expr.Dynset", rules[0].Exprs[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
if got, want := len(dynset.Exprs), 1; got != want {
|
||||||
|
t.Fatalf("unexpected number of dynset.Exprs: got %d, want %d", got, want)
|
||||||
|
}
|
||||||
|
|
||||||
|
if got, want := dynset.SetName, set.Name; got != want {
|
||||||
|
t.Fatalf("dynset.SetName is %s, want %s", got, want)
|
||||||
|
}
|
||||||
|
|
||||||
|
if want := (&expr.Limit{
|
||||||
|
Type: expr.LimitTypePkts,
|
||||||
|
Rate: 200,
|
||||||
|
Unit: expr.LimitTimeSecond,
|
||||||
|
Burst: 5,
|
||||||
|
}); !reflect.DeepEqual(dynset.Exprs[0], want) {
|
||||||
|
t.Errorf("dynset.Exprs[0] expr = %+v, wanted %+v", dynset.Exprs[0], want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDynsetWithMultipleExpressions(t *testing.T) {
|
||||||
|
// Create a new network namespace to test these operations,
|
||||||
|
// and tear down the namespace at test completion.
|
||||||
|
c, newNS := openSystemNFTConn(t)
|
||||||
|
defer cleanupSystemNFTConn(t, newNS)
|
||||||
|
// Clear all rules at the beginning + end of the test.
|
||||||
|
c.FlushRuleset()
|
||||||
|
defer c.FlushRuleset()
|
||||||
|
table := &nftables.Table{
|
||||||
|
Name: "filter",
|
||||||
|
Family: nftables.TableFamilyIPv4,
|
||||||
|
}
|
||||||
|
chain := &nftables.Chain{
|
||||||
|
Name: "forward",
|
||||||
|
Hooknum: nftables.ChainHookForward,
|
||||||
|
Table: table,
|
||||||
|
Priority: 0,
|
||||||
|
Type: nftables.ChainTypeFilter,
|
||||||
|
}
|
||||||
|
set := &nftables.Set{
|
||||||
|
Table: table,
|
||||||
|
Name: "myMeter",
|
||||||
|
KeyType: nftables.TypeIPAddr,
|
||||||
|
Dynamic: true,
|
||||||
|
}
|
||||||
|
c.AddTable(table)
|
||||||
|
c.AddChain(chain)
|
||||||
|
if err := c.AddSet(set, nil); err != nil {
|
||||||
|
t.Errorf("c.AddSet(myMeter) failed: %v", err)
|
||||||
|
}
|
||||||
|
if err := c.Flush(); err != nil {
|
||||||
|
t.Errorf("c.Flush() failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
rule := &nftables.Rule{
|
||||||
|
Table: table,
|
||||||
|
Chain: chain,
|
||||||
|
Exprs: []expr.Any{
|
||||||
|
&expr.Payload{
|
||||||
|
DestRegister: 1,
|
||||||
|
Base: expr.PayloadBaseNetworkHeader,
|
||||||
|
Offset: uint32(12),
|
||||||
|
Len: uint32(4),
|
||||||
|
},
|
||||||
|
&expr.Dynset{
|
||||||
|
SrcRegKey: 1,
|
||||||
|
SetName: set.Name,
|
||||||
|
Operation: uint32(unix.NFT_DYNSET_OP_ADD),
|
||||||
|
Exprs: []expr.Any{
|
||||||
|
&expr.Connlimit{
|
||||||
|
Count: 20,
|
||||||
|
Flags: 1,
|
||||||
|
},
|
||||||
|
&expr.Limit{
|
||||||
|
Type: expr.LimitTypePkts,
|
||||||
|
Rate: 10,
|
||||||
|
Unit: expr.LimitTimeSecond,
|
||||||
|
Burst: 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&expr.Verdict{
|
||||||
|
Kind: expr.VerdictDrop,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
c.AddRule(rule)
|
||||||
|
if err := c.Flush(); err != nil {
|
||||||
|
t.Errorf("c.Flush() failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
rules, err := c.GetRules(table, chain)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if got, want := len(rules), 1; got != want {
|
||||||
|
t.Fatalf("unexpected number of rules: got %d, want %d", got, want)
|
||||||
|
}
|
||||||
|
if got, want := len(rules[0].Exprs), 3; got != want {
|
||||||
|
t.Fatalf("unexpected number of exprs: got %d, want %d", got, want)
|
||||||
|
}
|
||||||
|
|
||||||
|
dynset, dynsetOk := rules[0].Exprs[1].(*expr.Dynset)
|
||||||
|
if !dynsetOk {
|
||||||
|
t.Fatalf("Exprs[0] is type %T, want *expr.Dynset", rules[0].Exprs[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
if got, want := len(dynset.Exprs), 2; got != want {
|
||||||
|
t.Fatalf("unexpected number of dynset.Exprs: got %d, want %d", got, want)
|
||||||
|
}
|
||||||
|
|
||||||
|
if got, want := dynset.SetName, set.Name; got != want {
|
||||||
|
t.Fatalf("dynset.SetName is %s, want %s", got, want)
|
||||||
|
}
|
||||||
|
|
||||||
|
if want := (&expr.Connlimit{
|
||||||
|
Count: 20,
|
||||||
|
Flags: 1,
|
||||||
|
}); !reflect.DeepEqual(dynset.Exprs[0], want) {
|
||||||
|
t.Errorf("dynset.Exprs[0] expr = %+v, wanted %+v", dynset.Exprs[0], want)
|
||||||
|
}
|
||||||
|
|
||||||
|
if want := (&expr.Limit{
|
||||||
|
Type: expr.LimitTypePkts,
|
||||||
|
Rate: 10,
|
||||||
|
Unit: expr.LimitTimeSecond,
|
||||||
|
Burst: 2,
|
||||||
|
}); !reflect.DeepEqual(dynset.Exprs[1], want) {
|
||||||
|
t.Errorf("dynset.Exprs[1] expr = %+v, wanted %+v", dynset.Exprs[1], want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestConfigureNATRedirect(t *testing.T) {
|
func TestConfigureNATRedirect(t *testing.T) {
|
||||||
// The want byte sequences come from stracing nft(8), e.g.:
|
// The want byte sequences come from stracing nft(8), e.g.:
|
||||||
// strace -f -v -x -s 2048 -eraw=sendto nft add table ip nat
|
// strace -f -v -x -s 2048 -eraw=sendto nft add table ip nat
|
||||||
|
|
103
rule.go
103
rule.go
|
@ -20,6 +20,7 @@ import (
|
||||||
|
|
||||||
"github.com/google/nftables/binaryutil"
|
"github.com/google/nftables/binaryutil"
|
||||||
"github.com/google/nftables/expr"
|
"github.com/google/nftables/expr"
|
||||||
|
"github.com/google/nftables/internal/parseexprfunc"
|
||||||
"github.com/mdlayher/netlink"
|
"github.com/mdlayher/netlink"
|
||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
)
|
)
|
||||||
|
@ -215,99 +216,6 @@ func (cc *Conn) DelRule(r *Rule) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func exprsFromMsg(fam TableFamily, b []byte) ([]expr.Any, error) {
|
|
||||||
ad, err := netlink.NewAttributeDecoder(b)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
ad.ByteOrder = binary.BigEndian
|
|
||||||
var exprs []expr.Any
|
|
||||||
for ad.Next() {
|
|
||||||
ad.Do(func(b []byte) error {
|
|
||||||
ad, err := netlink.NewAttributeDecoder(b)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
ad.ByteOrder = binary.BigEndian
|
|
||||||
var name string
|
|
||||||
for ad.Next() {
|
|
||||||
switch ad.Type() {
|
|
||||||
case unix.NFTA_EXPR_NAME:
|
|
||||||
name = ad.String()
|
|
||||||
if name == "notrack" {
|
|
||||||
e := &expr.Notrack{}
|
|
||||||
exprs = append(exprs, e)
|
|
||||||
}
|
|
||||||
case unix.NFTA_EXPR_DATA:
|
|
||||||
var e expr.Any
|
|
||||||
switch name {
|
|
||||||
case "ct":
|
|
||||||
e = &expr.Ct{}
|
|
||||||
case "range":
|
|
||||||
e = &expr.Range{}
|
|
||||||
case "meta":
|
|
||||||
e = &expr.Meta{}
|
|
||||||
case "cmp":
|
|
||||||
e = &expr.Cmp{}
|
|
||||||
case "counter":
|
|
||||||
e = &expr.Counter{}
|
|
||||||
case "payload":
|
|
||||||
e = &expr.Payload{}
|
|
||||||
case "lookup":
|
|
||||||
e = &expr.Lookup{}
|
|
||||||
case "immediate":
|
|
||||||
e = &expr.Immediate{}
|
|
||||||
case "bitwise":
|
|
||||||
e = &expr.Bitwise{}
|
|
||||||
case "redir":
|
|
||||||
e = &expr.Redir{}
|
|
||||||
case "nat":
|
|
||||||
e = &expr.NAT{}
|
|
||||||
case "limit":
|
|
||||||
e = &expr.Limit{}
|
|
||||||
case "quota":
|
|
||||||
e = &expr.Quota{}
|
|
||||||
case "dynset":
|
|
||||||
e = &expr.Dynset{}
|
|
||||||
case "log":
|
|
||||||
e = &expr.Log{}
|
|
||||||
case "exthdr":
|
|
||||||
e = &expr.Exthdr{}
|
|
||||||
case "match":
|
|
||||||
e = &expr.Match{}
|
|
||||||
case "target":
|
|
||||||
e = &expr.Target{}
|
|
||||||
}
|
|
||||||
if e == nil {
|
|
||||||
// TODO: introduce an opaque expression type so that users know
|
|
||||||
// something is here.
|
|
||||||
continue // unsupported expression type
|
|
||||||
}
|
|
||||||
|
|
||||||
ad.Do(func(b []byte) error {
|
|
||||||
if err := expr.Unmarshal(byte(fam), b, e); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// Verdict expressions are a special-case of immediate expressions, so
|
|
||||||
// if the expression is an immediate writing nothing into the verdict
|
|
||||||
// register (invalid), re-parse it as a verdict expression.
|
|
||||||
if imm, isImmediate := e.(*expr.Immediate); isImmediate && imm.Register == unix.NFT_REG_VERDICT && len(imm.Data) == 0 {
|
|
||||||
e = &expr.Verdict{}
|
|
||||||
if err := expr.Unmarshal(byte(fam), b, e); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
exprs = append(exprs, e)
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ad.Err()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return exprs, ad.Err()
|
|
||||||
}
|
|
||||||
|
|
||||||
func ruleFromMsg(fam TableFamily, msg netlink.Message) (*Rule, error) {
|
func ruleFromMsg(fam TableFamily, msg netlink.Message) (*Rule, error) {
|
||||||
if got, want := msg.Header.Type, ruleHeaderType; got != want {
|
if got, want := msg.Header.Type, ruleHeaderType; got != want {
|
||||||
return nil, fmt.Errorf("unexpected header type: got %v, want %v", got, want)
|
return nil, fmt.Errorf("unexpected header type: got %v, want %v", got, want)
|
||||||
|
@ -329,8 +237,15 @@ func ruleFromMsg(fam TableFamily, msg netlink.Message) (*Rule, error) {
|
||||||
r.Chain = &Chain{Name: ad.String()}
|
r.Chain = &Chain{Name: ad.String()}
|
||||||
case unix.NFTA_RULE_EXPRESSIONS:
|
case unix.NFTA_RULE_EXPRESSIONS:
|
||||||
ad.Do(func(b []byte) error {
|
ad.Do(func(b []byte) error {
|
||||||
r.Exprs, err = exprsFromMsg(fam, b)
|
exprs, err := parseexprfunc.ParseExprMsgFunc(byte(fam), b)
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
}
|
||||||
|
r.Exprs = make([]expr.Any, len(exprs))
|
||||||
|
for i := range exprs {
|
||||||
|
r.Exprs[i] = exprs[i].(expr.Any)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
})
|
})
|
||||||
case unix.NFTA_RULE_POSITION:
|
case unix.NFTA_RULE_POSITION:
|
||||||
r.Position = ad.Uint64()
|
r.Position = ad.Uint64()
|
||||||
|
|
7
set.go
7
set.go
|
@ -235,6 +235,10 @@ type Set struct {
|
||||||
Interval bool
|
Interval bool
|
||||||
IsMap bool
|
IsMap bool
|
||||||
HasTimeout bool
|
HasTimeout 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
|
// Indicates that the set contains a concatenation
|
||||||
// https://git.netfilter.org/nftables/tree/include/linux/netfilter/nf_tables.h?id=d1289bff58e1878c3162f574c603da993e29b113#n306
|
// https://git.netfilter.org/nftables/tree/include/linux/netfilter/nf_tables.h?id=d1289bff58e1878c3162f574c603da993e29b113#n306
|
||||||
Concatenation bool
|
Concatenation bool
|
||||||
|
@ -468,6 +472,9 @@ func (cc *Conn) AddSet(s *Set, vals []SetElement) error {
|
||||||
if s.HasTimeout {
|
if s.HasTimeout {
|
||||||
flags |= unix.NFT_SET_TIMEOUT
|
flags |= unix.NFT_SET_TIMEOUT
|
||||||
}
|
}
|
||||||
|
if s.Dynamic {
|
||||||
|
flags |= unix.NFT_SET_EVAL
|
||||||
|
}
|
||||||
if s.Concatenation {
|
if s.Concatenation {
|
||||||
flags |= NFT_SET_CONCAT
|
flags |= NFT_SET_CONCAT
|
||||||
}
|
}
|
||||||
|
|
2
table.go
2
table.go
|
@ -104,7 +104,7 @@ func (cc *Conn) ListTables() ([]*Table, error) {
|
||||||
return cc.ListTablesOfFamily(TableFamilyUnspecified)
|
return cc.ListTablesOfFamily(TableFamilyUnspecified)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListTables returns currently configured tables for the specified table family
|
// ListTablesOfFamily returns currently configured tables for the specified table family
|
||||||
// in the kernel. It lists all tables if family is TableFamilyUnspecified.
|
// in the kernel. It lists all tables if family is TableFamilyUnspecified.
|
||||||
func (cc *Conn) ListTablesOfFamily(family TableFamily) ([]*Table, error) {
|
func (cc *Conn) ListTablesOfFamily(family TableFamily) ([]*Table, error) {
|
||||||
conn, closer, err := cc.netlinkConn()
|
conn, closer, err := cc.netlinkConn()
|
||||||
|
|
Loading…
Reference in New Issue