add typed xtables information un/marshalling
more tests and fixes more info support; refactoring
This commit is contained in:
parent
4b6f0f2b44
commit
8ea944061f
|
@ -5,6 +5,7 @@ import (
|
|||
"encoding/binary"
|
||||
|
||||
"github.com/google/nftables/binaryutil"
|
||||
"github.com/google/nftables/xt"
|
||||
"github.com/mdlayher/netlink"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
@ -13,7 +14,7 @@ import (
|
|||
type Match struct {
|
||||
Name string
|
||||
Rev uint32
|
||||
Info []byte
|
||||
Info xt.InfoAny
|
||||
}
|
||||
|
||||
func (e *Match) marshal(fam byte) ([]byte, error) {
|
||||
|
@ -24,10 +25,16 @@ func (e *Match) marshal(fam byte) ([]byte, error) {
|
|||
if len(name) >= /* sic! */ XTablesExtensionNameMaxLen {
|
||||
name = name[:XTablesExtensionNameMaxLen-1] // leave room for trailing \x00.
|
||||
}
|
||||
// Marshalling assumes that the correct Info type for the particular table
|
||||
// family and Match revision has been set.
|
||||
info, err := xt.Marshal(xt.TableFamily(fam), e.Rev, e.Info)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
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},
|
||||
{Type: unix.NFTA_MATCH_INFO, Data: info},
|
||||
}
|
||||
data, err := netlink.MarshalAttributes(attrs)
|
||||
if err != nil {
|
||||
|
@ -47,6 +54,7 @@ func (e *Match) unmarshal(fam byte, data []byte) error {
|
|||
return err
|
||||
}
|
||||
|
||||
var info []byte
|
||||
ad.ByteOrder = binary.BigEndian
|
||||
for ad.Next() {
|
||||
switch ad.Type() {
|
||||
|
@ -56,8 +64,12 @@ func (e *Match) unmarshal(fam byte, data []byte) error {
|
|||
case unix.NFTA_MATCH_REV:
|
||||
e.Rev = ad.Uint32()
|
||||
case unix.NFTA_MATCH_INFO:
|
||||
e.Info = ad.Bytes()
|
||||
info = ad.Bytes()
|
||||
}
|
||||
}
|
||||
return ad.Err()
|
||||
if err = ad.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
e.Info, err = xt.Unmarshal(e.Name, xt.TableFamily(fam), e.Rev, info)
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -5,12 +5,14 @@ import (
|
|||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/google/nftables/xt"
|
||||
"github.com/mdlayher/netlink"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func TestMatch(t *testing.T) {
|
||||
t.Parallel()
|
||||
payload := xt.Unknown([]byte{0xb0, 0x1d, 0xca, 0xfe, 0x00})
|
||||
tests := []struct {
|
||||
name string
|
||||
mtch Match
|
||||
|
@ -20,7 +22,7 @@ func TestMatch(t *testing.T) {
|
|||
mtch: Match{
|
||||
Name: "foobar",
|
||||
Rev: 1234567890,
|
||||
Info: []byte{0xb0, 0x1d, 0xca, 0xfe, 0x00},
|
||||
Info: &payload,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
"encoding/binary"
|
||||
|
||||
"github.com/google/nftables/binaryutil"
|
||||
"github.com/google/nftables/xt"
|
||||
"github.com/mdlayher/netlink"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
@ -16,7 +17,7 @@ const XTablesExtensionNameMaxLen = 29
|
|||
type Target struct {
|
||||
Name string
|
||||
Rev uint32
|
||||
Info []byte
|
||||
Info xt.InfoAny
|
||||
}
|
||||
|
||||
func (e *Target) marshal(fam byte) ([]byte, error) {
|
||||
|
@ -27,10 +28,16 @@ func (e *Target) marshal(fam byte) ([]byte, error) {
|
|||
if len(name) >= /* sic! */ XTablesExtensionNameMaxLen {
|
||||
name = name[:XTablesExtensionNameMaxLen-1] // leave room for trailing \x00.
|
||||
}
|
||||
// Marshalling assumes that the correct Info type for the particular table
|
||||
// family and Match revision has been set.
|
||||
info, err := xt.Marshal(xt.TableFamily(fam), e.Rev, e.Info)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
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},
|
||||
{Type: unix.NFTA_TARGET_INFO, Data: info},
|
||||
}
|
||||
|
||||
data, err := netlink.MarshalAttributes(attrs)
|
||||
|
@ -51,6 +58,7 @@ func (e *Target) unmarshal(fam byte, data []byte) error {
|
|||
return err
|
||||
}
|
||||
|
||||
var info []byte
|
||||
ad.ByteOrder = binary.BigEndian
|
||||
for ad.Next() {
|
||||
switch ad.Type() {
|
||||
|
@ -60,8 +68,12 @@ func (e *Target) unmarshal(fam byte, data []byte) error {
|
|||
case unix.NFTA_TARGET_REV:
|
||||
e.Rev = ad.Uint32()
|
||||
case unix.NFTA_TARGET_INFO:
|
||||
e.Info = ad.Bytes()
|
||||
info = ad.Bytes()
|
||||
}
|
||||
}
|
||||
return ad.Err()
|
||||
if err = ad.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
e.Info, err = xt.Unmarshal(e.Name, xt.TableFamily(fam), e.Rev, info)
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -5,12 +5,14 @@ import (
|
|||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/google/nftables/xt"
|
||||
"github.com/mdlayher/netlink"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func TestTarget(t *testing.T) {
|
||||
t.Parallel()
|
||||
payload := xt.Unknown([]byte{0xb0, 0x1d, 0xca, 0xfe, 0x00})
|
||||
tests := []struct {
|
||||
name string
|
||||
tgt Target
|
||||
|
@ -20,7 +22,7 @@ func TestTarget(t *testing.T) {
|
|||
tgt: Target{
|
||||
Name: "foobar",
|
||||
Rev: 1234567890,
|
||||
Info: []byte{0xb0, 0x1d, 0xca, 0xfe, 0x00},
|
||||
Info: &payload,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -0,0 +1,94 @@
|
|||
package xt
|
||||
|
||||
import (
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// TableFamily specifies the address family of the table Match or Target Info
|
||||
// data is contained in. On purpose, we don't import the expr package here in
|
||||
// order to keep the option open to import this package instead into expr.
|
||||
type TableFamily byte
|
||||
|
||||
// InfoAny is a (un)marshaling implemented by any info type.
|
||||
type InfoAny interface {
|
||||
marshal(fam TableFamily, rev uint32) ([]byte, error)
|
||||
unmarshal(fam TableFamily, rev uint32, data []byte) error
|
||||
}
|
||||
|
||||
// Marshal a Match or Target Info type into its binary representation.
|
||||
func Marshal(fam TableFamily, rev uint32, info InfoAny) ([]byte, error) {
|
||||
return info.marshal(fam, rev)
|
||||
}
|
||||
|
||||
// Unmarshal Info binary payload into its corresponding dedicated type as
|
||||
// indicated by the name argument. In several cases, unmarshalling depends on
|
||||
// the specific table family the Target or Match expression with the info
|
||||
// payload belongs to, as well as the specific info structure revision.
|
||||
func Unmarshal(name string, fam TableFamily, rev uint32, data []byte) (InfoAny, error) {
|
||||
var i InfoAny
|
||||
switch name {
|
||||
case "addrtype":
|
||||
switch rev {
|
||||
case 0:
|
||||
i = &AddrType{}
|
||||
case 1:
|
||||
i = &AddrTypeV1{}
|
||||
}
|
||||
case "conntrack":
|
||||
switch rev {
|
||||
case 1:
|
||||
i = &ConntrackMtinfo1{}
|
||||
case 2:
|
||||
i = &ConntrackMtinfo2{}
|
||||
case 3:
|
||||
i = &ConntrackMtinfo3{}
|
||||
}
|
||||
case "tcp":
|
||||
i = &Tcp{}
|
||||
case "udp":
|
||||
i = &Udp{}
|
||||
case "SNAT":
|
||||
if fam == unix.NFPROTO_IPV4 {
|
||||
i = &NatIPv4MultiRangeCompat{}
|
||||
}
|
||||
case "DNAT":
|
||||
switch fam {
|
||||
case unix.NFPROTO_IPV4:
|
||||
if rev == 0 {
|
||||
i = &NatIPv4MultiRangeCompat{}
|
||||
break
|
||||
}
|
||||
fallthrough
|
||||
case unix.NFPROTO_IPV6:
|
||||
switch rev {
|
||||
case 1:
|
||||
i = &NatRange{}
|
||||
case 2:
|
||||
i = &NatRange2{}
|
||||
}
|
||||
}
|
||||
case "MASQUERADE":
|
||||
switch fam {
|
||||
case unix.NFPROTO_IPV4:
|
||||
i = &NatIPv4MultiRangeCompat{}
|
||||
}
|
||||
case "REDIRECT":
|
||||
switch fam {
|
||||
case unix.NFPROTO_IPV4:
|
||||
if rev == 0 {
|
||||
i = &NatIPv4MultiRangeCompat{}
|
||||
break
|
||||
}
|
||||
fallthrough
|
||||
case unix.NFPROTO_IPV6:
|
||||
i = &NatRange{}
|
||||
}
|
||||
}
|
||||
if i == nil {
|
||||
i = &Unknown{}
|
||||
}
|
||||
if err := i.unmarshal(fam, rev, data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return i, nil
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
package xt
|
||||
|
||||
import (
|
||||
"github.com/google/nftables/alignedbuff"
|
||||
)
|
||||
|
||||
// Rev. 0, see https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/netfilter/xt_addrtype.h#L38
|
||||
type AddrType struct {
|
||||
Source uint16
|
||||
Dest uint16
|
||||
InvertSource bool
|
||||
InvertDest bool
|
||||
}
|
||||
|
||||
type AddrTypeFlags uint32
|
||||
|
||||
const (
|
||||
AddrTypeUnspec AddrTypeFlags = 1 << iota
|
||||
AddrTypeUnicast
|
||||
AddrTypeLocal
|
||||
AddrTypeBroadcast
|
||||
AddrTypeAnycast
|
||||
AddrTypeMulticast
|
||||
AddrTypeBlackhole
|
||||
AddrTypeUnreachable
|
||||
AddrTypeProhibit
|
||||
AddrTypeThrow
|
||||
AddrTypeNat
|
||||
AddrTypeXresolve
|
||||
)
|
||||
|
||||
// See https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/netfilter/xt_addrtype.h#L31
|
||||
type AddrTypeV1 struct {
|
||||
Source uint16
|
||||
Dest uint16
|
||||
Flags AddrTypeFlags
|
||||
}
|
||||
|
||||
func (x *AddrType) marshal(fam TableFamily, rev uint32) ([]byte, error) {
|
||||
ab := alignedbuff.New()
|
||||
ab.PutUint16(x.Source)
|
||||
ab.PutUint16(x.Dest)
|
||||
putBool32(&ab, x.InvertSource)
|
||||
putBool32(&ab, x.InvertDest)
|
||||
return ab.Data(), nil
|
||||
}
|
||||
|
||||
func (x *AddrType) unmarshal(fam TableFamily, rev uint32, data []byte) error {
|
||||
ab := alignedbuff.NewWithData(data)
|
||||
var err error
|
||||
if x.Source, err = ab.Uint16(); err != nil {
|
||||
return nil
|
||||
}
|
||||
if x.Dest, err = ab.Uint16(); err != nil {
|
||||
return nil
|
||||
}
|
||||
if x.InvertSource, err = bool32(&ab); err != nil {
|
||||
return nil
|
||||
}
|
||||
if x.InvertDest, err = bool32(&ab); err != nil {
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *AddrTypeV1) marshal(fam TableFamily, rev uint32) ([]byte, error) {
|
||||
ab := alignedbuff.New()
|
||||
ab.PutUint16(x.Source)
|
||||
ab.PutUint16(x.Dest)
|
||||
ab.PutUint32(uint32(x.Flags))
|
||||
return ab.Data(), nil
|
||||
}
|
||||
|
||||
func (x *AddrTypeV1) unmarshal(fam TableFamily, rev uint32, data []byte) error {
|
||||
ab := alignedbuff.NewWithData(data)
|
||||
var err error
|
||||
if x.Source, err = ab.Uint16(); err != nil {
|
||||
return nil
|
||||
}
|
||||
if x.Dest, err = ab.Uint16(); err != nil {
|
||||
return nil
|
||||
}
|
||||
var flags uint32
|
||||
if flags, err = ab.Uint32(); err != nil {
|
||||
return nil
|
||||
}
|
||||
x.Flags = AddrTypeFlags(flags)
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
package xt
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestTargetAddrType(t *testing.T) {
|
||||
t.Parallel()
|
||||
tests := []struct {
|
||||
name string
|
||||
fam byte
|
||||
rev uint32
|
||||
info InfoAny
|
||||
empty InfoAny
|
||||
}{
|
||||
{
|
||||
name: "un/marshal AddrType Rev 0 round-trip",
|
||||
fam: 0,
|
||||
rev: 0,
|
||||
info: &AddrType{
|
||||
Source: 0x1234,
|
||||
Dest: 0x5678,
|
||||
InvertSource: true,
|
||||
InvertDest: false,
|
||||
},
|
||||
empty: &AddrType{},
|
||||
},
|
||||
{
|
||||
name: "un/marshal AddrType Rev 0 round-trip",
|
||||
fam: 0,
|
||||
rev: 0,
|
||||
info: &AddrType{
|
||||
Source: 0x1234,
|
||||
Dest: 0x5678,
|
||||
InvertSource: false,
|
||||
InvertDest: true,
|
||||
},
|
||||
empty: &AddrType{},
|
||||
},
|
||||
{
|
||||
name: "un/marshal AddrType Rev 1 round-trip",
|
||||
fam: 0,
|
||||
rev: 0,
|
||||
info: &AddrTypeV1{
|
||||
Source: 0x1234,
|
||||
Dest: 0x5678,
|
||||
Flags: 0xb00f,
|
||||
},
|
||||
empty: &AddrTypeV1{},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
data, err := tt.info.marshal(TableFamily(tt.fam), tt.rev)
|
||||
if err != nil {
|
||||
t.Fatalf("marshal error: %+v", err)
|
||||
|
||||
}
|
||||
var recoveredInfo InfoAny = tt.empty
|
||||
err = recoveredInfo.unmarshal(TableFamily(tt.fam), tt.rev, data)
|
||||
if err != nil {
|
||||
t.Fatalf("unmarshal error: %+v", err)
|
||||
}
|
||||
if !reflect.DeepEqual(tt.info, recoveredInfo) {
|
||||
t.Fatalf("original %+v and recovered %+v are different", tt.info, recoveredInfo)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,250 @@
|
|||
package xt
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
"github.com/google/nftables/alignedbuff"
|
||||
)
|
||||
|
||||
type ConntrackFlags uint16
|
||||
|
||||
const (
|
||||
ConntrackState ConntrackFlags = 1 << iota
|
||||
ConntrackProto
|
||||
ConntrackOrigSrc
|
||||
ConntrackOrigDst
|
||||
ConntrackReplSrc
|
||||
ConntrackReplDst
|
||||
ConntrackStatus
|
||||
ConntrackExpires
|
||||
ConntrackOrigSrcPort
|
||||
ConntrackOrigDstPort
|
||||
ConntrackReplSrcPort
|
||||
ConntrackReplDstPrt
|
||||
ConntrackDirection
|
||||
ConntrackStateAlias
|
||||
)
|
||||
|
||||
type ConntrackMtinfoBase struct {
|
||||
OrigSrcAddr net.IP
|
||||
OrigSrcMask net.IPMask
|
||||
OrigDstAddr net.IP
|
||||
OrigDstMask net.IPMask
|
||||
ReplSrcAddr net.IP
|
||||
ReplSrcMask net.IPMask
|
||||
ReplDstAddr net.IP
|
||||
ReplDstMask net.IPMask
|
||||
ExpiresMin uint32
|
||||
ExpiresMax uint32
|
||||
L4Proto uint16
|
||||
OrigSrcPort uint16
|
||||
OrigDstPort uint16
|
||||
ReplSrcPort uint16
|
||||
ReplDstPort uint16
|
||||
}
|
||||
|
||||
// See https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/netfilter/xt_conntrack.h#L38
|
||||
type ConntrackMtinfo1 struct {
|
||||
ConntrackMtinfoBase
|
||||
StateMask uint8
|
||||
StatusMask uint8
|
||||
}
|
||||
|
||||
// See https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/netfilter/xt_conntrack.h#L51
|
||||
type ConntrackMtinfo2 struct {
|
||||
ConntrackMtinfoBase
|
||||
StateMask uint16
|
||||
StatusMask uint16
|
||||
}
|
||||
|
||||
// See https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/netfilter/xt_conntrack.h#L64
|
||||
type ConntrackMtinfo3 struct {
|
||||
ConntrackMtinfo2
|
||||
OrigSrcPortHigh uint16
|
||||
OrigDstPortHigh uint16
|
||||
ReplSrcPortHigh uint16
|
||||
ReplDstPortHigh uint16
|
||||
}
|
||||
|
||||
func (x *ConntrackMtinfoBase) marshalAB(fam TableFamily, rev uint32, ab *alignedbuff.AlignedBuff) error {
|
||||
if err := putIPv46(ab, fam, x.OrigSrcAddr); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := putIPv46Mask(ab, fam, x.OrigSrcMask); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := putIPv46(ab, fam, x.OrigDstAddr); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := putIPv46Mask(ab, fam, x.OrigDstMask); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := putIPv46(ab, fam, x.ReplSrcAddr); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := putIPv46Mask(ab, fam, x.ReplSrcMask); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := putIPv46(ab, fam, x.ReplDstAddr); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := putIPv46Mask(ab, fam, x.ReplDstMask); err != nil {
|
||||
return err
|
||||
}
|
||||
ab.PutUint32(x.ExpiresMin)
|
||||
ab.PutUint32(x.ExpiresMax)
|
||||
ab.PutUint16(x.L4Proto)
|
||||
ab.PutUint16(x.OrigSrcPort)
|
||||
ab.PutUint16(x.OrigDstPort)
|
||||
ab.PutUint16(x.ReplSrcPort)
|
||||
ab.PutUint16(x.ReplDstPort)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *ConntrackMtinfoBase) unmarshalAB(fam TableFamily, rev uint32, ab *alignedbuff.AlignedBuff) error {
|
||||
var err error
|
||||
if x.OrigSrcAddr, err = iPv46(ab, fam); err != nil {
|
||||
return err
|
||||
}
|
||||
if x.OrigSrcMask, err = iPv46Mask(ab, fam); err != nil {
|
||||
return err
|
||||
}
|
||||
if x.OrigDstAddr, err = iPv46(ab, fam); err != nil {
|
||||
return err
|
||||
}
|
||||
if x.OrigDstMask, err = iPv46Mask(ab, fam); err != nil {
|
||||
return err
|
||||
}
|
||||
if x.ReplSrcAddr, err = iPv46(ab, fam); err != nil {
|
||||
return err
|
||||
}
|
||||
if x.ReplSrcMask, err = iPv46Mask(ab, fam); err != nil {
|
||||
return err
|
||||
}
|
||||
if x.ReplDstAddr, err = iPv46(ab, fam); err != nil {
|
||||
return err
|
||||
}
|
||||
if x.ReplDstMask, err = iPv46Mask(ab, fam); err != nil {
|
||||
return err
|
||||
}
|
||||
if x.ExpiresMin, err = ab.Uint32(); err != nil {
|
||||
return err
|
||||
}
|
||||
if x.ExpiresMax, err = ab.Uint32(); err != nil {
|
||||
return err
|
||||
}
|
||||
if x.L4Proto, err = ab.Uint16(); err != nil {
|
||||
return err
|
||||
}
|
||||
if x.OrigSrcPort, err = ab.Uint16(); err != nil {
|
||||
return err
|
||||
}
|
||||
if x.OrigDstPort, err = ab.Uint16(); err != nil {
|
||||
return err
|
||||
}
|
||||
if x.ReplSrcPort, err = ab.Uint16(); err != nil {
|
||||
return err
|
||||
}
|
||||
if x.ReplDstPort, err = ab.Uint16(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *ConntrackMtinfo1) marshal(fam TableFamily, rev uint32) ([]byte, error) {
|
||||
ab := alignedbuff.New()
|
||||
if err := x.ConntrackMtinfoBase.marshalAB(fam, rev, &ab); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ab.PutUint8(x.StateMask)
|
||||
ab.PutUint8(x.StatusMask)
|
||||
return ab.Data(), nil
|
||||
}
|
||||
|
||||
func (x *ConntrackMtinfo1) unmarshal(fam TableFamily, rev uint32, data []byte) error {
|
||||
ab := alignedbuff.NewWithData(data)
|
||||
var err error
|
||||
if err = x.ConntrackMtinfoBase.unmarshalAB(fam, rev, &ab); err != nil {
|
||||
return err
|
||||
}
|
||||
if x.StateMask, err = ab.Uint8(); err != nil {
|
||||
return err
|
||||
}
|
||||
if x.StatusMask, err = ab.Uint8(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *ConntrackMtinfo2) marshalAB(fam TableFamily, rev uint32, ab *alignedbuff.AlignedBuff) error {
|
||||
if err := x.ConntrackMtinfoBase.marshalAB(fam, rev, ab); err != nil {
|
||||
return err
|
||||
}
|
||||
ab.PutUint16(x.StateMask)
|
||||
ab.PutUint16(x.StatusMask)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *ConntrackMtinfo2) marshal(fam TableFamily, rev uint32) ([]byte, error) {
|
||||
ab := alignedbuff.New()
|
||||
if err := x.marshalAB(fam, rev, &ab); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ab.Data(), nil
|
||||
}
|
||||
|
||||
func (x *ConntrackMtinfo2) unmarshalAB(fam TableFamily, rev uint32, ab *alignedbuff.AlignedBuff) error {
|
||||
var err error
|
||||
if err = x.ConntrackMtinfoBase.unmarshalAB(fam, rev, ab); err != nil {
|
||||
return err
|
||||
}
|
||||
if x.StateMask, err = ab.Uint16(); err != nil {
|
||||
return err
|
||||
}
|
||||
if x.StatusMask, err = ab.Uint16(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *ConntrackMtinfo2) unmarshal(fam TableFamily, rev uint32, data []byte) error {
|
||||
ab := alignedbuff.NewWithData(data)
|
||||
var err error
|
||||
if err = x.unmarshalAB(fam, rev, &ab); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *ConntrackMtinfo3) marshal(fam TableFamily, rev uint32) ([]byte, error) {
|
||||
ab := alignedbuff.New()
|
||||
if err := x.ConntrackMtinfo2.marshalAB(fam, rev, &ab); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ab.PutUint16(x.OrigSrcPortHigh)
|
||||
ab.PutUint16(x.OrigDstPortHigh)
|
||||
ab.PutUint16(x.ReplSrcPortHigh)
|
||||
ab.PutUint16(x.ReplDstPortHigh)
|
||||
return ab.Data(), nil
|
||||
}
|
||||
|
||||
func (x *ConntrackMtinfo3) unmarshal(fam TableFamily, rev uint32, data []byte) error {
|
||||
ab := alignedbuff.NewWithData(data)
|
||||
var err error
|
||||
if err = x.ConntrackMtinfo2.unmarshalAB(fam, rev, &ab); err != nil {
|
||||
return err
|
||||
}
|
||||
if x.OrigSrcPortHigh, err = ab.Uint16(); err != nil {
|
||||
return err
|
||||
}
|
||||
if x.OrigDstPortHigh, err = ab.Uint16(); err != nil {
|
||||
return err
|
||||
}
|
||||
if x.ReplSrcPortHigh, err = ab.Uint16(); err != nil {
|
||||
return err
|
||||
}
|
||||
if x.ReplDstPortHigh, err = ab.Uint16(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,213 @@
|
|||
package xt
|
||||
|
||||
import (
|
||||
"net"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func TestMatchConntrack(t *testing.T) {
|
||||
t.Parallel()
|
||||
tests := []struct {
|
||||
name string
|
||||
fam byte
|
||||
rev uint32
|
||||
info InfoAny
|
||||
empty InfoAny
|
||||
}{
|
||||
{
|
||||
name: "un/marshal ConntrackMtinfo1 IPv4 round-trip",
|
||||
fam: unix.NFPROTO_IPV4,
|
||||
rev: 0,
|
||||
info: &ConntrackMtinfo1{
|
||||
ConntrackMtinfoBase: ConntrackMtinfoBase{
|
||||
OrigSrcAddr: net.ParseIP("1.2.3.4").To4(),
|
||||
OrigSrcMask: net.IPv4Mask(0x12, 0x23, 0x34, 0x45), // only for test ;)
|
||||
OrigDstAddr: net.ParseIP("2.3.4.5").To4(),
|
||||
OrigDstMask: net.IPv4Mask(0x23, 0x34, 0x45, 0x56), // only for test ;)
|
||||
ReplSrcAddr: net.ParseIP("10.20.30.40").To4(),
|
||||
ReplSrcMask: net.IPv4Mask(0xf2, 0xe3, 0xd4, 0xc5), // only for test ;)
|
||||
ReplDstAddr: net.ParseIP("2.3.4.5").To4(),
|
||||
ReplDstMask: net.IPv4Mask(0xe3, 0xd4, 0xc5, 0xb6), // only for test ;)
|
||||
ExpiresMin: 0x1234,
|
||||
ExpiresMax: 0x2345,
|
||||
L4Proto: 0xaa55,
|
||||
OrigSrcPort: 123,
|
||||
OrigDstPort: 321,
|
||||
ReplSrcPort: 789,
|
||||
ReplDstPort: 987,
|
||||
},
|
||||
StateMask: 0x55,
|
||||
StatusMask: 0xaa,
|
||||
},
|
||||
empty: &ConntrackMtinfo1{},
|
||||
},
|
||||
{
|
||||
name: "un/marshal ConntrackMtinfo1 IPv6 round-trip",
|
||||
fam: unix.NFPROTO_IPV6,
|
||||
rev: 0,
|
||||
info: &ConntrackMtinfo1{
|
||||
ConntrackMtinfoBase: ConntrackMtinfoBase{
|
||||
OrigSrcAddr: net.ParseIP("fe80::dead:f001"),
|
||||
OrigSrcMask: net.IPMask{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10}, // only for test ;)
|
||||
OrigDstAddr: net.ParseIP("fd00::dead:f001"),
|
||||
OrigDstMask: net.IPMask{0x11, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10}, // only for test ;)
|
||||
ReplSrcAddr: net.ParseIP("fe80::c01d:cafe"),
|
||||
ReplSrcMask: net.IPMask{0x21, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10}, // only for test ;)
|
||||
ReplDstAddr: net.ParseIP("fd00::c01d:cafe"),
|
||||
ReplDstMask: net.IPMask{0x31, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10}, // only for test ;)
|
||||
ExpiresMin: 0x1234,
|
||||
ExpiresMax: 0x2345,
|
||||
L4Proto: 0xaa55,
|
||||
OrigSrcPort: 123,
|
||||
OrigDstPort: 321,
|
||||
ReplSrcPort: 789,
|
||||
ReplDstPort: 987,
|
||||
},
|
||||
StateMask: 0x55,
|
||||
StatusMask: 0xaa,
|
||||
},
|
||||
empty: &ConntrackMtinfo1{},
|
||||
},
|
||||
{
|
||||
name: "un/marshal ConntrackMtinfo2 IPv4 round-trip",
|
||||
fam: unix.NFPROTO_IPV4,
|
||||
rev: 0,
|
||||
info: &ConntrackMtinfo2{
|
||||
ConntrackMtinfoBase: ConntrackMtinfoBase{
|
||||
OrigSrcAddr: net.ParseIP("1.2.3.4").To4(),
|
||||
OrigSrcMask: net.IPv4Mask(0x12, 0x23, 0x34, 0x45), // only for test ;)
|
||||
OrigDstAddr: net.ParseIP("2.3.4.5").To4(),
|
||||
OrigDstMask: net.IPv4Mask(0x23, 0x34, 0x45, 0x56), // only for test ;)
|
||||
ReplSrcAddr: net.ParseIP("10.20.30.40").To4(),
|
||||
ReplSrcMask: net.IPv4Mask(0xf2, 0xe3, 0xd4, 0xc5), // only for test ;)
|
||||
ReplDstAddr: net.ParseIP("2.3.4.5").To4(),
|
||||
ReplDstMask: net.IPv4Mask(0xe3, 0xd4, 0xc5, 0xb6), // only for test ;)
|
||||
ExpiresMin: 0x1234,
|
||||
ExpiresMax: 0x2345,
|
||||
L4Proto: 0xaa55,
|
||||
OrigSrcPort: 123,
|
||||
OrigDstPort: 321,
|
||||
ReplSrcPort: 789,
|
||||
ReplDstPort: 987,
|
||||
},
|
||||
StateMask: 0x55aa,
|
||||
StatusMask: 0xaa55,
|
||||
},
|
||||
empty: &ConntrackMtinfo2{},
|
||||
},
|
||||
{
|
||||
name: "un/marshal ConntrackMtinfo1 IPv6 round-trip",
|
||||
fam: unix.NFPROTO_IPV6,
|
||||
rev: 0,
|
||||
info: &ConntrackMtinfo2{
|
||||
ConntrackMtinfoBase: ConntrackMtinfoBase{
|
||||
OrigSrcAddr: net.ParseIP("fe80::dead:f001"),
|
||||
OrigSrcMask: net.IPMask{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10}, // only for test ;)
|
||||
OrigDstAddr: net.ParseIP("fd00::dead:f001"),
|
||||
OrigDstMask: net.IPMask{0x11, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10}, // only for test ;)
|
||||
ReplSrcAddr: net.ParseIP("fe80::c01d:cafe"),
|
||||
ReplSrcMask: net.IPMask{0x21, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10}, // only for test ;)
|
||||
ReplDstAddr: net.ParseIP("fd00::c01d:cafe"),
|
||||
ReplDstMask: net.IPMask{0x31, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10}, // only for test ;)
|
||||
ExpiresMin: 0x1234,
|
||||
ExpiresMax: 0x2345,
|
||||
L4Proto: 0xaa55,
|
||||
OrigSrcPort: 123,
|
||||
OrigDstPort: 321,
|
||||
ReplSrcPort: 789,
|
||||
ReplDstPort: 987,
|
||||
},
|
||||
StateMask: 0x55aa,
|
||||
StatusMask: 0xaa55,
|
||||
},
|
||||
empty: &ConntrackMtinfo2{},
|
||||
},
|
||||
{
|
||||
name: "un/marshal ConntrackMtinfo3 IPv4 round-trip",
|
||||
fam: unix.NFPROTO_IPV4,
|
||||
rev: 0,
|
||||
info: &ConntrackMtinfo3{
|
||||
ConntrackMtinfo2: ConntrackMtinfo2{
|
||||
ConntrackMtinfoBase: ConntrackMtinfoBase{
|
||||
OrigSrcAddr: net.ParseIP("1.2.3.4").To4(),
|
||||
OrigSrcMask: net.IPv4Mask(0x12, 0x23, 0x34, 0x45), // only for test ;)
|
||||
OrigDstAddr: net.ParseIP("2.3.4.5").To4(),
|
||||
OrigDstMask: net.IPv4Mask(0x23, 0x34, 0x45, 0x56), // only for test ;)
|
||||
ReplSrcAddr: net.ParseIP("10.20.30.40").To4(),
|
||||
ReplSrcMask: net.IPv4Mask(0xf2, 0xe3, 0xd4, 0xc5), // only for test ;)
|
||||
ReplDstAddr: net.ParseIP("2.3.4.5").To4(),
|
||||
ReplDstMask: net.IPv4Mask(0xe3, 0xd4, 0xc5, 0xb6), // only for test ;)
|
||||
ExpiresMin: 0x1234,
|
||||
ExpiresMax: 0x2345,
|
||||
L4Proto: 0xaa55,
|
||||
OrigSrcPort: 123,
|
||||
OrigDstPort: 321,
|
||||
ReplSrcPort: 789,
|
||||
ReplDstPort: 987,
|
||||
},
|
||||
StateMask: 0x55aa,
|
||||
StatusMask: 0xaa55,
|
||||
},
|
||||
OrigSrcPortHigh: 0xabcd,
|
||||
OrigDstPortHigh: 0xcdba,
|
||||
ReplSrcPortHigh: 0x1234,
|
||||
ReplDstPortHigh: 0x4321,
|
||||
},
|
||||
empty: &ConntrackMtinfo3{},
|
||||
},
|
||||
{
|
||||
name: "un/marshal ConntrackMtinfo1 IPv6 round-trip",
|
||||
fam: unix.NFPROTO_IPV6,
|
||||
rev: 0,
|
||||
info: &ConntrackMtinfo3{
|
||||
ConntrackMtinfo2: ConntrackMtinfo2{
|
||||
ConntrackMtinfoBase: ConntrackMtinfoBase{
|
||||
OrigSrcAddr: net.ParseIP("fe80::dead:f001"),
|
||||
OrigSrcMask: net.IPMask{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10}, // only for test ;)
|
||||
OrigDstAddr: net.ParseIP("fd00::dead:f001"),
|
||||
OrigDstMask: net.IPMask{0x11, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10}, // only for test ;)
|
||||
ReplSrcAddr: net.ParseIP("fe80::c01d:cafe"),
|
||||
ReplSrcMask: net.IPMask{0x21, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10}, // only for test ;)
|
||||
ReplDstAddr: net.ParseIP("fd00::c01d:cafe"),
|
||||
ReplDstMask: net.IPMask{0x31, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10}, // only for test ;)
|
||||
ExpiresMin: 0x1234,
|
||||
ExpiresMax: 0x2345,
|
||||
L4Proto: 0xaa55,
|
||||
OrigSrcPort: 123,
|
||||
OrigDstPort: 321,
|
||||
ReplSrcPort: 789,
|
||||
ReplDstPort: 987,
|
||||
},
|
||||
StateMask: 0x55aa,
|
||||
StatusMask: 0xaa55,
|
||||
},
|
||||
OrigSrcPortHigh: 0xabcd,
|
||||
OrigDstPortHigh: 0xcdba,
|
||||
ReplSrcPortHigh: 0x1234,
|
||||
ReplDstPortHigh: 0x4321,
|
||||
},
|
||||
empty: &ConntrackMtinfo3{},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
data, err := tt.info.marshal(TableFamily(tt.fam), tt.rev)
|
||||
if err != nil {
|
||||
t.Fatalf("marshal error: %+v", err)
|
||||
|
||||
}
|
||||
var recoveredInfo InfoAny = tt.empty
|
||||
err = recoveredInfo.unmarshal(TableFamily(tt.fam), tt.rev, data)
|
||||
if err != nil {
|
||||
t.Fatalf("unmarshal error: %+v", err)
|
||||
}
|
||||
if !reflect.DeepEqual(tt.info, recoveredInfo) {
|
||||
t.Fatalf("original %+v and recovered %+v are different", tt.info, recoveredInfo)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
package xt
|
||||
|
||||
import (
|
||||
"github.com/google/nftables/alignedbuff"
|
||||
)
|
||||
|
||||
// Tcp is the Match.Info payload for the tcp xtables extension
|
||||
// (https://wiki.nftables.org/wiki-nftables/index.php/Supported_features_compared_to_xtables#tcp).
|
||||
//
|
||||
// See
|
||||
// https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/netfilter/xt_tcpudp.h#L8
|
||||
type Tcp struct {
|
||||
SrcPorts [2]uint16 // min, max source port range
|
||||
DstPorts [2]uint16 // min, max destination port range
|
||||
Option uint8 // TCP option if non-zero
|
||||
FlagsMask uint8 // TCP flags mask
|
||||
FlagsCmp uint8 // TCP flags compare
|
||||
InvFlags TcpInvFlagset // Inverse flags
|
||||
}
|
||||
|
||||
type TcpInvFlagset uint8
|
||||
|
||||
const (
|
||||
TcpInvSrcPorts TcpInvFlagset = 1 << iota
|
||||
TcpInvDestPorts
|
||||
TcpInvFlags
|
||||
TcpInvOption
|
||||
TcpInvMask TcpInvFlagset = (1 << iota) - 1
|
||||
)
|
||||
|
||||
func (x *Tcp) marshal(fam TableFamily, rev uint32) ([]byte, error) {
|
||||
ab := alignedbuff.New()
|
||||
ab.PutUint16(x.SrcPorts[0])
|
||||
ab.PutUint16(x.SrcPorts[1])
|
||||
ab.PutUint16(x.DstPorts[0])
|
||||
ab.PutUint16(x.DstPorts[1])
|
||||
ab.PutUint8(x.Option)
|
||||
ab.PutUint8(x.FlagsMask)
|
||||
ab.PutUint8(x.FlagsCmp)
|
||||
ab.PutUint8(byte(x.InvFlags))
|
||||
return ab.Data(), nil
|
||||
}
|
||||
|
||||
func (x *Tcp) unmarshal(fam TableFamily, rev uint32, data []byte) error {
|
||||
ab := alignedbuff.NewWithData(data)
|
||||
var err error
|
||||
if x.SrcPorts[0], err = ab.Uint16(); err != nil {
|
||||
return err
|
||||
}
|
||||
if x.SrcPorts[1], err = ab.Uint16(); err != nil {
|
||||
return err
|
||||
}
|
||||
if x.DstPorts[0], err = ab.Uint16(); err != nil {
|
||||
return err
|
||||
}
|
||||
if x.DstPorts[1], err = ab.Uint16(); err != nil {
|
||||
return err
|
||||
}
|
||||
if x.Option, err = ab.Uint8(); err != nil {
|
||||
return err
|
||||
}
|
||||
if x.FlagsMask, err = ab.Uint8(); err != nil {
|
||||
return err
|
||||
}
|
||||
if x.FlagsCmp, err = ab.Uint8(); err != nil {
|
||||
return err
|
||||
}
|
||||
var invFlags uint8
|
||||
if invFlags, err = ab.Uint8(); err != nil {
|
||||
return err
|
||||
}
|
||||
x.InvFlags = TcpInvFlagset(invFlags)
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
package xt
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestMatchTcp(t *testing.T) {
|
||||
t.Parallel()
|
||||
tests := []struct {
|
||||
name string
|
||||
info InfoAny
|
||||
}{
|
||||
{
|
||||
name: "un/marshal Tcp round-trip",
|
||||
info: &Tcp{
|
||||
SrcPorts: [2]uint16{0x1234, 0x5678},
|
||||
DstPorts: [2]uint16{0x2345, 0x6789},
|
||||
Option: 0x12,
|
||||
FlagsMask: 0x34,
|
||||
FlagsCmp: 0x56,
|
||||
InvFlags: 0x78,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
data, err := tt.info.marshal(0, 0)
|
||||
if err != nil {
|
||||
t.Fatalf("marshal error: %+v", err)
|
||||
|
||||
}
|
||||
var recoveredInfo InfoAny = &Tcp{}
|
||||
err = recoveredInfo.unmarshal(0, 0, data)
|
||||
if err != nil {
|
||||
t.Fatalf("unmarshal error: %+v", err)
|
||||
}
|
||||
if !reflect.DeepEqual(tt.info, recoveredInfo) {
|
||||
t.Fatalf("original %+v and recovered %+v are different", tt.info, recoveredInfo)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
package xt
|
||||
|
||||
import (
|
||||
"github.com/google/nftables/alignedbuff"
|
||||
)
|
||||
|
||||
// Tcp is the Match.Info payload for the tcp xtables extension
|
||||
// (https://wiki.nftables.org/wiki-nftables/index.php/Supported_features_compared_to_xtables#tcp).
|
||||
//
|
||||
// See
|
||||
// https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/netfilter/xt_tcpudp.h#L8
|
||||
type Udp struct {
|
||||
SrcPorts [2]uint16 // min, max source port range
|
||||
DstPorts [2]uint16 // min, max destination port range
|
||||
InvFlags UdpInvFlagset // Inverse flags
|
||||
}
|
||||
|
||||
type UdpInvFlagset uint8
|
||||
|
||||
const (
|
||||
UdpInvSrcPorts UdpInvFlagset = 1 << iota
|
||||
UdpInvDestPorts
|
||||
UdpInvMask UdpInvFlagset = (1 << iota) - 1
|
||||
)
|
||||
|
||||
func (x *Udp) marshal(fam TableFamily, rev uint32) ([]byte, error) {
|
||||
ab := alignedbuff.New()
|
||||
ab.PutUint16(x.SrcPorts[0])
|
||||
ab.PutUint16(x.SrcPorts[1])
|
||||
ab.PutUint16(x.DstPorts[0])
|
||||
ab.PutUint16(x.DstPorts[1])
|
||||
ab.PutUint8(byte(x.InvFlags))
|
||||
return ab.Data(), nil
|
||||
}
|
||||
|
||||
func (x *Udp) unmarshal(fam TableFamily, rev uint32, data []byte) error {
|
||||
ab := alignedbuff.NewWithData(data)
|
||||
var err error
|
||||
if x.SrcPorts[0], err = ab.Uint16(); err != nil {
|
||||
return err
|
||||
}
|
||||
if x.SrcPorts[1], err = ab.Uint16(); err != nil {
|
||||
return err
|
||||
}
|
||||
if x.DstPorts[0], err = ab.Uint16(); err != nil {
|
||||
return err
|
||||
}
|
||||
if x.DstPorts[1], err = ab.Uint16(); err != nil {
|
||||
return err
|
||||
}
|
||||
var invFlags uint8
|
||||
if invFlags, err = ab.Uint8(); err != nil {
|
||||
return err
|
||||
}
|
||||
x.InvFlags = UdpInvFlagset(invFlags)
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
package xt
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestMatchUdp(t *testing.T) {
|
||||
t.Parallel()
|
||||
tests := []struct {
|
||||
name string
|
||||
info InfoAny
|
||||
}{
|
||||
{
|
||||
name: "un/marshal Udp round-trip",
|
||||
info: &Udp{
|
||||
SrcPorts: [2]uint16{0x1234, 0x5678},
|
||||
DstPorts: [2]uint16{0x2345, 0x6789},
|
||||
InvFlags: 0x78,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
data, err := tt.info.marshal(0, 0)
|
||||
if err != nil {
|
||||
t.Fatalf("marshal error: %+v", err)
|
||||
|
||||
}
|
||||
var recoveredInfo InfoAny = &Udp{}
|
||||
err = recoveredInfo.unmarshal(0, 0, data)
|
||||
if err != nil {
|
||||
t.Fatalf("unmarshal error: %+v", err)
|
||||
}
|
||||
if !reflect.DeepEqual(tt.info, recoveredInfo) {
|
||||
t.Fatalf("original %+v and recovered %+v are different", tt.info, recoveredInfo)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,106 @@
|
|||
package xt
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
"github.com/google/nftables/alignedbuff"
|
||||
)
|
||||
|
||||
type NatRangeFlags uint
|
||||
|
||||
// See: https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/netfilter/nf_nat.h#L8
|
||||
const (
|
||||
NatRangeMapIPs NatRangeFlags = (1 << iota)
|
||||
NatRangeProtoSpecified
|
||||
NatRangeProtoRandom
|
||||
NatRangePersistent
|
||||
NatRangeProtoRandomFully
|
||||
NatRangeProtoOffset
|
||||
NatRangeNetmap
|
||||
|
||||
NatRangeMask NatRangeFlags = (1 << iota) - 1
|
||||
|
||||
NatRangeProtoRandomAll = NatRangeProtoRandom | NatRangeProtoRandomFully
|
||||
)
|
||||
|
||||
// see: https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/netfilter/nf_nat.h#L38
|
||||
type NatRange struct {
|
||||
Flags uint // sic! platform/arch/compiler-dependent uint size
|
||||
MinIP net.IP // always taking up space for an IPv6 address
|
||||
MaxIP net.IP // dito
|
||||
MinPort uint16
|
||||
MaxPort uint16
|
||||
}
|
||||
|
||||
// see: https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/netfilter/nf_nat.h#L46
|
||||
type NatRange2 struct {
|
||||
NatRange
|
||||
BasePort uint16
|
||||
}
|
||||
|
||||
func (x *NatRange) marshal(fam TableFamily, rev uint32) ([]byte, error) {
|
||||
ab := alignedbuff.New()
|
||||
if err := x.marshalAB(fam, rev, &ab); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ab.Data(), nil
|
||||
}
|
||||
|
||||
func (x *NatRange) marshalAB(fam TableFamily, rev uint32, ab *alignedbuff.AlignedBuff) error {
|
||||
ab.PutUint(x.Flags)
|
||||
if err := putIPv46(ab, fam, x.MinIP); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := putIPv46(ab, fam, x.MaxIP); err != nil {
|
||||
return err
|
||||
}
|
||||
ab.PutUint16BE(x.MinPort)
|
||||
ab.PutUint16BE(x.MaxPort)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *NatRange) unmarshal(fam TableFamily, rev uint32, data []byte) error {
|
||||
ab := alignedbuff.NewWithData(data)
|
||||
return x.unmarshalAB(fam, rev, &ab)
|
||||
}
|
||||
|
||||
func (x *NatRange) unmarshalAB(fam TableFamily, rev uint32, ab *alignedbuff.AlignedBuff) error {
|
||||
var err error
|
||||
if x.Flags, err = ab.Uint(); err != nil {
|
||||
return err
|
||||
}
|
||||
if x.MinIP, err = iPv46(ab, fam); err != nil {
|
||||
return err
|
||||
}
|
||||
if x.MaxIP, err = iPv46(ab, fam); err != nil {
|
||||
return err
|
||||
}
|
||||
if x.MinPort, err = ab.Uint16BE(); err != nil {
|
||||
return err
|
||||
}
|
||||
if x.MaxPort, err = ab.Uint16BE(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *NatRange2) marshal(fam TableFamily, rev uint32) ([]byte, error) {
|
||||
ab := alignedbuff.New()
|
||||
if err := x.NatRange.marshalAB(fam, rev, &ab); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ab.PutUint16BE(x.BasePort)
|
||||
return ab.Data(), nil
|
||||
}
|
||||
|
||||
func (x *NatRange2) unmarshal(fam TableFamily, rev uint32, data []byte) error {
|
||||
ab := alignedbuff.NewWithData(data)
|
||||
var err error
|
||||
if err = x.NatRange.unmarshalAB(fam, rev, &ab); err != nil {
|
||||
return err
|
||||
}
|
||||
if x.BasePort, err = ab.Uint16BE(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,97 @@
|
|||
package xt
|
||||
|
||||
import (
|
||||
"net"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func TestTargetDNAT(t *testing.T) {
|
||||
t.Parallel()
|
||||
tests := []struct {
|
||||
name string
|
||||
fam byte
|
||||
rev uint32
|
||||
info InfoAny
|
||||
empty InfoAny
|
||||
}{
|
||||
{
|
||||
name: "un/marshal NatRange IPv4 round-trip",
|
||||
fam: unix.NFPROTO_IPV4,
|
||||
rev: 0,
|
||||
info: &NatRange{
|
||||
Flags: 0x1234,
|
||||
MinIP: net.ParseIP("12.23.34.45").To4(),
|
||||
MaxIP: net.ParseIP("21.32.43.54").To4(),
|
||||
MinPort: 0x5678,
|
||||
MaxPort: 0xabcd,
|
||||
},
|
||||
empty: &NatRange{},
|
||||
},
|
||||
{
|
||||
name: "un/marshal NatRange IPv6 round-trip",
|
||||
fam: unix.NFPROTO_IPV6,
|
||||
rev: 0,
|
||||
info: &NatRange{
|
||||
Flags: 0x1234,
|
||||
MinIP: net.ParseIP("fe80::dead:beef"),
|
||||
MaxIP: net.ParseIP("fe80::c001:cafe"),
|
||||
MinPort: 0x5678,
|
||||
MaxPort: 0xabcd,
|
||||
},
|
||||
empty: &NatRange{},
|
||||
},
|
||||
{
|
||||
name: "un/marshal NatRange2 IPv4 round-trip",
|
||||
fam: unix.NFPROTO_IPV4,
|
||||
rev: 0,
|
||||
info: &NatRange2{
|
||||
NatRange: NatRange{
|
||||
Flags: 0x1234,
|
||||
MinIP: net.ParseIP("12.23.34.45").To4(),
|
||||
MaxIP: net.ParseIP("21.32.43.54").To4(),
|
||||
MinPort: 0x5678,
|
||||
MaxPort: 0xabcd,
|
||||
},
|
||||
BasePort: 0xfedc,
|
||||
},
|
||||
empty: &NatRange2{},
|
||||
},
|
||||
{
|
||||
name: "un/marshal NatRange2 IPv6 round-trip",
|
||||
fam: unix.NFPROTO_IPV6,
|
||||
rev: 0,
|
||||
info: &NatRange2{
|
||||
NatRange: NatRange{
|
||||
Flags: 0x1234,
|
||||
MinIP: net.ParseIP("fe80::dead:beef"),
|
||||
MaxIP: net.ParseIP("fe80::c001:cafe"),
|
||||
MinPort: 0x5678,
|
||||
MaxPort: 0xabcd,
|
||||
},
|
||||
BasePort: 0xfedc,
|
||||
},
|
||||
empty: &NatRange2{},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
data, err := tt.info.marshal(TableFamily(tt.fam), tt.rev)
|
||||
if err != nil {
|
||||
t.Fatalf("marshal error: %+v", err)
|
||||
|
||||
}
|
||||
var recoveredInfo InfoAny = tt.empty
|
||||
err = recoveredInfo.unmarshal(TableFamily(tt.fam), tt.rev, data)
|
||||
if err != nil {
|
||||
t.Fatalf("unmarshal error: %+v", err)
|
||||
}
|
||||
if !reflect.DeepEqual(tt.info, recoveredInfo) {
|
||||
t.Fatalf("original %+v and recovered %+v are different", tt.info, recoveredInfo)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
package xt
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
|
||||
"github.com/google/nftables/alignedbuff"
|
||||
)
|
||||
|
||||
// See https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/netfilter/nf_nat.h#L25
|
||||
type NatIPv4Range struct {
|
||||
Flags uint // sic!
|
||||
MinIP net.IP
|
||||
MaxIP net.IP
|
||||
MinPort uint16
|
||||
MaxPort uint16
|
||||
}
|
||||
|
||||
// NatIPv4MultiRangeCompat despite being a slice of NAT IPv4 ranges is currently allowed to
|
||||
// only hold exactly one element.
|
||||
//
|
||||
// See https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/netfilter/nf_nat.h#L33
|
||||
type NatIPv4MultiRangeCompat []NatIPv4Range
|
||||
|
||||
func (x *NatIPv4MultiRangeCompat) marshal(fam TableFamily, rev uint32) ([]byte, error) {
|
||||
ab := alignedbuff.New()
|
||||
if len(*x) != 1 {
|
||||
return nil, errors.New("MasqueradeIp must contain exactly one NatIPv4Range")
|
||||
}
|
||||
ab.PutUint(uint(len(*x)))
|
||||
for _, nat := range *x {
|
||||
if err := nat.marshalAB(fam, rev, &ab); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return ab.Data(), nil
|
||||
}
|
||||
|
||||
func (x *NatIPv4MultiRangeCompat) unmarshal(fam TableFamily, rev uint32, data []byte) error {
|
||||
ab := alignedbuff.NewWithData(data)
|
||||
l, err := ab.Uint()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
nats := make(NatIPv4MultiRangeCompat, l)
|
||||
for l > 0 {
|
||||
l--
|
||||
if err := nats[l].unmarshalAB(fam, rev, &ab); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
*x = nats
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *NatIPv4Range) marshalAB(fam TableFamily, rev uint32, ab *alignedbuff.AlignedBuff) error {
|
||||
ab.PutUint(x.Flags)
|
||||
ab.PutBytesAligned32(x.MinIP.To4(), 4)
|
||||
ab.PutBytesAligned32(x.MaxIP.To4(), 4)
|
||||
ab.PutUint16BE(x.MinPort)
|
||||
ab.PutUint16BE(x.MaxPort)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *NatIPv4Range) unmarshalAB(fam TableFamily, rev uint32, ab *alignedbuff.AlignedBuff) error {
|
||||
var err error
|
||||
if x.Flags, err = ab.Uint(); err != nil {
|
||||
return err
|
||||
}
|
||||
var ip []byte
|
||||
if ip, err = ab.BytesAligned32(4); err != nil {
|
||||
return err
|
||||
}
|
||||
x.MinIP = net.IP(ip)
|
||||
if ip, err = ab.BytesAligned32(4); err != nil {
|
||||
return err
|
||||
}
|
||||
x.MaxIP = net.IP(ip)
|
||||
if x.MinPort, err = ab.Uint16BE(); err != nil {
|
||||
return err
|
||||
}
|
||||
if x.MaxPort, err = ab.Uint16BE(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
package xt
|
||||
|
||||
import (
|
||||
"net"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func TestTargetMasqueradeIP(t *testing.T) {
|
||||
t.Parallel()
|
||||
tests := []struct {
|
||||
name string
|
||||
fam byte
|
||||
rev uint32
|
||||
info InfoAny
|
||||
empty InfoAny
|
||||
}{
|
||||
{
|
||||
name: "un/marshal NatIPv4Range round-trip",
|
||||
fam: unix.NFPROTO_IPV4,
|
||||
rev: 0,
|
||||
info: &NatIPv4MultiRangeCompat{
|
||||
NatIPv4Range{
|
||||
Flags: 0x1234,
|
||||
MinIP: net.ParseIP("12.23.34.45").To4(),
|
||||
MaxIP: net.ParseIP("21.32.43.54").To4(),
|
||||
MinPort: 0x5678,
|
||||
MaxPort: 0xabcd,
|
||||
},
|
||||
},
|
||||
empty: new(NatIPv4MultiRangeCompat),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
data, err := tt.info.marshal(TableFamily(tt.fam), tt.rev)
|
||||
if err != nil {
|
||||
t.Fatalf("marshal error: %+v", err)
|
||||
|
||||
}
|
||||
var recoveredInfo InfoAny = tt.empty
|
||||
err = recoveredInfo.unmarshal(TableFamily(tt.fam), tt.rev, data)
|
||||
if err != nil {
|
||||
t.Fatalf("unmarshal error: %+v", err)
|
||||
}
|
||||
if !reflect.DeepEqual(tt.info, recoveredInfo) {
|
||||
t.Fatalf("original %+v and recovered %+v are different", tt.info, recoveredInfo)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
package xt
|
||||
|
||||
// Unknown represents the bytes Info payload for unknown Info types where no
|
||||
// dedicated match/target info type has (yet) been defined.
|
||||
type Unknown []byte
|
||||
|
||||
func (x *Unknown) marshal(fam TableFamily, rev uint32) ([]byte, error) {
|
||||
// In case of unknown payload we assume its creator knows what she/he does
|
||||
// and thus we don't do any alignment padding. Just take the payload "as
|
||||
// is".
|
||||
return *x, nil
|
||||
}
|
||||
|
||||
func (x *Unknown) unmarshal(fam TableFamily, rev uint32, data []byte) error {
|
||||
*x = data
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
package xt
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestUnknown(t *testing.T) {
|
||||
t.Parallel()
|
||||
payload := Unknown([]byte{0xb0, 0x1d, 0xca, 0xfe, 0x00})
|
||||
tests := []struct {
|
||||
name string
|
||||
info InfoAny
|
||||
}{
|
||||
{
|
||||
name: "un/marshal Unknown round-trip",
|
||||
info: &payload,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
data, err := tt.info.marshal(0, 0)
|
||||
if err != nil {
|
||||
t.Fatalf("marshal error: %+v", err)
|
||||
|
||||
}
|
||||
var recoveredInfo InfoAny = &Unknown{}
|
||||
err = recoveredInfo.unmarshal(0, 0, data)
|
||||
if err != nil {
|
||||
t.Fatalf("unmarshal error: %+v", err)
|
||||
}
|
||||
if !reflect.DeepEqual(tt.info, recoveredInfo) {
|
||||
t.Fatalf("original %+v and recovered %+v are different", tt.info, recoveredInfo)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
package xt
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"github.com/google/nftables/alignedbuff"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func bool32(ab *alignedbuff.AlignedBuff) (bool, error) {
|
||||
v, err := ab.Uint32()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if v != 0 {
|
||||
return true, nil
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func putBool32(ab *alignedbuff.AlignedBuff, b bool) {
|
||||
if b {
|
||||
ab.PutUint32(1)
|
||||
return
|
||||
}
|
||||
ab.PutUint32(0)
|
||||
}
|
||||
|
||||
func iPv46(ab *alignedbuff.AlignedBuff, fam TableFamily) (net.IP, error) {
|
||||
ip, err := ab.BytesAligned32(16)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch fam {
|
||||
case unix.NFPROTO_IPV4:
|
||||
return net.IP(ip[:4]), nil
|
||||
case unix.NFPROTO_IPV6:
|
||||
return net.IP(ip), nil
|
||||
default:
|
||||
return nil, fmt.Errorf("unmarshal IP: unsupported table family %d", fam)
|
||||
}
|
||||
}
|
||||
|
||||
func iPv46Mask(ab *alignedbuff.AlignedBuff, fam TableFamily) (net.IPMask, error) {
|
||||
v, err := iPv46(ab, fam)
|
||||
return net.IPMask(v), err
|
||||
}
|
||||
|
||||
func putIPv46(ab *alignedbuff.AlignedBuff, fam TableFamily, ip net.IP) error {
|
||||
switch fam {
|
||||
case unix.NFPROTO_IPV4:
|
||||
ab.PutBytesAligned32(ip.To4(), 16)
|
||||
case unix.NFPROTO_IPV6:
|
||||
ab.PutBytesAligned32(ip.To16(), 16)
|
||||
default:
|
||||
return fmt.Errorf("marshal IP: unsupported table family %d", fam)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func putIPv46Mask(ab *alignedbuff.AlignedBuff, fam TableFamily, mask net.IPMask) error {
|
||||
return putIPv46(ab, fam, net.IP(mask))
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
|
||||
Package xt implements dedicated types for (some) of the "Info" payload in Match
|
||||
and Target expressions that bridge between the nftables and xtables worlds.
|
||||
|
||||
Bridging between the more unified world of nftables and the slightly
|
||||
heterogenous world of xtables comes with some caveats. Unmarshalling the
|
||||
extension/translation information in Match and Target expressions requires
|
||||
information about the table family the information belongs to, as well as type
|
||||
and type revision information. In consequence, unmarshalling the Match and
|
||||
Target Info field payloads often (but not necessarily always) require the table
|
||||
family and revision information, so it gets passed to the type-specific
|
||||
unmarshallers.
|
||||
|
||||
To complicate things more, even marshalling requires knowledge about the
|
||||
enclosing table family. The NatRange/NatRange2 types are an example, where it is
|
||||
necessary to differentiate between IPv4 and IPv6 address marshalling. Due to
|
||||
Go's net.IP habit to normally store IPv4 addresses as IPv4-compatible IPv6
|
||||
addresses (see also RFC 4291, section 2.5.5.1) marshalling must be handled
|
||||
differently in the context of an IPv6 table compared to an IPv4 table. In an
|
||||
IPv4 table, an IPv4-compatible IPv6 address must be marshalled as a 32bit
|
||||
address, whereas in an IPv6 table the IPv4 address must be marshalled as an
|
||||
128bit IPv4-compatible IPv6 address. Not relying on heuristics here we avoid
|
||||
behavior unexpected and most probably unknown to our API users. The net.IP habit
|
||||
of storing IPv4 addresses in two different storage formats is already a source
|
||||
for trouble, especially when comparing net.IPs from different Go module sources.
|
||||
We won't add to this confusion. (...or maybe we can, because of it?)
|
||||
|
||||
An important property of all types of Info extension/translation payloads is
|
||||
that their marshalling and unmarshalling doesn't follow netlink's TLV
|
||||
(tag-length-value) architecture. Instead, Info payloads a basically plain binary
|
||||
blobs of their respective type-specific data structures, so host
|
||||
platform/architecture alignment and data type sizes apply. The alignedbuff
|
||||
package implements the different required data types alignments.
|
||||
|
||||
Please note that Info payloads are always padded at their end to the next uint64
|
||||
alignment. Kernel code is checking for the padded payload size and will reject
|
||||
payloads not correctly padded at their ends.
|
||||
|
||||
Most of the time, we find explifcitly sized (unsigned integer) data types.
|
||||
However, there are notable exceptions where "unsigned int" is used: on 64bit
|
||||
platforms this mostly translates into 32bit(!). This differs from Go mapping
|
||||
uint to uint64 instead. This package currently clamps its mapping of C's
|
||||
"unsigned int" to Go's uint32 for marshalling and unmarshalling. If in the
|
||||
future 128bit platforms with a differently sized C unsigned int should come into
|
||||
production, then the alignedbuff package will need to be adapted accordingly, as
|
||||
it abstracts away this data type handling.
|
||||
|
||||
*/
|
||||
package xt
|
Loading…
Reference in New Issue