diff --git a/expr/match.go b/expr/match.go new file mode 100644 index 0000000..6c0e83d --- /dev/null +++ b/expr/match.go @@ -0,0 +1,63 @@ +package expr + +import ( + "bytes" + "encoding/binary" + + "github.com/google/nftables/binaryutil" + "github.com/mdlayher/netlink" + "golang.org/x/sys/unix" +) + +// See https://git.netfilter.org/libnftnl/tree/src/expr/match.c?id=09456c720e9c00eecc08e41ac6b7c291b3821ee5#n30 +type Match struct { + Name string + Rev uint32 + Info []byte +} + +func (e *Match) marshal() ([]byte, error) { + // Per https://git.netfilter.org/libnftnl/tree/src/expr/match.c?id=09456c720e9c00eecc08e41ac6b7c291b3821ee5#n38 + name := e.Name + // limit the extension name as (some) user-space tools do and leave room for + // trailing \x00 + if len(name) >= /* sic! */ XTablesExtensionNameMaxLen { + name = name[:XTablesExtensionNameMaxLen-1] // leave room for trailing \x00. + } + attrs := []netlink.Attribute{ + {Type: unix.NFTA_MATCH_NAME, Data: []byte(name + "\x00")}, + {Type: unix.NFTA_MATCH_REV, Data: binaryutil.BigEndian.PutUint32(e.Rev)}, + {Type: unix.NFTA_MATCH_INFO, Data: e.Info}, + } + data, err := netlink.MarshalAttributes(attrs) + if err != nil { + return nil, err + } + + return netlink.MarshalAttributes([]netlink.Attribute{ + {Type: unix.NFTA_EXPR_NAME, Data: []byte("match\x00")}, + {Type: unix.NLA_F_NESTED | unix.NFTA_EXPR_DATA, Data: data}, + }) +} + +func (e *Match) unmarshal(data []byte) error { + // Per https://git.netfilter.org/libnftnl/tree/src/expr/match.c?id=09456c720e9c00eecc08e41ac6b7c291b3821ee5#n65 + ad, err := netlink.NewAttributeDecoder(data) + if err != nil { + return err + } + + ad.ByteOrder = binary.BigEndian + for ad.Next() { + switch ad.Type() { + case unix.NFTA_MATCH_NAME: + // We are forgiving here, accepting any length and even missing terminating \x00. + e.Name = string(bytes.TrimRight(ad.Bytes(), "\x00")) + case unix.NFTA_MATCH_REV: + e.Rev = ad.Uint32() + case unix.NFTA_MATCH_INFO: + e.Info = ad.Bytes() + } + } + return ad.Err() +} diff --git a/expr/match_test.go b/expr/match_test.go new file mode 100644 index 0000000..08730b8 --- /dev/null +++ b/expr/match_test.go @@ -0,0 +1,54 @@ +package expr + +import ( + "encoding/binary" + "reflect" + "testing" + + "github.com/mdlayher/netlink" + "golang.org/x/sys/unix" +) + +func TestMatch(t *testing.T) { + t.Parallel() + tests := []struct { + name string + mtch Match + }{ + { + name: "Unmarshal Target case", + mtch: Match{ + Name: "foobar", + Rev: 1234567890, + Info: []byte{0xb0, 0x1d, 0xca, 0xfe, 0x00}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ntgt := Match{} + data, err := tt.mtch.marshal() + if err != nil { + t.Fatalf("marshal error: %+v", err) + + } + ad, err := netlink.NewAttributeDecoder(data) + if err != nil { + t.Fatalf("NewAttributeDecoder() error: %+v", err) + } + ad.ByteOrder = binary.BigEndian + for ad.Next() { + if ad.Type() == unix.NFTA_EXPR_DATA { + if err := ntgt.unmarshal(ad.Bytes()); err != nil { + t.Errorf("unmarshal error: %+v", err) + break + } + } + } + if !reflect.DeepEqual(tt.mtch, ntgt) { + t.Fatalf("original %+v and recovered %+v Match structs are different", tt.mtch, ntgt) + } + }) + } +} diff --git a/expr/target.go b/expr/target.go new file mode 100644 index 0000000..dab6424 --- /dev/null +++ b/expr/target.go @@ -0,0 +1,67 @@ +package expr + +import ( + "bytes" + "encoding/binary" + + "github.com/google/nftables/binaryutil" + "github.com/mdlayher/netlink" + "golang.org/x/sys/unix" +) + +// See https://git.netfilter.org/libnftnl/tree/src/expr/target.c?id=09456c720e9c00eecc08e41ac6b7c291b3821ee5#n28 +const XTablesExtensionNameMaxLen = 29 + +// See https://git.netfilter.org/libnftnl/tree/src/expr/target.c?id=09456c720e9c00eecc08e41ac6b7c291b3821ee5#n30 +type Target struct { + Name string + Rev uint32 + Info []byte +} + +func (e *Target) marshal() ([]byte, error) { + // Per https://git.netfilter.org/libnftnl/tree/src/expr/target.c?id=09456c720e9c00eecc08e41ac6b7c291b3821ee5#n38 + name := e.Name + // limit the extension name as (some) user-space tools do and leave room for + // trailing \x00 + if len(name) >= /* sic! */ XTablesExtensionNameMaxLen { + name = name[:XTablesExtensionNameMaxLen-1] // leave room for trailing \x00. + } + attrs := []netlink.Attribute{ + {Type: unix.NFTA_TARGET_NAME, Data: []byte(name + "\x00")}, + {Type: unix.NFTA_TARGET_REV, Data: binaryutil.BigEndian.PutUint32(e.Rev)}, + {Type: unix.NFTA_TARGET_INFO, Data: e.Info}, + } + + data, err := netlink.MarshalAttributes(attrs) + if err != nil { + return nil, err + } + + return netlink.MarshalAttributes([]netlink.Attribute{ + {Type: unix.NFTA_EXPR_NAME, Data: []byte("target\x00")}, + {Type: unix.NLA_F_NESTED | unix.NFTA_EXPR_DATA, Data: data}, + }) +} + +func (e *Target) unmarshal(data []byte) error { + // Per https://git.netfilter.org/libnftnl/tree/src/expr/target.c?id=09456c720e9c00eecc08e41ac6b7c291b3821ee5#n65 + ad, err := netlink.NewAttributeDecoder(data) + if err != nil { + return err + } + + ad.ByteOrder = binary.BigEndian + for ad.Next() { + switch ad.Type() { + case unix.NFTA_TARGET_NAME: + // We are forgiving here, accepting any length and even missing terminating \x00. + e.Name = string(bytes.TrimRight(ad.Bytes(), "\x00")) + case unix.NFTA_TARGET_REV: + e.Rev = ad.Uint32() + case unix.NFTA_TARGET_INFO: + e.Info = ad.Bytes() + } + } + return ad.Err() +} diff --git a/expr/target_test.go b/expr/target_test.go new file mode 100644 index 0000000..b757fc9 --- /dev/null +++ b/expr/target_test.go @@ -0,0 +1,54 @@ +package expr + +import ( + "encoding/binary" + "reflect" + "testing" + + "github.com/mdlayher/netlink" + "golang.org/x/sys/unix" +) + +func TestTarget(t *testing.T) { + t.Parallel() + tests := []struct { + name string + tgt Target + }{ + { + name: "Unmarshal Target case", + tgt: Target{ + Name: "foobar", + Rev: 1234567890, + Info: []byte{0xb0, 0x1d, 0xca, 0xfe, 0x00}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ntgt := Target{} + data, err := tt.tgt.marshal() + if err != nil { + t.Fatalf("marshal error: %+v", err) + + } + ad, err := netlink.NewAttributeDecoder(data) + if err != nil { + t.Fatalf("NewAttributeDecoder() error: %+v", err) + } + ad.ByteOrder = binary.BigEndian + for ad.Next() { + if ad.Type() == unix.NFTA_EXPR_DATA { + if err := ntgt.unmarshal(ad.Bytes()); err != nil { + t.Errorf("unmarshal error: %+v", err) + break + } + } + } + if !reflect.DeepEqual(tt.tgt, ntgt) { + t.Fatalf("original %+v and recovered %+v Target structs are different", tt.tgt, ntgt) + } + }) + } +} diff --git a/rule.go b/rule.go index f2057ee..11013d7 100644 --- a/rule.go +++ b/rule.go @@ -273,6 +273,10 @@ func exprsFromMsg(b []byte) ([]expr.Any, error) { 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