netconfig: implement port forwardings

This commit is contained in:
Michael Stapelberg 2018-06-05 08:51:51 +02:00
parent 320ca04a2f
commit 0152ef3601
2 changed files with 116 additions and 3 deletions

View File

@ -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 = ` const goldenDhcp4 = `
{ {
"valid_until":"2018-05-18T23:46:04.429895261+02:00", "valid_until":"2018-05-18T23:46:04.429895261+02:00",
@ -70,6 +87,7 @@ func TestNetconfig(t *testing.T) {
{"dhcp4/wire/lease.json", goldenDhcp4}, {"dhcp4/wire/lease.json", goldenDhcp4},
{"dhcp6/wire/lease.json", goldenDhcp6}, {"dhcp6/wire/lease.json", goldenDhcp6},
{"interfaces.json", goldenInterfaces}, {"interfaces.json", goldenInterfaces},
{"portforwardings.json", goldenPortForwardings},
} { } {
if err := os.MkdirAll(filepath.Join(tmp, filepath.Dir(golden.filename)), 0755); err != nil { if err := os.MkdirAll(filepath.Join(tmp, filepath.Dir(golden.filename)), 0755); err != nil {
t.Fatal(err) t.Fatal(err)
@ -183,6 +201,8 @@ func TestNetconfig(t *testing.T) {
`table ip nat {`, `table ip nat {`,
` chain prerouting {`, ` chain prerouting {`,
` type nat hook prerouting priority 0; policy accept;`, ` 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 {`, ` chain postrouting {`,

View File

@ -11,8 +11,10 @@ import (
"strings" "strings"
"github.com/google/nftables" "github.com/google/nftables"
"github.com/google/nftables/binaryutil"
"github.com/google/nftables/expr" "github.com/google/nftables/expr"
"github.com/vishvananda/netlink" "github.com/vishvananda/netlink"
"golang.org/x/sys/unix"
"router7/internal/dhcp4" "router7/internal/dhcp4"
"router7/internal/dhcp6" "router7/internal/dhcp6"
@ -261,13 +263,100 @@ func applyInterfaces(dir, root string) error {
return nil return nil
} }
func applyFirewall() error {
func ifname(n string) []byte { func ifname(n string) []byte {
b := make([]byte, 16) b := make([]byte, 16)
copy(b, []byte(n+"\x00")) copy(b, []byte(n+"\x00"))
return b 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{} c := &nftables.Conn{}
// TODO: currently, each iteration adds a nftables.Rule — clear before? // TODO: currently, each iteration adds a nftables.Rule — clear before?
@ -277,7 +366,7 @@ func ifname(n string) []byte {
Name: "nat", Name: "nat",
}) })
c.AddChain(&nftables.Chain{ prerouting := c.AddChain(&nftables.Chain{
Name: "prerouting", Name: "prerouting",
Hooknum: nftables.ChainHookPrerouting, Hooknum: nftables.ChainHookPrerouting,
Priority: nftables.ChainPriorityFilter, 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() 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) return fmt.Errorf("firewall: %v", err)
} }