netconfig: implement support for port ranges

This commit is contained in:
Michael Stapelberg 2018-06-14 22:25:39 +02:00
parent 390c2af7db
commit a1c4d60666
2 changed files with 111 additions and 31 deletions

View File

@ -35,20 +35,20 @@ const goldenPortForwardings = `
{ {
"forwardings":[ "forwardings":[
{ {
"port": 8080, "port": "8080",
"dest_addr": "192.168.42.23", "dest_addr": "192.168.42.23",
"dest_port": 9999 "dest_port": "9999"
}, },
{ {
"port": 8022, "port": "8040-8060",
"dest_addr": "192.168.42.99", "dest_addr": "192.168.42.99",
"dest_port": 22 "dest_port": "8040-8060"
}, },
{ {
"proto": "udp", "proto": "udp",
"port": 53, "port": "53",
"dest_addr": "192.168.42.99", "dest_addr": "192.168.42.99",
"dest_port": 53 "dest_port": "53"
} }
] ]
} }
@ -214,7 +214,7 @@ func TestNetconfig(t *testing.T) {
` chain prerouting {`, ` chain prerouting {`,
` type nat hook prerouting priority 0; policy accept;`, ` type nat hook prerouting priority 0; policy accept;`,
` iifname "uplink0" udp dport domain dnat to 192.168.42.99:domain`, ` 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`, ` iifname "uplink0" tcp dport http-alt dnat to 192.168.42.23:9999`,
` }`, ` }`,
``, ``,

View File

@ -7,6 +7,7 @@ import (
"net" "net"
"os" "os"
"path/filepath" "path/filepath"
"regexp"
"strconv" "strconv"
"strings" "strings"
@ -273,8 +274,34 @@ func ifname(n string) []byte {
return b return b
} }
func portForwardExpr(proto uint8, port uint16, dest net.IP, dport uint16) []expr.Any { func portForwardExpr(proto uint8, portMin, portMax uint16, dest net.IP, dportMin, dportMax uint16) []expr.Any {
return []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 ] // [ meta load iifname => reg 1 ]
&expr.Meta{Key: expr.MetaKeyIIFNAME, Register: 1}, &expr.Meta{Key: expr.MetaKeyIIFNAME, Register: 1},
// [ cmp eq reg 1 0x696c7075 0x00306b6e 0x00000000 0x00000000 ] // [ 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 Offset: 2, // TODO
Len: 2, // TODO Len: 2, // TODO
}, },
// [ cmp eq reg 1 0x0000e60f ] }
&expr.Cmp{ ex = append(ex, cmp...)
Op: expr.CmpOpEq, ex = append(ex,
Register: 1,
Data: binaryutil.BigEndian.PutUint16(port),
},
// [ immediate reg 1 0x0217a8c0 ] // [ immediate reg 1 0x0217a8c0 ]
&expr.Immediate{ &expr.Immediate{
Register: 1, Register: 1,
Data: dest.To4(), Data: dest.To4(),
}, },
// [ immediate reg 2 0x0000f00f ] )
&expr.Immediate{ if dportMin == dportMax {
Register: 2, ex = append(ex,
Data: binaryutil.BigEndian.PutUint16(dport), // [ immediate reg 2 0x0000f00f ]
}, &expr.Immediate{
// [ nat dnat ip addr_min reg 1 addr_max reg 0 proto_min reg 2 proto_max reg 0 ] Register: 2,
&expr.NAT{ Data: binaryutil.BigEndian.PutUint16(dportMin),
Type: expr.NATTypeDestNAT, },
Family: unix.NFPROTO_IPV4, // [ nat dnat ip addr_min reg 1 addr_max reg 0 proto_min reg 2 proto_max reg 0 ]
RegAddrMin: 1, &expr.NAT{
RegProtoMin: 2, 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 { type portForwarding struct {
Proto string `json:"proto"` // e.g. “tcp” (or “tcp,udp”) 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” 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 { type portForwardings struct {
Forwardings []portForwarding `json:"forwardings"` 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 { func applyPortForwardings(dir string, c *nftables.Conn, nat *nftables.Table, prerouting *nftables.Chain) error {
b, err := ioutil.ReadFile(filepath.Join(dir, "portforwardings.json")) b, err := ioutil.ReadFile(filepath.Join(dir, "portforwardings.json"))
if err != nil { if err != nil {
@ -362,10 +432,20 @@ func applyPortForwardings(dir string, c *nftables.Conn, nat *nftables.Table, pre
default: default:
return fmt.Errorf(`unknown proto %q, expected "tcp" or "udp"`, proto) 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{ c.AddRule(&nftables.Rule{
Table: nat, Table: nat,
Chain: prerouting, Chain: prerouting,
Exprs: portForwardExpr(p, fw.Port, net.ParseIP(fw.DestAddr), fw.DestPort), Exprs: portForwardExpr(p, min, max, net.ParseIP(fw.DestAddr), dmin, dmax),
}) })
} }
} }