Skip to content

Commit

Permalink
Added dynset exprs support (#173)
Browse files Browse the repository at this point in the history
fixes #172

- Rearranged `exprFromMsg` function
- Rearranged limit expr marshaling logic
- Added dynamic flag for sets
- Implemented connlimit
- Added missing constants 
- Added tests
  • Loading branch information
turekt authored Jul 29, 2022
1 parent a346d51 commit ec1e802
Show file tree
Hide file tree
Showing 9 changed files with 501 additions and 112 deletions.
70 changes: 70 additions & 0 deletions expr/connlimit.go
Original file line number Diff line number Diff line change
@@ -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()
}
61 changes: 60 additions & 1 deletion expr/dynset.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,18 @@ import (
"time"

"github.com/google/nftables/binaryutil"
"github.com/google/nftables/internal/parseexprfunc"
"github.com/mdlayher/netlink"
"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.
type Dynset struct {
SrcRegKey uint32
Expand All @@ -32,6 +40,7 @@ type Dynset struct {
Operation uint32
Timeout time.Duration
Invert bool
Exprs []Any
}

func (e *Dynset) marshal(fam byte) ([]byte, error) {
Expand All @@ -45,12 +54,43 @@ func (e *Dynset) marshal(fam byte) ([]byte, error) {
if e.Timeout != 0 {
opAttrs = append(opAttrs, netlink.Attribute{Type: unix.NFTA_DYNSET_TIMEOUT, Data: binaryutil.BigEndian.PutUint64(uint64(e.Timeout.Milliseconds()))})
}
var flags uint32
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,
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)})

// 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)
if err != nil {
return nil, err
Expand Down Expand Up @@ -84,7 +124,26 @@ func (e *Dynset) unmarshal(fam byte, data []byte) error {
e.Timeout = time.Duration(time.Millisecond * time.Duration(ad.Uint64()))
case unix.NFTA_DYNSET_FLAGS:
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()
}

func (e *Dynset) setInterfaceExprs(exprs []interface{}) {
e.Exprs = make([]Any, len(exprs))
for i := range exprs {
e.Exprs[i] = exprs[i].(Any)
}
}
121 changes: 121 additions & 0 deletions expr/expr.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,41 @@ import (
"encoding/binary"

"github.com/google/nftables/binaryutil"
"github.com/google/nftables/internal/parseexprfunc"
"github.com/mdlayher/netlink"
"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.
func Marshal(fam byte, e Any) ([]byte, error) {
return e.marshal(fam)
Expand All @@ -33,6 +64,96 @@ func Unmarshal(fam byte, data []byte, e Any) error {
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.
type Any interface {
marshal(fam byte) ([]byte, error)
Expand Down
22 changes: 7 additions & 15 deletions expr/limit.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,24 +72,16 @@ type Limit struct {
}

func (l *Limit) marshal(fam byte) ([]byte, error) {
var flags uint32
if l.Over {
flags = unix.NFT_LIMIT_F_INV
}
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_UNIT, Data: binaryutil.BigEndian.PutUint64(uint64(l.Unit))},
}

if l.Over {
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),
})
{Type: unix.NFTA_LIMIT_BURST, Data: binaryutil.BigEndian.PutUint32(l.Burst)},
{Type: unix.NFTA_LIMIT_TYPE, Data: binaryutil.BigEndian.PutUint32(uint32(l.Type))},
{Type: unix.NFTA_LIMIT_FLAGS, Data: binaryutil.BigEndian.PutUint32(flags)},
}

data, err := netlink.MarshalAttributes(attrs)
Expand Down
10 changes: 10 additions & 0 deletions internal/parseexprfunc/parseexprfunc.go
Original file line number Diff line number Diff line change
@@ -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)
)
Loading

0 comments on commit ec1e802

Please sign in to comment.