From c5bb71b2cfcbb7c433bbe0fc46eebff3f3802c48 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 17 May 2019 13:57:31 +0200 Subject: [PATCH] implement redir expression (+test) fixes #17 --- expr/redirect.go | 71 ++++++++++++++++++++++++++++++++ nftables_test.go | 105 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 176 insertions(+) create mode 100644 expr/redirect.go diff --git a/expr/redirect.go b/expr/redirect.go new file mode 100644 index 0000000..506047a --- /dev/null +++ b/expr/redirect.go @@ -0,0 +1,71 @@ +// 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 ( + "encoding/binary" + + "github.com/google/nftables/binaryutil" + "github.com/mdlayher/netlink" + "golang.org/x/sys/unix" +) + +type Redir struct { + RegisterProtoMin uint32 + RegisterProtoMax uint32 + Flags uint32 +} + +func (e *Redir) marshal() ([]byte, error) { + var attrs []netlink.Attribute + if e.RegisterProtoMin > 0 { + attrs = append(attrs, netlink.Attribute{Type: unix.NFTA_REDIR_REG_PROTO_MIN, Data: binaryutil.BigEndian.PutUint32(e.RegisterProtoMin)}) + } + if e.RegisterProtoMax > 0 { + attrs = append(attrs, netlink.Attribute{Type: unix.NFTA_REDIR_REG_PROTO_MAX, Data: binaryutil.BigEndian.PutUint32(e.RegisterProtoMax)}) + } + if e.Flags > 0 { + attrs = append(attrs, netlink.Attribute{Type: unix.NFTA_REDIR_FLAGS, Data: binaryutil.BigEndian.PutUint32(e.Flags)}) + } + + data, err := netlink.MarshalAttributes(attrs) + if err != nil { + return nil, err + } + + return netlink.MarshalAttributes([]netlink.Attribute{ + {Type: unix.NFTA_EXPR_NAME, Data: []byte("redir\x00")}, + {Type: unix.NLA_F_NESTED | unix.NFTA_EXPR_DATA, Data: data}, + }) +} + +func (e *Redir) unmarshal(data []byte) error { + ad, err := netlink.NewAttributeDecoder(data) + if err != nil { + return err + } + ad.ByteOrder = binary.BigEndian + for ad.Next() { + switch ad.Type() { + case unix.NFTA_REDIR_REG_PROTO_MIN: + e.RegisterProtoMin = ad.Uint32() + case unix.NFTA_REDIR_REG_PROTO_MAX: + e.RegisterProtoMax = ad.Uint32() + case unix.NFTA_REDIR_FLAGS: + e.Flags = ad.Uint32() + } + } + return ad.Err() +} diff --git a/nftables_test.go b/nftables_test.go index 2cde4ef..2153f1b 100644 --- a/nftables_test.go +++ b/nftables_test.go @@ -1240,3 +1240,108 @@ func TestGetRuleLookupVerdictImmediate(t *testing.T) { t.Errorf("verdict expr = %+v, wanted %+v", imm, want) } } + +func TestConfigureNATRedirect(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 + // + // The nft(8) command sequence was taken from: + // https://wiki.nftables.org/wiki-nftables/index.php/Performing_Network_Address_Translation_(NAT) + want := [][]byte{ + // batch begin + []byte("\x00\x00\x00\x0a"), + // nft flush ruleset + []byte("\x00\x00\x00\x00"), + // nft add table ip nat + []byte("\x02\x00\x00\x00\x08\x00\x01\x00\x6e\x61\x74\x00\x08\x00\x02\x00\x00\x00\x00\x00"), + // nft add chain nat prerouting '{' type nat hook prerouting priority 0 \; '}' + []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 rule nat prerouting tcp dport 22 redirect to 2222 + []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\xfc\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\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\x00\x16\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\x06\x00\x01\x00\x08\xae\x00\x00\x1c\x00\x01\x80\x0a\x00\x01\x00\x72\x65\x64\x69\x72\x00\x00\x00\x0c\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01"), + // batch end + []byte("\x00\x00\x00\x0a"), + } + + c := &nftables.Conn{ + TestDial: func(req []netlink.Message) ([]netlink.Message, error) { + for idx, msg := range req { + b, err := msg.MarshalBinary() + if err != nil { + t.Fatal(err) + } + if len(b) < 16 { + continue + } + b = b[16:] + if len(want) == 0 { + t.Errorf("no want entry for message %d: %x", idx, b) + continue + } + if got, want := b, want[0]; !bytes.Equal(got, want) { + t.Errorf("message %d: %s", idx, linediff(nfdump(got), nfdump(want))) + } + want = want[1:] + } + return req, nil + }, + } + + c.FlushRuleset() + + nat := c.AddTable(&nftables.Table{ + Family: nftables.TableFamilyIPv4, + Name: "nat", + }) + + prerouting := c.AddChain(&nftables.Chain{ + Name: "prerouting", + Hooknum: nftables.ChainHookPrerouting, + Priority: nftables.ChainPriorityFilter, + Table: nat, + Type: nftables.ChainTypeNAT, + }) + + c.AddRule(&nftables.Rule{ + Table: nat, + Chain: prerouting, + Exprs: []expr.Any{ + // [ meta load l4proto => reg 1 ] + &expr.Meta{Key: expr.MetaKeyL4PROTO, Register: 1}, + // [ cmp eq reg 1 0x00000006 ] + &expr.Cmp{ + Op: expr.CmpOpEq, + Register: 1, + Data: []byte{unix.IPPROTO_TCP}, + }, + + // [ payload load 2b @ transport header + 2 => reg 1 ] + &expr.Payload{ + DestRegister: 1, + Base: expr.PayloadBaseTransportHeader, + Offset: 2, // TODO + Len: 2, // TODO + }, + // [ cmp eq reg 1 0x00001600 ] + &expr.Cmp{ + Op: expr.CmpOpEq, + Register: 1, + Data: []byte{0x00, 0x16}, + }, + + // [ immediate reg 1 0x0000ae08 ] + &expr.Immediate{ + Register: 1, + Data: binaryutil.BigEndian.PutUint16(2222), + }, + + // [ redir proto_min reg 1 ] + &expr.Redir{ + RegisterProtoMin: 1, + }, + }, + }) + + if err := c.Flush(); err != nil { + t.Fatal(err) + } +}