From 6e30646a68239c95d5ed46f4559508bfff985915 Mon Sep 17 00:00:00 2001 From: hwipl <33433250+hwipl@users.noreply.github.com> Date: Fri, 17 Jan 2025 12:21:03 +0100 Subject: [PATCH] Move commands for setting split routing to VPNSetup Move the commands for setting up and tearing down split routing from SplitRouting to VPNSetup. Merge the split routing commands with commands for device and DNS setup in the two new command lists "VPNSetupSetup" and "VPNSetupTeardown". Signed-off-by: hwipl <33433250+hwipl@users.noreply.github.com> --- internal/cmdtmpl/command.go | 349 ++++++++++++++--------------- internal/cmdtmpl/command_test.go | 16 +- internal/splitrt/splitrt.go | 111 +++------ internal/vpnsetup/vpnsetup.go | 189 +++++----------- internal/vpnsetup/vpnsetup_test.go | 175 --------------- 5 files changed, 257 insertions(+), 583 deletions(-) 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()))