From 0152ef360140c341a3e01b447e4a55ccdc7f5bca Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 5 Jun 2018 08:51:51 +0200 Subject: [PATCH] netconfig: implement port forwardings --- integrationnetconfig_test.go | 20 +++++++ internal/netconfig/netconfig.go | 99 ++++++++++++++++++++++++++++++++- 2 files changed, 116 insertions(+), 3 deletions(-) diff --git a/integrationnetconfig_test.go b/integrationnetconfig_test.go index 02a884a..2bcc9b3 100644 --- a/integrationnetconfig_test.go +++ b/integrationnetconfig_test.go @@ -30,6 +30,23 @@ const goldenInterfaces = ` } ` +const goldenPortForwardings = ` +{ + "forwardings":[ + { + "port": 8080, + "dest_addr": "192.168.42.23", + "dest_port": 9999 + }, + { + "port": 8022, + "dest_addr": "192.168.42.99", + "dest_port": 22 + } + ] +} +` + const goldenDhcp4 = ` { "valid_until":"2018-05-18T23:46:04.429895261+02:00", @@ -70,6 +87,7 @@ func TestNetconfig(t *testing.T) { {"dhcp4/wire/lease.json", goldenDhcp4}, {"dhcp6/wire/lease.json", goldenDhcp6}, {"interfaces.json", goldenInterfaces}, + {"portforwardings.json", goldenPortForwardings}, } { if err := os.MkdirAll(filepath.Join(tmp, filepath.Dir(golden.filename)), 0755); err != nil { t.Fatal(err) @@ -183,6 +201,8 @@ func TestNetconfig(t *testing.T) { `table ip nat {`, ` chain prerouting {`, ` type nat hook prerouting priority 0; policy accept;`, + ` iifname "uplink0" tcp dport 8022 dnat to 192.168.42.99:ssh`, + ` iifname "uplink0" tcp dport http-alt dnat to 192.168.42.23:9999`, ` }`, ``, ` chain postrouting {`, diff --git a/internal/netconfig/netconfig.go b/internal/netconfig/netconfig.go index 348a729..4875f0e 100644 --- a/internal/netconfig/netconfig.go +++ b/internal/netconfig/netconfig.go @@ -11,8 +11,10 @@ import ( "strings" "github.com/google/nftables" + "github.com/google/nftables/binaryutil" "github.com/google/nftables/expr" "github.com/vishvananda/netlink" + "golang.org/x/sys/unix" "router7/internal/dhcp4" "router7/internal/dhcp6" @@ -261,13 +263,100 @@ func applyInterfaces(dir, root string) error { return nil } -func applyFirewall() error { func ifname(n string) []byte { b := make([]byte, 16) copy(b, []byte(n+"\x00")) return b } +func portForwardExpr(port uint16, dest net.IP, dport uint16) []expr.Any { + return []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{0x06}, /* 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(port), + }, + + // [ immediate reg 1 0x0217a8c0 ] + &expr.Immediate{ + Register: 1, + Data: dest.To4(), + }, + // [ immediate reg 2 0x0000f00f ] + &expr.Immediate{ + Register: 2, + Data: binaryutil.BigEndian.PutUint16(dport), + }, + // [ 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, + }, + } +} + +type portForwarding struct { + Port uint16 `json:"port"` // e.g. 8080 + DestAddr string `json:"dest_addr"` // e.g. 192.168.42.2 + DestPort uint16 `json:"dest_port"` // e.g. 80 +} + +type portForwardings struct { + Forwardings []portForwarding `json:"forwardings"` +} + +func applyPortForwardings(dir string, c *nftables.Conn, nat *nftables.Table, prerouting *nftables.Chain) error { + b, err := ioutil.ReadFile(filepath.Join(dir, "portforwardings.json")) + if err != nil { + if os.IsNotExist(err) { + return nil + } + return err + } + var cfg portForwardings + if err := json.Unmarshal(b, &cfg); err != nil { + return err + } + + for _, fw := range cfg.Forwardings { + c.AddRule(&nftables.Rule{ + Table: nat, + Chain: prerouting, + Exprs: portForwardExpr(fw.Port, net.ParseIP(fw.DestAddr), fw.DestPort), + }) + } + return nil +} + +func applyFirewall(dir string) error { c := &nftables.Conn{} // TODO: currently, each iteration adds a nftables.Rule — clear before? @@ -277,7 +366,7 @@ func ifname(n string) []byte { Name: "nat", }) - c.AddChain(&nftables.Chain{ + prerouting := c.AddChain(&nftables.Chain{ Name: "prerouting", Hooknum: nftables.ChainHookPrerouting, Priority: nftables.ChainPriorityFilter, @@ -310,6 +399,10 @@ func ifname(n string) []byte { }, }) + if err := applyPortForwardings(dir, c, nat, prerouting); err != nil { + return err + } + return c.Flush() } @@ -359,7 +452,7 @@ func Apply(dir, root string) error { } } - if err := applyFirewall(); err != nil { + if err := applyFirewall(dir); err != nil { return fmt.Errorf("firewall: %v", err) }