add typed xtables information un/marshalling

more tests and fixes

more info support; refactoring
This commit is contained in:
thediveo 2022-05-14 16:49:27 +00:00 committed by Michael Stapelberg
parent 4b6f0f2b44
commit 8ea944061f
21 changed files with 1483 additions and 10 deletions

View File

@ -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
}

View File

@ -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,
},
},
}

View File

@ -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
}

View File

@ -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,
},
},
}

94
xt/info.go Normal file
View File

@ -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
}

89
xt/match_addrtype.go Normal file
View File

@ -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
}

71
xt/match_addrtype_test.go Normal file
View File

@ -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)
}
})
}
}

250
xt/match_conntrack.go Normal file
View File

@ -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
}

213
xt/match_conntrack_test.go Normal file
View File

@ -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)
}
})
}
}

74
xt/match_tcp.go Normal file
View File

@ -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
}

44
xt/match_tcp_test.go Normal file
View File

@ -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)
}
})
}
}

57
xt/match_udp.go Normal file
View File

@ -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
}

41
xt/match_udp_test.go Normal file
View File

@ -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)
}
})
}
}

106
xt/target_dnat.go Normal file
View File

@ -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
}

97
xt/target_dnat_test.go Normal file
View File

@ -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)
}
})
}
}

View File

@ -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
}

View File

@ -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)
}
})
}
}

17
xt/unknown.go Normal file
View File

@ -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
}

38
xt/unknown_test.go Normal file
View File

@ -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)
}
})
}
}

64
xt/util.go Normal file
View File

@ -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))
}

50
xt/xt.go Normal file
View File

@ -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