From a1c4d60666e771b6420dfcec0593f7e583b61b01 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Thu, 14 Jun 2018 22:25:39 +0200 Subject: [PATCH] netconfig: implement support for port ranges --- integrationnetconfig_test.go | 14 ++-- internal/netconfig/netconfig.go | 128 ++++++++++++++++++++++++++------ 2 files changed, 111 insertions(+), 31 deletions(-) diff --git a/integrationnetconfig_test.go b/integrationnetconfig_test.go index b4a75e1..bc52303 100644 --- a/integrationnetconfig_test.go +++ b/integrationnetconfig_test.go @@ -35,20 +35,20 @@ const goldenPortForwardings = ` { "forwardings":[ { - "port": 8080, + "port": "8080", "dest_addr": "192.168.42.23", - "dest_port": 9999 + "dest_port": "9999" }, { - "port": 8022, + "port": "8040-8060", "dest_addr": "192.168.42.99", - "dest_port": 22 + "dest_port": "8040-8060" }, { "proto": "udp", - "port": 53, + "port": "53", "dest_addr": "192.168.42.99", - "dest_port": 53 + "dest_port": "53" } ] } @@ -214,7 +214,7 @@ func TestNetconfig(t *testing.T) { ` chain prerouting {`, ` type nat hook prerouting priority 0; policy accept;`, ` iifname "uplink0" udp dport domain dnat to 192.168.42.99:domain`, - ` iifname "uplink0" tcp dport 8022 dnat to 192.168.42.99:ssh`, + ` iifname "uplink0" tcp dport 8040-8060 dnat to 192.168.42.99:8040-8060`, ` iifname "uplink0" tcp dport http-alt dnat to 192.168.42.23:9999`, ` }`, ``, diff --git a/internal/netconfig/netconfig.go b/internal/netconfig/netconfig.go index 8174e30..9dffb47 100644 --- a/internal/netconfig/netconfig.go +++ b/internal/netconfig/netconfig.go @@ -7,6 +7,7 @@ import ( "net" "os" "path/filepath" + "regexp" "strconv" "strings" @@ -273,8 +274,34 @@ func ifname(n string) []byte { return b } -func portForwardExpr(proto uint8, port uint16, dest net.IP, dport uint16) []expr.Any { - return []expr.Any{ +func portForwardExpr(proto uint8, portMin, portMax uint16, dest net.IP, dportMin, dportMax uint16) []expr.Any { + var cmp []expr.Any + if portMin == portMax { + cmp = []expr.Any{ + // [ cmp eq reg 1 0x0000e60f ] + &expr.Cmp{ + Op: expr.CmpOpEq, + Register: 1, + Data: binaryutil.BigEndian.PutUint16(portMin), + }, + } + } else { + cmp = []expr.Any{ + // [ cmp gte reg 1 0x0000e60f ] + &expr.Cmp{ + Op: expr.CmpOpGte, + Register: 1, + Data: binaryutil.BigEndian.PutUint16(portMin), + }, + // [ cmp lte reg 1 0x0000fa0f ] + &expr.Cmp{ + Op: expr.CmpOpLte, + Register: 1, + Data: binaryutil.BigEndian.PutUint16(portMax), + }, + } + } + ex := []expr.Any{ // [ meta load iifname => reg 1 ] &expr.Meta{Key: expr.MetaKeyIIFNAME, Register: 1}, // [ cmp eq reg 1 0x696c7075 0x00306b6e 0x00000000 0x00000000 ] @@ -300,44 +327,87 @@ func portForwardExpr(proto uint8, port uint16, dest net.IP, dport uint16) []expr Offset: 2, // TODO Len: 2, // TODO }, - // [ cmp eq reg 1 0x0000e60f ] - &expr.Cmp{ - Op: expr.CmpOpEq, - Register: 1, - Data: binaryutil.BigEndian.PutUint16(port), - }, - + } + ex = append(ex, cmp...) + ex = append(ex, // [ 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, - }, + ) + if dportMin == dportMax { + ex = append(ex, + // [ immediate reg 2 0x0000f00f ] + &expr.Immediate{ + Register: 2, + Data: binaryutil.BigEndian.PutUint16(dportMin), + }, + // [ 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, + }, + ) + } else { + ex = append(ex, + // [ immediate reg 2 0x0000e60f ] + &expr.Immediate{ + Register: 2, + Data: binaryutil.BigEndian.PutUint16(dportMin), + }, + // [ immediate reg 3 0x0000fa0f ] + &expr.Immediate{ + Register: 3, + Data: binaryutil.BigEndian.PutUint16(dportMax), + }, + // [ nat dnat ip addr_min reg 1 addr_max reg 0 proto_min reg 2 proto_max reg 3 ] + &expr.NAT{ + Type: expr.NATTypeDestNAT, + Family: unix.NFPROTO_IPV4, + RegAddrMin: 1, + RegProtoMin: 2, + RegProtoMax: 3, + }, + ) } + return ex } type portForwarding struct { Proto string `json:"proto"` // e.g. “tcp” (or “tcp,udp”) - Port uint16 `json:"port"` // e.g. “8080” + Port string `json:"port"` // e.g. “8080” (or “8080-8090”) DestAddr string `json:"dest_addr"` // e.g. “192.168.42.2” - DestPort uint16 `json:"dest_port"` // e.g. “80” + DestPort string `json:"dest_port"` // e.g. “80” (or “80-90”) } type portForwardings struct { Forwardings []portForwarding `json:"forwardings"` } +var rangeRe = regexp.MustCompile(`^([0-9]+)(?:-([0-9]+))?$`) + +func parsePort(p string) (min uint16, max uint16, _ error) { + matches := rangeRe.FindStringSubmatch(p) + if len(matches) == 0 { + return 0, 0, fmt.Errorf("malformed port %q, expected port number (e.g. 8080) or port range (e.g. 8080-8090)", p) + } + min64, err := strconv.ParseUint(matches[1], 0, 16) + if err != nil { + return 0, 0, fmt.Errorf("ParseInt(%q): %v", matches[1], err) + } + max64 := min64 + if matches[2] != "" { + max64, err = strconv.ParseUint(matches[2], 0, 16) + if err != nil { + return 0, 0, fmt.Errorf("ParseInt(%q): %v", matches[2], err) + } + } + return uint16(min64), uint16(max64), nil +} + 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 { @@ -362,10 +432,20 @@ func applyPortForwardings(dir string, c *nftables.Conn, nat *nftables.Table, pre default: return fmt.Errorf(`unknown proto %q, expected "tcp" or "udp"`, proto) } + + min, max, err := parsePort(fw.Port) + if err != nil { + return err + } + dmin, dmax, err := parsePort(fw.DestPort) + if err != nil { + return err + } + c.AddRule(&nftables.Rule{ Table: nat, Chain: prerouting, - Exprs: portForwardExpr(p, fw.Port, net.ParseIP(fw.DestAddr), fw.DestPort), + Exprs: portForwardExpr(p, min, max, net.ParseIP(fw.DestAddr), dmin, dmax), }) } }