From dab6002a0916e80fafe63aebcc104d98a4e8fc8c Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 4 Jun 2018 22:48:32 +0200 Subject: [PATCH] add enough expressions to express port forwardings --- .../binaryutil => binaryutil}/binaryutil.go | 0 expr/expr.go | 6 +- expr/immediate.go | 47 +++++++++++ expr/nat.go | 65 ++++++++++++++ expr/payload.go | 53 ++++++++++++ nativeendian.go | 21 ----- nftables.go | 2 +- nftables_test.go | 84 ++++++++++++++++--- 8 files changed, 243 insertions(+), 35 deletions(-) rename {internal/binaryutil => binaryutil}/binaryutil.go (100%) create mode 100644 expr/immediate.go create mode 100644 expr/nat.go create mode 100644 expr/payload.go delete mode 100644 nativeendian.go diff --git a/internal/binaryutil/binaryutil.go b/binaryutil/binaryutil.go similarity index 100% rename from internal/binaryutil/binaryutil.go rename to binaryutil/binaryutil.go diff --git a/expr/expr.go b/expr/expr.go index e161e14..f1e6d5c 100644 --- a/expr/expr.go +++ b/expr/expr.go @@ -16,7 +16,7 @@ package expr import ( - "github.com/google/nftables/internal/binaryutil" + "github.com/google/nftables/binaryutil" "github.com/mdlayher/netlink" "golang.org/x/sys/unix" ) @@ -116,12 +116,12 @@ const ( type Cmp struct { Op CmpOp Register uint32 - Data uint32 + Data []byte } func (e *Cmp) marshal() ([]byte, error) { cmpData, err := netlink.MarshalAttributes([]netlink.Attribute{ - {Type: unix.NFTA_DATA_VALUE, Data: binaryutil.NativeEndian.PutUint32(e.Data)}, + {Type: unix.NFTA_DATA_VALUE, Data: e.Data}, }) if err != nil { return nil, err diff --git a/expr/immediate.go b/expr/immediate.go new file mode 100644 index 0000000..38f815d --- /dev/null +++ b/expr/immediate.go @@ -0,0 +1,47 @@ +// Copyright 2018 Google LLC. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package expr + +import ( + "github.com/google/nftables/binaryutil" + "github.com/mdlayher/netlink" + "golang.org/x/sys/unix" +) + +type Immediate struct { + Register uint32 + Data []byte +} + +func (e *Immediate) marshal() ([]byte, error) { + immData, err := netlink.MarshalAttributes([]netlink.Attribute{ + {Type: unix.NFTA_DATA_VALUE, Data: e.Data}, + }) + if err != nil { + return nil, err + } + + data, err := netlink.MarshalAttributes([]netlink.Attribute{ + {Type: unix.NFTA_IMMEDIATE_DREG, Data: binaryutil.BigEndian.PutUint32(e.Register)}, + {Type: unix.NLA_F_NESTED | unix.NFTA_IMMEDIATE_DATA, Data: immData}, + }) + if err != nil { + return nil, err + } + return netlink.MarshalAttributes([]netlink.Attribute{ + {Type: unix.NFTA_EXPR_NAME, Data: []byte("immediate\x00")}, + {Type: unix.NLA_F_NESTED | unix.NFTA_EXPR_DATA, Data: data}, + }) +} diff --git a/expr/nat.go b/expr/nat.go new file mode 100644 index 0000000..bbfc90b --- /dev/null +++ b/expr/nat.go @@ -0,0 +1,65 @@ +// Copyright 2018 Google LLC. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package expr + +import ( + "github.com/google/nftables/binaryutil" + "github.com/mdlayher/netlink" + "golang.org/x/sys/unix" +) + +type NATType uint32 + +// Possible NATType values. +const ( + NATTypeSourceNAT NATType = unix.NFT_NAT_SNAT + NATTypeDestNAT NATType = unix.NFT_NAT_DNAT +) + +type NAT struct { + Type NATType + Family uint32 // TODO: typed const + RegAddrMin uint32 + RegProtoMin uint32 +} + +// |00048|N-|00001| |len |flags| type| +// |00008|--|00001| |len |flags| type| +// | 6e 61 74 00 | | data | n a t +// |00036|N-|00002| |len |flags| type| +// |00008|--|00001| |len |flags| type| NFTA_NAT_TYPE +// | 00 00 00 01 | | data | NFT_NAT_DNAT +// |00008|--|00002| |len |flags| type| NFTA_NAT_FAMILY +// | 00 00 00 02 | | data | NFPROTO_IPV4 +// |00008|--|00003| |len |flags| type| NFTA_NAT_REG_ADDR_MIN +// | 00 00 00 01 | | data | reg 1 +// |00008|--|00005| |len |flags| type| NFTA_NAT_REG_PROTO_MIN +// | 00 00 00 02 | | data | reg 2 + +func (e *NAT) marshal() ([]byte, error) { + data, err := netlink.MarshalAttributes([]netlink.Attribute{ + {Type: unix.NFTA_NAT_TYPE, Data: binaryutil.BigEndian.PutUint32(uint32(e.Type))}, + {Type: unix.NFTA_NAT_FAMILY, Data: binaryutil.BigEndian.PutUint32(e.Family)}, + {Type: unix.NFTA_NAT_REG_ADDR_MIN, Data: binaryutil.BigEndian.PutUint32(e.RegAddrMin)}, + {Type: unix.NFTA_NAT_REG_PROTO_MIN, Data: binaryutil.BigEndian.PutUint32(e.RegProtoMin)}, + }) + if err != nil { + return nil, err + } + return netlink.MarshalAttributes([]netlink.Attribute{ + {Type: unix.NFTA_EXPR_NAME, Data: []byte("nat\x00")}, + {Type: unix.NLA_F_NESTED | unix.NFTA_EXPR_DATA, Data: data}, + }) +} diff --git a/expr/payload.go b/expr/payload.go new file mode 100644 index 0000000..5d48d75 --- /dev/null +++ b/expr/payload.go @@ -0,0 +1,53 @@ +// Copyright 2018 Google LLC. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package expr + +import ( + "github.com/google/nftables/binaryutil" + "github.com/mdlayher/netlink" + "golang.org/x/sys/unix" +) + +type PayloadBase uint32 + +// Possible PayloadBase values. +const ( + PayloadBaseLLHeader PayloadBase = unix.NFT_PAYLOAD_LL_HEADER + PayloadBaseNetworkHeader PayloadBase = unix.NFT_PAYLOAD_NETWORK_HEADER + PayloadBaseTransportHeader PayloadBase = unix.NFT_PAYLOAD_TRANSPORT_HEADER +) + +type Payload struct { + DestRegister uint32 + Base PayloadBase + Offset uint32 + Len uint32 +} + +func (e *Payload) marshal() ([]byte, error) { + data, err := netlink.MarshalAttributes([]netlink.Attribute{ + {Type: unix.NFTA_PAYLOAD_DREG, Data: binaryutil.BigEndian.PutUint32(e.DestRegister)}, + {Type: unix.NFTA_PAYLOAD_BASE, Data: binaryutil.BigEndian.PutUint32(uint32(e.Base))}, + {Type: unix.NFTA_PAYLOAD_OFFSET, Data: binaryutil.BigEndian.PutUint32(e.Offset)}, + {Type: unix.NFTA_PAYLOAD_LEN, Data: binaryutil.BigEndian.PutUint32(e.Len)}, + }) + if err != nil { + return nil, err + } + return netlink.MarshalAttributes([]netlink.Attribute{ + {Type: unix.NFTA_EXPR_NAME, Data: []byte("payload\x00")}, + {Type: unix.NLA_F_NESTED | unix.NFTA_EXPR_DATA, Data: data}, + }) +} diff --git a/nativeendian.go b/nativeendian.go deleted file mode 100644 index 5bdf5c8..0000000 --- a/nativeendian.go +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright 2018 Google LLC. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package nftables - -import "github.com/google/nftables/internal/binaryutil" - -// NativeEndian is either little endian or big endian, depending on the -// native endian-ness. -var NativeEndian binaryutil.ByteOrder = binaryutil.NativeEndian diff --git a/nftables.go b/nftables.go index ebf78e9..82bf204 100644 --- a/nftables.go +++ b/nftables.go @@ -19,8 +19,8 @@ import ( "fmt" "math" + "github.com/google/nftables/binaryutil" "github.com/google/nftables/expr" - "github.com/google/nftables/internal/binaryutil" "github.com/mdlayher/netlink" "github.com/mdlayher/netlink/nltest" "golang.org/x/sys/unix" diff --git a/nftables_test.go b/nftables_test.go index 34c5d1d..d4545b2 100644 --- a/nftables_test.go +++ b/nftables_test.go @@ -16,17 +16,23 @@ package nftables_test import ( "bytes" + "net" "testing" "github.com/google/nftables" + "github.com/google/nftables/binaryutil" "github.com/google/nftables/expr" "github.com/mdlayher/netlink" + "golang.org/x/sys/unix" ) -func TestConfigureNAT(t *testing.T) { - // TODO: skip this test as non-root - // TODO: run this in its own network namespace to not mess with the host +func ifname(n string) []byte { + b := make([]byte, 16) + copy(b, []byte(n+"\x00")) + return b +} +func TestConfigureNAT(t *testing.T) { // The want byte sequences come from stracing nft(8), e.g.: // strace -f -v -x -s 2048 -eraw=sendto nft add table ip nat // @@ -41,8 +47,10 @@ func TestConfigureNAT(t *testing.T) { []byte("\x02\x00\x00\x00\x08\x00\x01\x00\x6e\x61\x74\x00\x0f\x00\x03\x00\x70\x72\x65\x72\x6f\x75\x74\x69\x6e\x67\x00\x00\x14\x00\x04\x80\x08\x00\x01\x00\x00\x00\x00\x00\x08\x00\x02\x00\x00\x00\x00\x00\x08\x00\x07\x00\x6e\x61\x74\x00"), // nft add chain nat postrouting { type nat hook postrouting priority 100 \; } []byte("\x02\x00\x00\x00\x08\x00\x01\x00\x6e\x61\x74\x00\x10\x00\x03\x00\x70\x6f\x73\x74\x72\x6f\x75\x74\x69\x6e\x67\x00\x14\x00\x04\x80\x08\x00\x01\x00\x00\x00\x00\x04\x08\x00\x02\x00\x00\x00\x00\x64\x08\x00\x07\x00\x6e\x61\x74\x00"), - // nft add rule nat postrouting oif wlp4s0 masquerade - []byte("\x02\x00\x00\x00\x08\x00\x01\x00\x6e\x61\x74\x00\x10\x00\x02\x00\x70\x6f\x73\x74\x72\x6f\x75\x74\x69\x6e\x67\x00\x68\x00\x04\x80\x24\x00\x01\x80\x09\x00\x01\x00\x6d\x65\x74\x61\x00\x00\x00\x00\x14\x00\x02\x80\x08\x00\x02\x00\x00\x00\x00\x05\x08\x00\x01\x00\x00\x00\x00\x01\x2c\x00\x01\x80\x08\x00\x01\x00\x63\x6d\x70\x00\x20\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x00\x0c\x00\x03\x80\x08\x00\x01\x00\x03\x00\x00\x00\x14\x00\x01\x80\x09\x00\x01\x00\x6d\x61\x73\x71\x00\x00\x00\x00\x04\x00\x02\x80"), + // nft add rule nat postrouting oifname uplink0 masquerade + []byte("\x02\x00\x00\x00\x08\x00\x01\x00\x6e\x61\x74\x00\x10\x00\x02\x00\x70\x6f\x73\x74\x72\x6f\x75\x74\x69\x6e\x67\x00\x74\x00\x04\x80\x24\x00\x01\x80\x09\x00\x01\x00\x6d\x65\x74\x61\x00\x00\x00\x00\x14\x00\x02\x80\x08\x00\x02\x00\x00\x00\x00\x07\x08\x00\x01\x00\x00\x00\x00\x01\x38\x00\x01\x80\x08\x00\x01\x00\x63\x6d\x70\x00\x2c\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x00\x18\x00\x03\x80\x14\x00\x01\x00\x75\x70\x6c\x69\x6e\x6b\x30\x00\x00\x00\x00\x00\x00\x00\x00\x00\x14\x00\x01\x80\x09\x00\x01\x00\x6d\x61\x73\x71\x00\x00\x00\x00\x04\x00\x02\x80"), + // nft add rule nat prerouting iif uplink0 tcp dport 4070 dnat 192.168.23.2:4080 + []byte("\x02\x00\x00\x00\x08\x00\x01\x00\x6e\x61\x74\x00\x0f\x00\x02\x00\x70\x72\x65\x72\x6f\x75\x74\x69\x6e\x67\x00\x00\x98\x01\x04\x80\x24\x00\x01\x80\x09\x00\x01\x00\x6d\x65\x74\x61\x00\x00\x00\x00\x14\x00\x02\x80\x08\x00\x02\x00\x00\x00\x00\x06\x08\x00\x01\x00\x00\x00\x00\x01\x38\x00\x01\x80\x08\x00\x01\x00\x63\x6d\x70\x00\x2c\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x00\x18\x00\x03\x80\x14\x00\x01\x00\x75\x70\x6c\x69\x6e\x6b\x30\x00\x00\x00\x00\x00\x00\x00\x00\x00\x24\x00\x01\x80\x09\x00\x01\x00\x6d\x65\x74\x61\x00\x00\x00\x00\x14\x00\x02\x80\x08\x00\x02\x00\x00\x00\x00\x10\x08\x00\x01\x00\x00\x00\x00\x01\x2c\x00\x01\x80\x08\x00\x01\x00\x63\x6d\x70\x00\x20\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x00\x0c\x00\x03\x80\x05\x00\x01\x00\x06\x00\x00\x00\x34\x00\x01\x80\x0c\x00\x01\x00\x70\x61\x79\x6c\x6f\x61\x64\x00\x24\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x02\x08\x00\x03\x00\x00\x00\x00\x02\x08\x00\x04\x00\x00\x00\x00\x02\x2c\x00\x01\x80\x08\x00\x01\x00\x63\x6d\x70\x00\x20\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x00\x0c\x00\x03\x80\x06\x00\x01\x00\x0f\xe6\x00\x00\x2c\x00\x01\x80\x0e\x00\x01\x00\x69\x6d\x6d\x65\x64\x69\x61\x74\x65\x00\x00\x00\x18\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x0c\x00\x02\x80\x08\x00\x01\x00\xc0\xa8\x17\x02\x2c\x00\x01\x80\x0e\x00\x01\x00\x69\x6d\x6d\x65\x64\x69\x61\x74\x65\x00\x00\x00\x18\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x02\x0c\x00\x02\x80\x06\x00\x01\x00\x0f\xf0\x00\x00\x30\x00\x01\x80\x08\x00\x01\x00\x6e\x61\x74\x00\x24\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x02\x08\x00\x03\x00\x00\x00\x00\x01\x08\x00\x05\x00\x00\x00\x00\x02"), // batch end []byte("\x00\x00\x0a\x00"), } @@ -76,7 +84,7 @@ func TestConfigureNAT(t *testing.T) { Name: "nat", }) - c.AddChain(&nftables.Chain{ + prerouting := c.AddChain(&nftables.Chain{ Name: "prerouting", Hooknum: nftables.ChainHookPrerouting, Priority: nftables.ChainPriorityFilter, @@ -96,19 +104,75 @@ func TestConfigureNAT(t *testing.T) { Table: nat, Chain: postrouting, Exprs: []expr.Any{ - // meta load oif => reg 1 - &expr.Meta{Key: expr.MetaKeyOIF, Register: 1}, - // cmp eq reg 1 0x00000003 + // meta load oifname => reg 1 + &expr.Meta{Key: expr.MetaKeyOIFNAME, Register: 1}, + // cmp eq reg 1 0x696c7075 0x00306b6e 0x00000000 0x00000000 &expr.Cmp{ Op: expr.CmpOpEq, Register: 1, - Data: 3, /* wlp4s0 */ + Data: ifname("uplink0"), }, // masq &expr.Masq{}, }, }) + c.AddRule(&nftables.Rule{ + Table: nat, + Chain: prerouting, + Exprs: []expr.Any{ + // [ meta load iifname => reg 1 ] + &expr.Meta{Key: expr.MetaKeyIIFNAME, Register: 1}, + // [ cmp eq reg 1 0x696c7075 0x00306b6e 0x00000000 0x00000000 ] + &expr.Cmp{ + Op: expr.CmpOpEq, + Register: 1, + Data: ifname("uplink0"), + }, + + // [ meta load l4proto => reg 1 ] + &expr.Meta{Key: expr.MetaKeyL4PROTO, Register: 1}, + // [ cmp eq reg 1 0x00000006 ] + &expr.Cmp{ + Op: expr.CmpOpEq, + Register: 1, + Data: []byte{unix.IPPROTO_TCP}, + }, + + // [ payload load 2b @ transport header + 2 => reg 1 ] + &expr.Payload{ + DestRegister: 1, + Base: expr.PayloadBaseTransportHeader, + Offset: 2, // TODO + Len: 2, // TODO + }, + // [ cmp eq reg 1 0x0000e60f ] + &expr.Cmp{ + Op: expr.CmpOpEq, + Register: 1, + Data: binaryutil.BigEndian.PutUint16(4070), + }, + + // [ immediate reg 1 0x0217a8c0 ] + &expr.Immediate{ + Register: 1, + Data: net.ParseIP("192.168.23.2").To4(), + }, + // [ immediate reg 2 0x0000f00f ] + &expr.Immediate{ + Register: 2, + Data: binaryutil.BigEndian.PutUint16(4080), + }, + // [ nat dnat ip addr_min reg 1 addr_max reg 0 proto_min reg 2 proto_max reg 0 ] + &expr.NAT{ + Type: expr.NATTypeDestNAT, + Family: unix.NFPROTO_IPV4, + RegAddrMin: 1, + RegProtoMin: 2, + }, + }, + }) + if err := c.Flush(); err != nil { t.Fatal(err) }