// 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_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 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 // // 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\x0a\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 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 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"), } 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: got %x, want %x", idx, got, want) } want = want[1:] } return req, nil }, } 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, }) postrouting := c.AddChain(&nftables.Chain{ Name: "postrouting", Hooknum: nftables.ChainHookPostrouting, Priority: nftables.ChainPriorityNATSource, Table: nat, Type: nftables.ChainTypeNAT, }) c.AddRule(&nftables.Rule{ Table: nat, Chain: postrouting, Exprs: []expr.Any{ // 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: 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) } }