Implement GetRule for Immediate, Verdict, and Lookup expressions (#11)

This commit is contained in:
Tom D 2019-02-18 04:01:32 -08:00 committed by Michael Stapelberg
parent ca263a814b
commit b8b6574812
5 changed files with 214 additions and 4 deletions

View File

@ -15,6 +15,7 @@
package expr
import (
"encoding/binary"
"fmt"
"github.com/google/nftables/binaryutil"
@ -49,5 +50,30 @@ func (e *Immediate) marshal() ([]byte, error) {
}
func (e *Immediate) unmarshal(data []byte) error {
return fmt.Errorf("not yet implemented")
ad, err := netlink.NewAttributeDecoder(data)
if err != nil {
return err
}
ad.ByteOrder = binary.BigEndian
for ad.Next() {
switch ad.Type() {
case unix.NFTA_IMMEDIATE_DREG:
e.Register = ad.Uint32()
case unix.NFTA_IMMEDIATE_DATA:
nestedAD, err := netlink.NewAttributeDecoder(ad.Bytes())
if err != nil {
return fmt.Errorf("nested NewAttributeDecoder() failed: %v", err)
}
for nestedAD.Next() {
switch nestedAD.Type() {
case unix.NFTA_DATA_VALUE:
e.Data = nestedAD.Bytes()
}
}
if nestedAD.Err() != nil {
return fmt.Errorf("decoding immediate: %v", nestedAD.Err())
}
}
}
return ad.Err()
}

View File

@ -15,7 +15,7 @@
package expr
import (
"fmt"
"encoding/binary"
"github.com/google/nftables/binaryutil"
"github.com/mdlayher/netlink"
@ -60,5 +60,24 @@ func (e *Lookup) marshal() ([]byte, error) {
}
func (e *Lookup) unmarshal(data []byte) error {
return fmt.Errorf("not yet implemented")
ad, err := netlink.NewAttributeDecoder(data)
if err != nil {
return err
}
ad.ByteOrder = binary.BigEndian
for ad.Next() {
switch ad.Type() {
case unix.NFTA_LOOKUP_SET:
e.SetName = ad.String()
case unix.NFTA_LOOKUP_SET_ID:
e.SetID = ad.Uint32()
case unix.NFTA_LOOKUP_SREG:
e.SourceRegister = ad.Uint32()
case unix.NFTA_LOOKUP_DREG:
e.DestRegister = ad.Uint32()
case unix.NFTA_LOOKUP_FLAGS:
e.Invert = (ad.Uint32() & unix.NFT_LOOKUP_F_INV) != 0
}
}
return ad.Err()
}

View File

@ -15,6 +15,7 @@
package expr
import (
"encoding/binary"
"fmt"
"github.com/google/nftables/binaryutil"
@ -85,5 +86,28 @@ func (e *Verdict) marshal() ([]byte, error) {
}
func (e *Verdict) unmarshal(data []byte) error {
return fmt.Errorf("not yet implemented")
ad, err := netlink.NewAttributeDecoder(data)
if err != nil {
return err
}
ad.ByteOrder = binary.BigEndian
for ad.Next() {
switch ad.Type() {
case unix.NFTA_IMMEDIATE_DATA:
nestedAD, err := netlink.NewAttributeDecoder(ad.Bytes())
if err != nil {
return fmt.Errorf("nested NewAttributeDecoder() failed: %v", err)
}
for nestedAD.Next() {
switch nestedAD.Type() {
case unix.NFTA_DATA_VERDICT:
e.Kind = VerdictKind(binaryutil.BigEndian.Uint32(nestedAD.Bytes()[4:]))
}
}
if nestedAD.Err() != nil {
return fmt.Errorf("decoding immediate: %v", nestedAD.Err())
}
}
}
return ad.Err()
}

View File

@ -213,6 +213,10 @@ func exprsFromMsg(b []byte) ([]expr.Any, error) {
e = &expr.Counter{}
case "payload":
e = &expr.Payload{}
case "lookup":
e = &expr.Lookup{}
case "immediate":
e = &expr.Immediate{}
}
if e == nil {
// TODO: introduce an opaque expression type so that users know
@ -224,6 +228,15 @@ func exprsFromMsg(b []byte) ([]expr.Any, error) {
if err := expr.Unmarshal(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(b, e); err != nil {
return err
}
}
exprs = append(exprs, e)
return nil
})

View File

@ -19,6 +19,7 @@ import (
"flag"
"fmt"
"net"
"reflect"
"runtime"
"strings"
"testing"
@ -1112,3 +1113,130 @@ func TestDeleteElementNamedSet(t *testing.T) {
t.Errorf("elems[0].Key = %v, want 22", elems[0].Key)
}
}
func TestGetRuleLookupVerdictImmediate(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()
filter := c.AddTable(&nftables.Table{
Family: nftables.TableFamilyIPv4,
Name: "filter",
})
forward := c.AddChain(&nftables.Chain{
Name: "forward",
Table: filter,
Type: nftables.ChainTypeFilter,
Hooknum: nftables.ChainHookForward,
Priority: nftables.ChainPriorityFilter,
})
set := &nftables.Set{
Table: filter,
Name: "kek",
KeyType: nftables.TypeInetService,
}
if err := c.AddSet(set, nil); err != nil {
t.Errorf("c.AddSet(portSet) failed: %v", err)
}
if err := c.Flush(); err != nil {
t.Errorf("c.Flush() failed: %v", err)
}
c.AddRule(&nftables.Rule{
Table: filter,
Chain: forward,
Exprs: []expr.Any{
// [ meta load l4proto => reg 1 ]
&expr.Meta{Key: expr.MetaKeyL4PROTO, Register: 1},
// [ cmp eq reg 1 0x00000006 ]
&expr.Cmp{
Op: expr.CmpOpEq,
Register: 1,
Data: []byte{unix.IPPROTO_TCP},
},
// [ payload load 2b @ transport header + 2 => reg 1 ]
&expr.Payload{
DestRegister: 1,
Base: expr.PayloadBaseTransportHeader,
Offset: 2,
Len: 2,
},
// [ lookup reg 1 set __set%d ]
&expr.Lookup{
SourceRegister: 1,
SetName: set.Name,
SetID: set.ID,
},
// [ immediate reg 0 drop ]
&expr.Verdict{
Kind: expr.VerdictAccept,
},
// [ immediate reg 2 kek ]
&expr.Immediate{
Register: 2,
Data: []byte("kek"),
},
},
})
if err := c.Flush(); err != nil {
t.Errorf("c.Flush() failed: %v", err)
}
rules, err := c.GetRule(
&nftables.Table{
Family: nftables.TableFamilyIPv4,
Name: "filter",
},
&nftables.Chain{
Name: "forward",
},
)
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), 6; got != want {
t.Fatalf("unexpected number of exprs: got %d, want %d", got, want)
}
lookup, lookupOk := rules[0].Exprs[3].(*expr.Lookup)
if !lookupOk {
t.Fatalf("Exprs[3] is type %T, want *expr.Lookup", rules[0].Exprs[3])
}
if want := (&expr.Lookup{
SourceRegister: 1,
SetName: set.Name,
}); !reflect.DeepEqual(lookup, want) {
t.Errorf("lookup expr = %+v, wanted %+v", lookup, want)
}
verdict, verdictOk := rules[0].Exprs[4].(*expr.Verdict)
if !verdictOk {
t.Fatalf("Exprs[4] is type %T, want *expr.Verdict", rules[0].Exprs[4])
}
if want := (&expr.Verdict{
Kind: expr.VerdictAccept,
}); !reflect.DeepEqual(verdict, want) {
t.Errorf("verdict expr = %+v, wanted %+v", verdict, want)
}
imm, immOk := rules[0].Exprs[5].(*expr.Immediate)
if !immOk {
t.Fatalf("Exprs[4] is type %T, want *expr.Immediate", rules[0].Exprs[5])
}
if want := (&expr.Immediate{
Register: 2,
Data: []byte("kek"),
}); !reflect.DeepEqual(imm, want) {
t.Errorf("verdict expr = %+v, wanted %+v", imm, want)
}
}