159 lines
6 KiB
Go
159 lines
6 KiB
Go
package tunnel
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"fmt"
|
|
"net"
|
|
|
|
"github.com/google/nftables"
|
|
"github.com/google/nftables/expr"
|
|
"github.com/vishvananda/netlink"
|
|
"github.com/vishvananda/netlink/nl"
|
|
"golang.org/x/sys/unix"
|
|
"quorate.dev/services/short/config"
|
|
)
|
|
|
|
func forwarding(link netlink.Link) error {
|
|
req := nl.NewNetlinkRequest(unix.RTM_NEWLINK, unix.NLM_F_ACK)
|
|
info := nl.NewIfInfomsg(unix.AF_UNSPEC)
|
|
info.Index = int32(link.Attrs().Index)
|
|
req.AddData(info)
|
|
af := nl.NewRtAttr(unix.IFLA_AF_SPEC|unix.NLA_F_NESTED, nil)
|
|
inet := nl.NewRtAttrChild(af, unix.AF_INET|unix.NLA_F_NESTED, nil)
|
|
conf := nl.NewRtAttrChild(inet, unix.IFLA_INET_CONF|unix.NLA_F_NESTED, nil)
|
|
// Linux UAPI IPV4_DEVCONF_FORWARDING is the first IPv4 interface setting.
|
|
nl.NewRtAttrChild(conf, 1, nl.Uint32Attr(1))
|
|
req.AddData(af)
|
|
_, err := req.Execute(unix.NETLINK_ROUTE, 0)
|
|
return err
|
|
}
|
|
|
|
func network(p config.Path) error {
|
|
if err := p.Validate(); err != nil {
|
|
return err
|
|
}
|
|
links := make([]netlink.Link, 2)
|
|
for index := range links {
|
|
name := fmt.Sprintf("ipsec%d", index)
|
|
link, err := netlink.LinkByName(name)
|
|
if _, ok := err.(netlink.LinkNotFoundError); ok {
|
|
link = &netlink.Xfrmi{LinkAttrs: netlink.LinkAttrs{Name: name, MTU: 1280}, Ifid: uint32(42 + index)}
|
|
if err = netlink.LinkAdd(link); err != nil {
|
|
return err
|
|
}
|
|
} else if err != nil {
|
|
return err
|
|
}
|
|
if err = netlink.LinkSetUp(link); err != nil {
|
|
return err
|
|
}
|
|
links[index] = link
|
|
}
|
|
lo, err := netlink.LinkByName("lo")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
addr, err := netlink.ParseAddr(p.Address + "/32")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err = netlink.AddrReplace(lo, addr); err != nil {
|
|
return err
|
|
}
|
|
eth, err := netlink.LinkByName("eth0")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for _, device := range append(links, eth) {
|
|
if err = forwarding(device); err != nil {
|
|
return fmt.Errorf("enable interface forwarding: %w", err)
|
|
}
|
|
}
|
|
return firewall(p)
|
|
}
|
|
|
|
func returnRoute(network string, index int) error {
|
|
link, err := netlink.LinkByName(fmt.Sprintf("ipsec%d", index))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, destination, err := net.ParseCIDR(network)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return netlink.RouteReplace(&netlink.Route{LinkIndex: link.Attrs().Index, Dst: destination})
|
|
}
|
|
|
|
func inputInterface(name string) []expr.Any {
|
|
value := make([]byte, 16)
|
|
copy(value, name)
|
|
return []expr.Any{&expr.Meta{Key: expr.MetaKeyIIFNAME, Register: 1}, &expr.Cmp{Op: expr.CmpOpEq, Register: 1, Data: value}}
|
|
}
|
|
|
|
func clampMSS(name string, key expr.MetaKey) []expr.Any {
|
|
value := make([]byte, 16)
|
|
copy(value, name)
|
|
return []expr.Any{
|
|
&expr.Meta{Key: key, Register: 1}, &expr.Cmp{Op: expr.CmpOpEq, Register: 1, Data: value},
|
|
&expr.Meta{Key: expr.MetaKeyL4PROTO, Register: 1}, &expr.Cmp{Op: expr.CmpOpEq, Register: 1, Data: []byte{unix.IPPROTO_TCP}},
|
|
&expr.Payload{DestRegister: 1, Base: expr.PayloadBaseTransportHeader, Offset: 13, Len: 1},
|
|
&expr.Bitwise{DestRegister: 1, SourceRegister: 1, Len: 1, Mask: []byte{0x02}, Xor: []byte{0}},
|
|
&expr.Cmp{Op: expr.CmpOpEq, Register: 1, Data: []byte{0x02}},
|
|
&expr.Rt{Register: 1, Key: expr.RtTCPMSS},
|
|
&expr.Byteorder{DestRegister: 1, SourceRegister: 1, Op: expr.ByteorderHton, Len: 2, Size: 2},
|
|
&expr.Exthdr{SourceRegister: 1, Type: 2, Offset: 2, Len: 2, Op: expr.ExthdrOpTcpopt},
|
|
}
|
|
}
|
|
|
|
func dest(address string, port uint16) []expr.Any {
|
|
buf := make([]byte, 2)
|
|
binary.BigEndian.PutUint16(buf, port)
|
|
return []expr.Any{
|
|
&expr.Meta{Key: expr.MetaKeyL4PROTO, Register: 1}, &expr.Cmp{Op: expr.CmpOpEq, Register: 1, Data: []byte{unix.IPPROTO_TCP}},
|
|
&expr.Payload{DestRegister: 1, Base: expr.PayloadBaseNetworkHeader, Offset: 16, Len: 4}, &expr.Cmp{Op: expr.CmpOpEq, Register: 1, Data: net.ParseIP(address).To4()},
|
|
&expr.Payload{DestRegister: 1, Base: expr.PayloadBaseTransportHeader, Offset: 2, Len: 2}, &expr.Cmp{Op: expr.CmpOpEq, Register: 1, Data: buf},
|
|
}
|
|
}
|
|
|
|
func firewall(p config.Path) error {
|
|
c := &nftables.Conn{}
|
|
tables, err := c.ListTables()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for _, t := range tables {
|
|
if t.Name == "short" && t.Family == nftables.TableFamilyIPv4 {
|
|
c.DelTable(t)
|
|
}
|
|
}
|
|
t := c.AddTable(&nftables.Table{Name: "short", Family: nftables.TableFamilyIPv4})
|
|
chain := func(name string, kind nftables.ChainType, hook *nftables.ChainHook, priority *nftables.ChainPriority) *nftables.Chain {
|
|
return c.AddChain(&nftables.Chain{Name: name, Table: t, Type: kind, Hooknum: hook, Priority: priority})
|
|
}
|
|
pre := chain("dnat", nftables.ChainTypeNAT, nftables.ChainHookPrerouting, nftables.ChainPriorityNATDest)
|
|
post := chain("snat", nftables.ChainTypeNAT, nftables.ChainHookPostrouting, nftables.ChainPriorityNATSource)
|
|
forward := chain("forward", nftables.ChainTypeFilter, nftables.ChainHookForward, nftables.ChainPriorityFilter)
|
|
input := chain("input", nftables.ChainTypeFilter, nftables.ChainHookInput, nftables.ChainPriorityFilter)
|
|
for index := range 2 {
|
|
device := fmt.Sprintf("ipsec%d", index)
|
|
for _, key := range []expr.MetaKey{expr.MetaKeyIIFNAME, expr.MetaKeyOIFNAME} {
|
|
c.AddRule(&nftables.Rule{Table: t, Chain: forward, Exprs: clampMSS(device, key)})
|
|
}
|
|
for _, port := range p.Ports {
|
|
match := append(inputInterface(device), dest(p.Address, port)...)
|
|
match = append(match, &expr.Immediate{Register: 1, Data: net.ParseIP(p.Gateway).To4()}, &expr.NAT{Type: expr.NATTypeDestNAT, Family: unix.NFPROTO_IPV4, RegAddrMin: 1})
|
|
c.AddRule(&nftables.Rule{Table: t, Chain: pre, Exprs: match})
|
|
match = append(inputInterface(device), dest(p.Gateway, port)...)
|
|
c.AddRule(&nftables.Rule{Table: t, Chain: post, Exprs: append(match, &expr.Masq{})})
|
|
match = append(inputInterface(device), dest(p.Gateway, port)...)
|
|
c.AddRule(&nftables.Rule{Table: t, Chain: forward, Exprs: append(match, &expr.Verdict{Kind: expr.VerdictAccept})})
|
|
}
|
|
for _, chain := range []*nftables.Chain{forward, input} {
|
|
c.AddRule(&nftables.Rule{Table: t, Chain: chain, Exprs: append(inputInterface(device), &expr.Verdict{Kind: expr.VerdictDrop})})
|
|
}
|
|
}
|
|
if err := c.Flush(); err != nil {
|
|
return fmt.Errorf("apply pod forwarding: %w", err)
|
|
}
|
|
return nil
|
|
}
|