diff --git a/internal/cmdtmpl/command.go b/internal/cmdtmpl/command.go index 88694cc..6039870 100644 --- a/internal/cmdtmpl/command.go +++ b/internal/cmdtmpl/command.go @@ -47,156 +47,10 @@ func (cl *CommandList) executeTemplate(tmpl string, data any) (string, error) { return s, nil } -// SplitRoutingDefaultTemplate is the default template for Split Routing. -const SplitRoutingDefaultTemplate = ` -{{- define "SplitRoutingRules"}} -table inet oc-daemon-routing { - # set for ipv4 excludes - set excludes4 { - type ipv4_addr - flags interval - {{if .VPNConfig.Gateway.Is4}} - elements = { {{.VPNConfig.Gateway}}/32 } - {{end}} - } - - # set for ipv6 excludes - set excludes6 { - type ipv6_addr - flags interval - {{if .VPNConfig.Gateway.Is6}} - elements = { {{.VPNConfig.Gateway}}/128 } - {{end}} - } - - chain preraw { - type filter hook prerouting priority raw; policy accept; - - # add drop rules for non-local traffic from other devices to - # tunnel network addresses here - {{if .VPNConfig.IPv4.IsValid}} - iifname != {{.VPNConfig.Device.Name}} ip daddr {{.VPNConfig.IPv4}} fib saddr type != local counter drop - {{end}} - {{if .VPNConfig.IPv6.IsValid}} - iifname != {{.VPNConfig.Device.Name}} ip6 daddr {{.VPNConfig.IPv6}} fib saddr type != local counter drop - {{end}} - } - - chain splitrouting { - # restore mark from conntracking - ct mark != 0 meta mark set ct mark counter - meta mark != 0 counter accept - - # mark packets in exclude sets - ip daddr @excludes4 counter meta mark set {{.SplitRouting.FirewallMark}} - ip6 daddr @excludes6 counter meta mark set {{.SplitRouting.FirewallMark}} - - # save mark in conntraction - ct mark set meta mark counter - } - - chain premangle { - type filter hook prerouting priority mangle; policy accept; - - # handle split routing - counter jump splitrouting - } - - chain output { - type route hook output priority mangle; policy accept; - - # handle split routing - counter jump splitrouting - } - - chain postmangle { - type filter hook postrouting priority mangle; policy accept; - - # save mark in conntracking - meta mark {{.SplitRouting.FirewallMark}} ct mark set meta mark counter - } - - chain postrouting { - type nat hook postrouting priority srcnat; policy accept; - - # masquerare tunnel/exclude traffic to make sure the source IP - # matches the outgoing interface - ct mark {{.SplitRouting.FirewallMark}} counter masquerade - } - - chain rejectipversion { - # used to reject unsupported ip version on the tunnel device - - # make sure exclude traffic is not filtered - ct mark {{.SplitRouting.FirewallMark}} counter accept - - # use tcp reset and icmp admin prohibited - meta l4proto tcp counter reject with tcp reset - counter reject with icmpx admin-prohibited - } - - chain rejectforward { - type filter hook forward priority filter; policy accept; - - # reject unsupported ip versions when forwarding packets, - # add matching jump rule to rejectipversion if necessary - {{if .VPNConfig.IPv4.IsValid}} - meta oifname {{.VPNConfig.Device.Name}} meta nfproto ipv6 counter jump rejectipversion - {{end}} - {{if .VPNConfig.IPv6.IsValid}} - meta oifname {{.VPNConfig.Device.Name}} meta nfproto ipv4 counter jump rejectipversion - {{end}} - } - - chain rejectoutput { - type filter hook output priority filter; policy accept; - - # reject unsupported ip versions when sending packets, - # add matching jump rule to rejectipversion if necessary - {{if .VPNConfig.IPv4.IsValid}} - meta oifname {{.VPNConfig.Device.Name}} meta nfproto ipv6 counter jump rejectipversion - {{end}} - {{if .VPNConfig.IPv6.IsValid}} - meta oifname {{.VPNConfig.Device.Name}} meta nfproto ipv4 counter jump rejectipversion - {{end}} - } -} -{{end -}} -` - // getCommandListSplitRouting returns the command list identified by name for SplitRouting. func getCommandListSplitRouting(name string) *CommandList { var cl *CommandList switch name { - case "SplitRoutingSetupRouting": - // Setup Routing - cl = &CommandList{ - Name: name, - Commands: []*Command{ - {Line: "{{.Executables.Nft}} -f -", Stdin: `{{template "SplitRoutingRules" .}}`}, - {Line: "{{.Executables.IP}} -4 route add 0.0.0.0/0 dev {{.VPNConfig.Device.Name}} table {{.SplitRouting.RoutingTable}}"}, - {Line: "{{.Executables.IP}} -4 rule add iif {{.VPNConfig.Device.Name}} table main pref {{.SplitRouting.RulePriority1}}"}, - {Line: "{{.Executables.IP}} -4 rule add not fwmark {{.SplitRouting.FirewallMark}} table {{.SplitRouting.RoutingTable}} pref {{.SplitRouting.RulePriority2}}"}, - {Line: "{{.Executables.Sysctl}} -q net.ipv4.conf.all.src_valid_mark=1"}, - {Line: "{{.Executables.IP}} -6 route add ::/0 dev {{.VPNConfig.Device.Name}} table {{.SplitRouting.RoutingTable}}"}, - {Line: "{{.Executables.IP}} -6 rule add iif {{.VPNConfig.Device.Name}} table main pref {{.SplitRouting.RulePriority1}}"}, - {Line: "{{.Executables.IP}} -6 rule add not fwmark {{.SplitRouting.FirewallMark}} table {{.SplitRouting.RoutingTable}} pref {{.SplitRouting.RulePriority2}}"}, - }, - defaultTemplate: SplitRoutingDefaultTemplate, - } - case "SplitRoutingTeardownRouting": - // Teardown Routing - cl = &CommandList{ - Name: name, - Commands: []*Command{ - {Line: "{{.Executables.IP}} -4 rule delete table {{.SplitRouting.RoutingTable}}"}, - {Line: "{{.Executables.IP}} -4 rule delete iif {{.VPNConfig.Device.Name}} table main"}, - {Line: "{{.Executables.IP}} -6 rule delete table {{.SplitRouting.RoutingTable}}"}, - {Line: "{{.Executables.IP}} -6 rule delete iif {{.VPNConfig.Device.Name}} table main"}, - {Line: "{{.Executables.Nft}} -f - delete table inet oc-daemon-routing"}, - }, - defaultTemplate: SplitRoutingDefaultTemplate, - } case "SplitRoutingSetExcludes": // Set Excludes cl = &CommandList{ @@ -215,7 +69,7 @@ add element inet oc-daemon-routing excludes4 { {{.}} } {{end -}} {{end}}`}, }, - defaultTemplate: SplitRoutingDefaultTemplate, + defaultTemplate: VPNSetupDefaultTemplate, } default: return nil @@ -471,33 +325,182 @@ func getCommandListTrafPol(name string) *CommandList { return cl } +// VPNSetupDefaultTemplate is the default template for VPNSetup. +const VPNSetupDefaultTemplate = ` +{{- define "SplitRoutingRules"}} +table inet oc-daemon-routing { + # set for ipv4 excludes + set excludes4 { + type ipv4_addr + flags interval + {{if .VPNConfig.Gateway.Is4}} + elements = { {{.VPNConfig.Gateway}}/32 } + {{end}} + } + + # set for ipv6 excludes + set excludes6 { + type ipv6_addr + flags interval + {{if .VPNConfig.Gateway.Is6}} + elements = { {{.VPNConfig.Gateway}}/128 } + {{end}} + } + + chain preraw { + type filter hook prerouting priority raw; policy accept; + + # add drop rules for non-local traffic from other devices to + # tunnel network addresses here + {{if .VPNConfig.IPv4.IsValid}} + iifname != {{.VPNConfig.Device.Name}} ip daddr {{.VPNConfig.IPv4}} fib saddr type != local counter drop + {{end}} + {{if .VPNConfig.IPv6.IsValid}} + iifname != {{.VPNConfig.Device.Name}} ip6 daddr {{.VPNConfig.IPv6}} fib saddr type != local counter drop + {{end}} + } + + chain splitrouting { + # restore mark from conntracking + ct mark != 0 meta mark set ct mark counter + meta mark != 0 counter accept + + # mark packets in exclude sets + ip daddr @excludes4 counter meta mark set {{.SplitRouting.FirewallMark}} + ip6 daddr @excludes6 counter meta mark set {{.SplitRouting.FirewallMark}} + + # save mark in conntraction + ct mark set meta mark counter + } + + chain premangle { + type filter hook prerouting priority mangle; policy accept; + + # handle split routing + counter jump splitrouting + } + + chain output { + type route hook output priority mangle; policy accept; + + # handle split routing + counter jump splitrouting + } + + chain postmangle { + type filter hook postrouting priority mangle; policy accept; + + # save mark in conntracking + meta mark {{.SplitRouting.FirewallMark}} ct mark set meta mark counter + } + + chain postrouting { + type nat hook postrouting priority srcnat; policy accept; + + # masquerare tunnel/exclude traffic to make sure the source IP + # matches the outgoing interface + ct mark {{.SplitRouting.FirewallMark}} counter masquerade + } + + chain rejectipversion { + # used to reject unsupported ip version on the tunnel device + + # make sure exclude traffic is not filtered + ct mark {{.SplitRouting.FirewallMark}} counter accept + + # use tcp reset and icmp admin prohibited + meta l4proto tcp counter reject with tcp reset + counter reject with icmpx admin-prohibited + } + + chain rejectforward { + type filter hook forward priority filter; policy accept; + + # reject unsupported ip versions when forwarding packets, + # add matching jump rule to rejectipversion if necessary + {{if .VPNConfig.IPv4.IsValid}} + meta oifname {{.VPNConfig.Device.Name}} meta nfproto ipv6 counter jump rejectipversion + {{end}} + {{if .VPNConfig.IPv6.IsValid}} + meta oifname {{.VPNConfig.Device.Name}} meta nfproto ipv4 counter jump rejectipversion + {{end}} + } + + chain rejectoutput { + type filter hook output priority filter; policy accept; + + # reject unsupported ip versions when sending packets, + # add matching jump rule to rejectipversion if necessary + {{if .VPNConfig.IPv4.IsValid}} + meta oifname {{.VPNConfig.Device.Name}} meta nfproto ipv6 counter jump rejectipversion + {{end}} + {{if .VPNConfig.IPv6.IsValid}} + meta oifname {{.VPNConfig.Device.Name}} meta nfproto ipv4 counter jump rejectipversion + {{end}} + } +} +{{end -}} +` + // getCommandListVPNSetup returns the command list identified by name for VPNSetup. func getCommandListVPNSetup(name string) *CommandList { var cl *CommandList switch name { - case "VPNSetupSetupVPNDevice": - // Setup VPN Device + case "VPNSetupSetup": + // Setup VPN cl = &CommandList{ Name: name, Commands: []*Command{ - // set mtu on device + // Device setup: + // - set mtu on device + // - set device up + // - set ipv4 and ipv6 addresses on device {Line: "{{.Executables.IP}} link set {{.VPNConfig.Device.Name}} mtu {{.VPNConfig.Device.MTU}}"}, - // set device up {Line: "{{.Executables.IP}} link set {{.VPNConfig.Device.Name}} up"}, - // set ipv4 and ipv6 addresses on device {Line: "{{if .VPNConfig.IPv4.IsValid}}{{.Executables.IP}} address add {{.VPNConfig.IPv4}} dev {{.VPNConfig.Device.Name}}{{end}}"}, {Line: "{{if .VPNConfig.IPv6.IsValid}}{{.Executables.IP}} address add {{.VPNConfig.IPv6}} dev {{.VPNConfig.Device.Name}}{{end}}"}, + // Routing setup + {Line: "{{.Executables.Nft}} -f -", Stdin: `{{template "SplitRoutingRules" .}}`}, + {Line: "{{.Executables.IP}} -4 route add 0.0.0.0/0 dev {{.VPNConfig.Device.Name}} table {{.SplitRouting.RoutingTable}}"}, + {Line: "{{.Executables.IP}} -4 rule add iif {{.VPNConfig.Device.Name}} table main pref {{.SplitRouting.RulePriority1}}"}, + {Line: "{{.Executables.IP}} -4 rule add not fwmark {{.SplitRouting.FirewallMark}} table {{.SplitRouting.RoutingTable}} pref {{.SplitRouting.RulePriority2}}"}, + {Line: "{{.Executables.Sysctl}} -q net.ipv4.conf.all.src_valid_mark=1"}, + {Line: "{{.Executables.IP}} -6 route add ::/0 dev {{.VPNConfig.Device.Name}} table {{.SplitRouting.RoutingTable}}"}, + {Line: "{{.Executables.IP}} -6 rule add iif {{.VPNConfig.Device.Name}} table main pref {{.SplitRouting.RulePriority1}}"}, + {Line: "{{.Executables.IP}} -6 rule add not fwmark {{.SplitRouting.FirewallMark}} table {{.SplitRouting.RoutingTable}} pref {{.SplitRouting.RulePriority2}}"}, + // DNS setup: + // - set DNS Proxy + // - set Domains + // - set default DNS route + // - flush caches + // - reset server features + {Line: "{{.Executables.Resolvectl}} dns {{.VPNConfig.Device.Name}} {{.DNSProxy.Address}}"}, + {Line: "{{.Executables.Resolvectl}} domain {{.VPNConfig.Device.Name}} {{.VPNConfig.DNS.DefaultDomain}} ~."}, + {Line: "{{.Executables.Resolvectl}} default-route {{.VPNConfig.Device.Name}} yes"}, + {Line: "{{.Executables.Resolvectl}} flush-caches"}, + {Line: "{{.Executables.Resolvectl}} reset-server-features"}, }, - defaultTemplate: "", + defaultTemplate: VPNSetupDefaultTemplate, } - case "VPNSetupTeardownVPNDevice": - // Teardown VPN Device + case "VPNSetupTeardown": + // Teardown VPN cl = &CommandList{ Name: name, Commands: []*Command{ + // Device teardown {Line: "{{.Executables.IP}} link set {{.VPNConfig.Device.Name}} down"}, + // Routing teardown + {Line: "{{.Executables.IP}} -4 rule delete table {{.SplitRouting.RoutingTable}}"}, + {Line: "{{.Executables.IP}} -4 rule delete iif {{.VPNConfig.Device.Name}} table main"}, + {Line: "{{.Executables.IP}} -6 rule delete table {{.SplitRouting.RoutingTable}}"}, + {Line: "{{.Executables.IP}} -6 rule delete iif {{.VPNConfig.Device.Name}} table main"}, + {Line: "{{.Executables.Nft}} -f - delete table inet oc-daemon-routing"}, + // DNS teardown + {Line: "{{.Executables.Resolvectl}} revert {{.VPNConfig.Device.Name}}"}, + {Line: "{{.Executables.Resolvectl}} flush-caches"}, + {Line: "{{.Executables.Resolvectl}} reset-server-features"}, }, - defaultTemplate: "", + defaultTemplate: VPNSetupDefaultTemplate, } case "VPNSetupSetupDNSServer": // Setup DNS server @@ -506,7 +509,7 @@ func getCommandListVPNSetup(name string) *CommandList { Commands: []*Command{ {Line: "{{.Executables.Resolvectl}} dns {{.VPNConfig.Device.Name}} {{.DNSProxy.Address}}"}, }, - defaultTemplate: "", + defaultTemplate: VPNSetupDefaultTemplate, } case "VPNSetupSetupDNSDomains": // Setup DNS domains @@ -515,7 +518,7 @@ func getCommandListVPNSetup(name string) *CommandList { Commands: []*Command{ {Line: "{{.Executables.Resolvectl}} domain {{.VPNConfig.Device.Name}} {{.VPNConfig.DNS.DefaultDomain}} ~."}, }, - defaultTemplate: "", + defaultTemplate: VPNSetupDefaultTemplate, } case "VPNSetupSetupDNSDefaultRoute": // Setup DNS Default Route @@ -524,31 +527,7 @@ func getCommandListVPNSetup(name string) *CommandList { Commands: []*Command{ {Line: "{{.Executables.Resolvectl}} default-route {{.VPNConfig.Device}} yes"}, }, - defaultTemplate: "", - } - case "VPNSetupSetupDNS": - // Setup DNS - cl = &CommandList{ - Name: name, - Commands: []*Command{ - {Line: "{{.Executables.Resolvectl}} dns {{.VPNConfig.Device.Name}} {{.DNSProxy.Address}}"}, - {Line: "{{.Executables.Resolvectl}} domain {{.VPNConfig.Device.Name}} {{.VPNConfig.DNS.DefaultDomain}} ~."}, - {Line: "{{.Executables.Resolvectl}} default-route {{.VPNConfig.Device.Name}} yes"}, - {Line: "{{.Executables.Resolvectl}} flush-caches"}, - {Line: "{{.Executables.Resolvectl}} reset-server-features"}, - }, - defaultTemplate: "", - } - case "VPNSetupTeardownDNS": - // Teardown DNS - cl = &CommandList{ - Name: name, - Commands: []*Command{ - {Line: "{{.Executables.Resolvectl}} revert {{.VPNConfig.Device.Name}}"}, - {Line: "{{.Executables.Resolvectl}} flush-caches"}, - {Line: "{{.Executables.Resolvectl}} reset-server-features"}, - }, - defaultTemplate: "", + defaultTemplate: VPNSetupDefaultTemplate, } case "VPNSetupEnsureDNS": // Ensure DNS @@ -557,7 +536,7 @@ func getCommandListVPNSetup(name string) *CommandList { Commands: []*Command{ {Line: "{{.Executables.Resolvectl}} status {{.VPNConfig.Device.Name}} --no-pager"}, }, - defaultTemplate: "", + defaultTemplate: VPNSetupDefaultTemplate, } case "VPNSetupCleanup": // Cleanup @@ -577,7 +556,7 @@ func getCommandListVPNSetup(name string) *CommandList { {Line: "{{.Executables.IP}} -6 route flush table {{.SplitRouting.RoutingTable}}"}, {Line: "{{.Executables.Nft}} -f - delete table inet oc-daemon-routing"}, }, - defaultTemplate: "", + defaultTemplate: VPNSetupDefaultTemplate, } default: return nil diff --git a/internal/cmdtmpl/command_test.go b/internal/cmdtmpl/command_test.go index aa2ca05..1e11758 100644 --- a/internal/cmdtmpl/command_test.go +++ b/internal/cmdtmpl/command_test.go @@ -36,8 +36,6 @@ func TestGetCommandList(t *testing.T) { // existing for _, name := range []string{ // Split Routing - "SplitRoutingSetupRouting", - "SplitRoutingTeardownRouting", "SplitRoutingSetExcludes", // Traffic Policing @@ -52,13 +50,11 @@ func TestGetCommandList(t *testing.T) { "TrafPolCleanup", // VPN Setup - "VPNSetupSetupVPNDevice", - "VPNSetupTeardownVPNDevice", + "VPNSetupSetup", + "VPNSetupTeardown", "VPNSetupSetupDNSServer", "VPNSetupSetupDNSDomains", "VPNSetupSetupDNSDefaultRoute", - "VPNSetupSetupDNS", - "VPNSetupTeardownDNS", "VPNSetupEnsureDNS", "VPNSetupCleanup", } { @@ -94,8 +90,6 @@ func TestGetCmds(t *testing.T) { // existing, that only need daemon config as input data for _, name := range []string{ // Split Routing - "SplitRoutingSetupRouting", - "SplitRoutingTeardownRouting", // "SplitRoutingSetExcludes", // skip, requires excludes // Traffic Policing @@ -110,13 +104,11 @@ func TestGetCmds(t *testing.T) { "TrafPolCleanup", // VPN Setup - "VPNSetupSetupVPNDevice", - "VPNSetupTeardownVPNDevice", + "VPNSetupSetup", + "VPNSetupTeardown", "VPNSetupSetupDNSServer", "VPNSetupSetupDNSDomains", "VPNSetupSetupDNSDefaultRoute", - "VPNSetupSetupDNS", - "VPNSetupTeardownDNS", "VPNSetupEnsureDNS", "VPNSetupCleanup", } { diff --git a/internal/splitrt/splitrt.go b/internal/splitrt/splitrt.go index 6a82c34..02dcf1f 100644 --- a/internal/splitrt/splitrt.go +++ b/internal/splitrt/splitrt.go @@ -10,7 +10,6 @@ import ( log "github.com/sirupsen/logrus" "github.com/telekom-mms/oc-daemon/internal/addrmon" - "github.com/telekom-mms/oc-daemon/internal/cmdtmpl" "github.com/telekom-mms/oc-daemon/internal/daemoncfg" "github.com/telekom-mms/oc-daemon/internal/devmon" "github.com/telekom-mms/oc-daemon/internal/dnsproxy" @@ -61,80 +60,6 @@ type SplitRouting struct { closed chan struct{} } -// setupRouting sets up routing using config. -func (s *SplitRouting) setupRouting(ctx context.Context) { - // set up routing - data := s.config - cmds, err := cmdtmpl.GetCmds("SplitRoutingSetupRouting", data) - if err != nil { - log.WithError(err).Error("SplitRouting could not get setup routing commands") - } - for _, c := range cmds { - if stdout, stderr, err := c.Run(ctx); err != nil { - log.WithFields(log.Fields{ - "command": c.Cmd, - "args": c.Args, - "stdin": c.Stdin, - "stdout": string(stdout), - "stderr": string(stderr), - "error": err, - }).Error("SplitRouting could not run setup routing command") - } - } - - // add gateway to static excludes - if s.config.VPNConfig.Gateway.IsValid() { - gateway := netip.PrefixFrom(s.config.VPNConfig.Gateway, - s.config.VPNConfig.Gateway.BitLen()) - if s.excludes.AddStatic(gateway) { - setExcludes(ctx, s.config, s.excludes.GetPrefixes()) - } - } - - // add static IPv4 excludes - for _, e := range s.config.VPNConfig.Split.ExcludeIPv4 { - if e.String() == "0.0.0.0/32" { - continue - } - if s.excludes.AddStatic(e) { - setExcludes(ctx, s.config, s.excludes.GetPrefixes()) - } - } - - // add static IPv6 excludes - for _, e := range s.config.VPNConfig.Split.ExcludeIPv6 { - // TODO: does ::/128 exist? - if e.String() == "::/128" { - continue - } - if s.excludes.AddStatic(e) { - setExcludes(ctx, s.config, s.excludes.GetPrefixes()) - } - } -} - -// teardownRouting tears down the routing configuration. -func (s *SplitRouting) teardownRouting(ctx context.Context) { - // tear down routing - data := s.config - cmds, err := cmdtmpl.GetCmds("SplitRoutingTeardownRouting", data) - if err != nil { - log.WithError(err).Error("SplitRouting could not get teardown routing commands") - } - for _, c := range cmds { - if stdout, stderr, err := c.Run(ctx); err != nil { - log.WithFields(log.Fields{ - "command": c.Cmd, - "args": c.Args, - "stdin": c.Stdin, - "stdout": string(stdout), - "stderr": string(stderr), - "error": err, - }).Error("SplitRouting could not run teardown routing command") - } - } -} - // excludeLocalNetworks returns whether local (virtual) networks should be excluded. func (s *SplitRouting) excludeLocalNetworks() (exclude bool, virtual bool) { for _, e := range s.config.VPNConfig.Split.ExcludeIPv4 { @@ -250,7 +175,6 @@ func (s *SplitRouting) handleDNSReport(ctx context.Context, r *dnsproxy.Report) // start starts split routing. func (s *SplitRouting) start(ctx context.Context) { defer close(s.closed) - defer s.teardownRouting(ctx) defer s.devmon.Stop() defer s.addrmon.Stop() @@ -283,22 +207,47 @@ func (s *SplitRouting) Start() error { // create context ctx := context.Background() - // configure routing - s.setupRouting(ctx) - // start device monitor if err := s.devmon.Start(); err != nil { - s.teardownRouting(ctx) return fmt.Errorf("SplitRouting could not start DevMon: %w", err) } // start address monitor if err := s.addrmon.Start(); err != nil { s.devmon.Stop() - s.teardownRouting(ctx) return fmt.Errorf("SplitRouting could not start AddrMon: %w", err) } + // add gateway to static excludes + if s.config.VPNConfig.Gateway.IsValid() { + gateway := netip.PrefixFrom(s.config.VPNConfig.Gateway, + s.config.VPNConfig.Gateway.BitLen()) + if s.excludes.AddStatic(gateway) { + setExcludes(ctx, s.config, s.excludes.GetPrefixes()) + } + } + + // add static IPv4 excludes + for _, e := range s.config.VPNConfig.Split.ExcludeIPv4 { + if e.String() == "0.0.0.0/32" { + continue + } + if s.excludes.AddStatic(e) { + setExcludes(ctx, s.config, s.excludes.GetPrefixes()) + } + } + + // add static IPv6 excludes + for _, e := range s.config.VPNConfig.Split.ExcludeIPv6 { + // TODO: does ::/128 exist? + if e.String() == "::/128" { + continue + } + if s.excludes.AddStatic(e) { + setExcludes(ctx, s.config, s.excludes.GetPrefixes()) + } + } + go s.start(ctx) return nil } diff --git a/internal/vpnsetup/vpnsetup.go b/internal/vpnsetup/vpnsetup.go index 0de1d80..10bb5c9 100644 --- a/internal/vpnsetup/vpnsetup.go +++ b/internal/vpnsetup/vpnsetup.go @@ -50,66 +50,6 @@ type VPNSetup struct { closed chan struct{} } -// setupVPNDevice sets up the vpn device with config. -func setupVPNDevice(ctx context.Context, config *daemoncfg.Config) { - cmds, err := cmdtmpl.GetCmds("VPNSetupSetupVPNDevice", config) - if err != nil { - log.WithError(err).Error("VPNSetup could not get setup VPN device commands") - } - for _, c := range cmds { - if stdout, stderr, err := c.Run(ctx); err != nil && !errors.Is(err, context.Canceled) { - log.WithFields(log.Fields{ - "command": c.Cmd, - "args": c.Args, - "stdin": c.Stdin, - "stdout": string(stdout), - "stderr": string(stderr), - "error": err, - }).Error("VPNSetup could not run setup VPN device command") - } - } -} - -// teardownVPNDevice tears down the configured vpn device. -func teardownVPNDevice(ctx context.Context, config *daemoncfg.Config) { - cmds, err := cmdtmpl.GetCmds("VPNSetupTeardownVPNDevice", config) - if err != nil { - log.WithError(err).Error("VPNSetup could not get teardown VPN device commands") - } - for _, c := range cmds { - if stdout, stderr, err := c.Run(ctx); err != nil && !errors.Is(err, context.Canceled) { - log.WithFields(log.Fields{ - "command": c.Cmd, - "args": c.Args, - "stdin": c.Stdin, - "stdout": string(stdout), - "stderr": string(stderr), - "error": err, - }).Error("VPNSetup could not run teardown VPN device command") - } - } -} - -// setupRouting sets up routing using config. -func (v *VPNSetup) setupRouting(config *daemoncfg.Config) { - if v.splitrt != nil { - return - } - v.splitrt = splitrt.NewSplitRouting(config) - if err := v.splitrt.Start(); err != nil { - log.WithError(err).Error("VPNSetup error setting split routing") - } -} - -// teardownRouting tears down the routing configuration. -func (v *VPNSetup) teardownRouting() { - if v.splitrt == nil { - return - } - v.splitrt.Stop() - v.splitrt = nil -} - // setupDNSServer sets the DNS server. func (v *VPNSetup) setupDNSServer(ctx context.Context, config *daemoncfg.Config) { cmds, err := cmdtmpl.GetCmds("VPNSetupSetupDNSServer", config) @@ -170,68 +110,6 @@ func (v *VPNSetup) setupDNSDefaultRoute(ctx context.Context, config *daemoncfg.C } } -// setupDNS sets up DNS using config. -func (v *VPNSetup) setupDNS(ctx context.Context, config *daemoncfg.Config) { - // configure dns proxy - - // set remotes - remotes := config.VPNConfig.DNS.Remotes() - v.dnsProxy.SetRemotes(remotes) - - // set watches - excludes := config.VPNConfig.Split.DNSExcludes() - log.WithField("excludes", excludes).Debug("Daemon setting DNS Split Excludes") - v.dnsProxy.SetWatches(excludes) - - // update dns configuration of host - cmds, err := cmdtmpl.GetCmds("VPNSetupSetupDNS", config) - if err != nil { - log.WithError(err).Error("VPNSetup could not get setup DNS commands") - } - for _, c := range cmds { - if stdout, stderr, err := c.Run(ctx); err != nil && !errors.Is(err, context.Canceled) { - log.WithFields(log.Fields{ - "command": c.Cmd, - "args": c.Args, - "stdin": c.Stdin, - "stdout": string(stdout), - "stderr": string(stderr), - "error": err, - }).Error("VPNSetup could not run setup DNS command") - } - } -} - -// teardownDNS tears down the DNS configuration. -func (v *VPNSetup) teardownDNS(ctx context.Context, config *daemoncfg.Config) { - // update dns proxy configuration - - // reset remotes - remotes := map[string][]string{} - v.dnsProxy.SetRemotes(remotes) - - // reset watches - v.dnsProxy.SetWatches([]string{}) - - // update dns configuration of host - cmds, err := cmdtmpl.GetCmds("VPNSetupTeardownDNS", config) - if err != nil { - log.WithError(err).Error("VPNSetup could not get teardown DNS commands") - } - for _, c := range cmds { - if stdout, stderr, err := c.Run(ctx); err != nil && !errors.Is(err, context.Canceled) { - log.WithFields(log.Fields{ - "command": c.Cmd, - "args": c.Args, - "stdin": c.Stdin, - "stdout": string(stdout), - "stderr": string(stderr), - "error": err, - }).Error("VPNSetup could not run teardown DNS command") - } - } -} - // checkDNSProtocols checks the configured DNS protocols, only checks default-route. func (v *VPNSetup) checkDNSProtocols(protocols []string) bool { // check if default route is set @@ -404,10 +282,37 @@ func (v *VPNSetup) stopEnsure() { // setup sets up the vpn configuration. func (v *VPNSetup) setup(ctx context.Context, conf *daemoncfg.Config) { - // setup device, routing, dns - setupVPNDevice(ctx, conf) - v.setupRouting(conf) - v.setupDNS(ctx, conf) + // configure dns proxy + // - set remotes + // - set watches + remotes := conf.VPNConfig.DNS.Remotes() + v.dnsProxy.SetRemotes(remotes) + excludes := conf.VPNConfig.Split.DNSExcludes() + log.WithField("excludes", excludes).Debug("Daemon setting DNS Split Excludes") + v.dnsProxy.SetWatches(excludes) + + cmds, err := cmdtmpl.GetCmds("VPNSetupSetup", conf) + if err != nil { + log.WithError(err).Error("VPNSetup could not get setup commands") + } + for _, c := range cmds { + if stdout, stderr, err := c.Run(ctx); err != nil && !errors.Is(err, context.Canceled) { + log.WithFields(log.Fields{ + "command": c.Cmd, + "args": c.Args, + "stdin": c.Stdin, + "stdout": string(stdout), + "stderr": string(stderr), + "error": err, + }).Error("VPNSetup could not run setup command") + } + } + + // configure split routing + v.splitrt = splitrt.NewSplitRouting(conf) + if err := v.splitrt.Start(); err != nil { + log.WithError(err).Error("VPNSetup error setting split routing") + } // ensure VPN config v.startEnsure(ctx, conf) @@ -418,10 +323,34 @@ func (v *VPNSetup) teardown(ctx context.Context, conf *daemoncfg.Config) { // stop ensuring VPN config v.stopEnsure() - // tear down device, routing, dns - teardownVPNDevice(ctx, conf) - v.teardownRouting() - v.teardownDNS(ctx, conf) + // unconfigure split routing + v.splitrt.Stop() + v.splitrt = nil + + cmds, err := cmdtmpl.GetCmds("VPNSetupTeardown", conf) + if err != nil { + log.WithError(err).Error("VPNSetup could not get teardown commands") + } + for _, c := range cmds { + if stdout, stderr, err := c.Run(ctx); err != nil && !errors.Is(err, context.Canceled) { + log.WithFields(log.Fields{ + "command": c.Cmd, + "args": c.Args, + "stdin": c.Stdin, + "stdout": string(stdout), + "stderr": string(stderr), + "error": err, + }).Error("VPNSetup could not run teardown command") + } + } + + // unconfigure dns proxy + // - reset remotes + // - reset watches + remotes := map[string][]string{} + v.dnsProxy.SetRemotes(remotes) + v.dnsProxy.SetWatches([]string{}) + } // getState gets the internal state. diff --git a/internal/vpnsetup/vpnsetup_test.go b/internal/vpnsetup/vpnsetup_test.go index 67b9525..b0e9a2a 100644 --- a/internal/vpnsetup/vpnsetup_test.go +++ b/internal/vpnsetup/vpnsetup_test.go @@ -17,181 +17,6 @@ import ( "github.com/vishvananda/netlink" ) -// TestSetupVPNDevice tests setupVPNDevice. -func TestSetupVPNDevice(t *testing.T) { - // clean up after tests - oldRunCmd := execs.RunCmd - defer func() { execs.RunCmd = oldRunCmd }() - - c := daemoncfg.NewConfig() - c.DNSProxy.Address = "127.0.0.1:4253" - c.VPNConfig.Device.Name = "tun0" - c.VPNConfig.Device.MTU = 1300 - c.VPNConfig.IPv4 = netip.MustParsePrefix("192.168.0.123/24") - c.VPNConfig.IPv6 = netip.MustParsePrefix("2001::1/64") - - // overwrite RunCmd - want := []string{ - "link set tun0 mtu 1300", - "link set tun0 up", - "address add 192.168.0.123/24 dev tun0", - "address add 2001::1/64 dev tun0", - } - got := []string{} - execs.RunCmd = func(_ context.Context, _ string, _ string, arg ...string) ([]byte, []byte, error) { - got = append(got, strings.Join(arg, " ")) - return nil, nil, nil - } - - // test - setupVPNDevice(context.Background(), c) - if !reflect.DeepEqual(got, want) { - t.Errorf("got %v, want %v", got, want) - } - - // test with execs errors - // run test above multiple times, each time failing execs.RunCmd at a - // later time. Expect only parts of the results defined in want above - // depending on when execs.RunCmd failed. - numRuns := 0 - failAt := 0 - execs.RunCmd = func(_ context.Context, _ string, _ string, arg ...string) ([]byte, []byte, error) { - // fail after failAt runs - if numRuns == failAt { - return nil, nil, errors.New("test error") - } - - numRuns++ - got = append(got, strings.Join(arg, " ")) - return nil, nil, nil - } - for _, f := range []int{0, 1, 2} { - got = []string{} - numRuns = 0 - failAt = f - - setupVPNDevice(context.Background(), c) - if !reflect.DeepEqual(got, want[:f]) { - t.Errorf("got %v, want %v", got, want) - } - } -} - -// TestTeardownVPNDevice tests teardownVPNDevice. -func TestTeardownVPNDevice(t *testing.T) { - // clean up after tests - oldRunCmd := execs.RunCmd - defer func() { execs.RunCmd = oldRunCmd }() - - c := daemoncfg.NewConfig() - c.DNSProxy.Address = "127.0.0.1:4253" - c.VPNConfig.Device.Name = "tun0" - - // overwrite RunCmd - want := []string{ - "link set tun0 down", - } - got := []string{} - execs.RunCmd = func(_ context.Context, _ string, _ string, arg ...string) ([]byte, []byte, error) { - got = append(got, strings.Join(arg, " ")) - return nil, nil, nil - } - - // test - teardownVPNDevice(context.Background(), c) - if !reflect.DeepEqual(got, want) { - t.Errorf("got %v, want %v", got, want) - } - - // test with execs error - execs.RunCmd = func(context.Context, string, string, ...string) ([]byte, []byte, error) { - return nil, nil, errors.New("test error") - } - teardownVPNDevice(context.Background(), c) -} - -// TestVPNSetupSetupDNS tests setupDNS of VPNSetup. -func TestVPNSetupSetupDNS(t *testing.T) { - // clean up after tests - oldRunCmd := execs.RunCmd - defer func() { execs.RunCmd = oldRunCmd }() - - c := daemoncfg.NewConfig() - c.VPNConfig.Device.Name = "tun0" - c.VPNConfig.DNS.DefaultDomain = "mycompany.com" - - got := []string{} - execs.RunCmd = func(_ context.Context, _ string, _ string, arg ...string) ([]byte, []byte, error) { - got = append(got, strings.Join(arg, " ")) - return nil, nil, nil - } - v := NewVPNSetup(dnsproxy.NewProxy(daemoncfg.NewDNSProxy())) - v.setupDNS(context.Background(), c) - - want := []string{ - "dns tun0 127.0.0.1:4253", - "domain tun0 mycompany.com ~.", - "default-route tun0 yes", - "flush-caches", - "reset-server-features", - } - if !reflect.DeepEqual(got, want) { - t.Errorf("got %v, want %v", got, want) - } - - // test with execs errors - execs.RunCmd = func(_ context.Context, _ string, _ string, arg ...string) ([]byte, []byte, error) { - got = append(got, strings.Join(arg, " ")) - return nil, nil, errors.New("test error") - } - - got = []string{} - v.setupDNS(context.Background(), c) - if !reflect.DeepEqual(got, want) { - t.Errorf("got %v, want %v", got, want) - } -} - -// TestVPNSetupTeardownDNS tests teardownDNS of VPNSetup. -func TestVPNSetupTeardownDNS(t *testing.T) { - // clean up after tests - oldRunCmd := execs.RunCmd - defer func() { execs.RunCmd = oldRunCmd }() - - c := daemoncfg.NewConfig() - c.VPNConfig.Device.Name = "tun0" - - got := []string{} - execs.RunCmd = func(_ context.Context, _ string, _ string, arg ...string) ([]byte, []byte, error) { - got = append(got, strings.Join(arg, " ")) - return nil, nil, nil - } - - v := NewVPNSetup(dnsproxy.NewProxy(daemoncfg.NewDNSProxy())) - v.teardownDNS(context.Background(), c) - - want := []string{ - "revert tun0", - "flush-caches", - "reset-server-features", - } - if !reflect.DeepEqual(got, want) { - t.Errorf("got %v, want %v", got, want) - } - - // test with execs errors - execs.RunCmd = func(_ context.Context, _ string, _ string, arg ...string) ([]byte, []byte, error) { - got = append(got, strings.Join(arg, " ")) - return nil, nil, errors.New("test error") - } - - got = []string{} - v.teardownDNS(context.Background(), c) - if !reflect.DeepEqual(got, want) { - t.Errorf("got %v, want %v", got, want) - } -} - // TestVPNSetupCheckDNSProtocols tests checkDNSProtocols of VPNSetup. func TestVPNSetupCheckDNSProtocols(t *testing.T) { v := NewVPNSetup(dnsproxy.NewProxy(daemoncfg.NewDNSProxy()))