Skip to content

Commit

Permalink
Merge pull request #50 from telekom-mms/feature/ensure-dns-config
Browse files Browse the repository at this point in the history
Ensure DNS configuration in VPNSetup
  • Loading branch information
hwipl authored Dec 11, 2023
2 parents bd82a37 + 62c8f5f commit 910a9a7
Show file tree
Hide file tree
Showing 4 changed files with 390 additions and 17 deletions.
14 changes: 14 additions & 0 deletions internal/execs/execs.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,15 @@ var RunCmd = func(ctx context.Context, cmd string, s string, arg ...string) erro
return c.Run()
}

// RunCmdOutput runs the cmd with args and sets stdin to s, returns output
var RunCmdOutput = func(ctx context.Context, cmd string, s string, arg ...string) ([]byte, error) {
c := exec.CommandContext(ctx, cmd, arg...)
if s != "" {
c.Stdin = bytes.NewBufferString(s)
}
return c.Output()
}

// RunIP runs the "ip" command with args
func RunIP(ctx context.Context, arg ...string) error {
return RunCmd(ctx, ip, "", arg...)
Expand Down Expand Up @@ -79,6 +88,11 @@ func RunResolvectl(ctx context.Context, arg ...string) error {
return RunCmd(ctx, resolvectl, "", arg...)
}

// RunResolvectlOutput runs the "resolvectl" command with args, returns output
func RunResolvectlOutput(ctx context.Context, arg ...string) ([]byte, error) {
return RunCmdOutput(ctx, resolvectl, "", arg...)
}

// SetExecutables configures all executables from config
func SetExecutables(config *Config) {
ip = config.IP
Expand Down
32 changes: 32 additions & 0 deletions internal/execs/execs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,22 @@ func TestRunCmd(t *testing.T) {
}
}

// TestRunCmdOutput tests RunCmdOutput
func TestRunCmdOutput(t *testing.T) {
ctx := context.Background()
dir := t.TempDir()

// test with error
if _, err := RunCmdOutput(ctx, filepath.Join(dir, "does/not/exist"), ""); err == nil {
t.Errorf("running should fail: %v", err)
}

// test without error
if b, err := RunCmdOutput(ctx, "ls", "", "-d", dir); err != nil || len(b) == 0 {
t.Errorf("running should not fail: %v, %v", b, err)
}
}

// TestRunIP tests RunIP
func TestRunIP(t *testing.T) {
want := []string{"ip address show"}
Expand Down Expand Up @@ -170,6 +186,22 @@ func TestRunResolvectl(t *testing.T) {
}
}

// TestRunResolvectlOutput tests RunResolvectlOutput
func TestRunResolvectlOutput(t *testing.T) {
want := []string{"resolvectl dns"}
got := []string{}
RunCmdOutput = func(ctx context.Context, cmd string, s string, arg ...string) ([]byte, error) {
got = append(got, cmd+" "+strings.Join(arg, " "))
return []byte("OK"), nil
}
if b, err := RunResolvectlOutput(context.Background(), "dns"); err != nil || string(b) != "OK" {
t.Errorf("invalid return values %v, %v", b, err)
}
if !reflect.DeepEqual(got, want) {
t.Errorf("got %v, want %v", got, want)
}
}

// TestSetExecutables tests SetExecutables
func TestSetExecutables(t *testing.T) {
config := &Config{
Expand Down
222 changes: 205 additions & 17 deletions internal/vpnsetup/vpnsetup.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import (
"context"
"net"
"strconv"
"strings"
"time"

log "github.com/sirupsen/logrus"
"github.com/telekom-mms/oc-daemon/internal/dnsproxy"
Expand Down Expand Up @@ -44,6 +46,9 @@ type VPNSetup struct {
dnsProxy *dnsproxy.Proxy
dnsProxyConf *dnsproxy.Config

ensureDone chan struct{}
ensureClosed chan struct{}

cmds chan *command
events chan *Event
done chan struct{}
Expand Down Expand Up @@ -130,6 +135,37 @@ func (v *VPNSetup) teardownRouting() {
v.splitrt = nil
}

// setupDNSServer sets the DNS server
func (v *VPNSetup) setupDNSServer(ctx context.Context, config *vpnconfig.Config) {
device := config.Device.Name
if err := execs.RunResolvectl(ctx, "dns", device, v.dnsProxyConf.Address); err != nil {
log.WithError(err).WithFields(log.Fields{
"device": device,
"server": v.dnsProxyConf.Address,
}).Error("VPNSetup error setting dns server")
}
}

// setupDNSDomains sets the DNS domains
func (v *VPNSetup) setupDNSDomains(ctx context.Context, config *vpnconfig.Config) {
device := config.Device.Name
if err := execs.RunResolvectl(ctx, "domain", device, config.DNS.DefaultDomain, "~."); err != nil {
log.WithError(err).WithFields(log.Fields{
"device": device,
"domain": config.DNS.DefaultDomain,
}).Error("VPNSetup error setting dns domains")
}
}

// setupDNSDefaultRoute sets the DNS default route
func (v *VPNSetup) setupDNSDefaultRoute(ctx context.Context, config *vpnconfig.Config) {
device := config.Device.Name
if err := execs.RunResolvectl(ctx, "default-route", device, "yes"); err != nil {
log.WithError(err).WithField("device", device).
Error("VPNSetup error setting dns default route")
}
}

// setupDNS sets up DNS using config
func (v *VPNSetup) setupDNS(ctx context.Context, config *vpnconfig.Config) {
// configure dns proxy
Expand All @@ -146,28 +182,14 @@ func (v *VPNSetup) setupDNS(ctx context.Context, config *vpnconfig.Config) {
// update dns configuration of host

// set dns server for device
device := config.Device.Name
if err := execs.RunResolvectl(ctx, "dns", device, v.dnsProxyConf.Address); err != nil {
log.WithError(err).WithFields(log.Fields{
"device": device,
"server": v.dnsProxyConf.Address,
}).Error("VPNSetup error setting dns server")
}
v.setupDNSServer(ctx, config)

// set domains for device
// this includes "~." to use this device for all domains
if err := execs.RunResolvectl(ctx, "domain", device, config.DNS.DefaultDomain, "~."); err != nil {
log.WithError(err).WithFields(log.Fields{
"device": device,
"domain": config.DNS.DefaultDomain,
}).Error("VPNSetup error setting dns domains")
}
v.setupDNSDomains(ctx, config)

// set default route for device
if err := execs.RunResolvectl(ctx, "default-route", device, "yes"); err != nil {
log.WithError(err).WithField("device", device).
Error("VPNSetup error setting dns default route")
}
v.setupDNSDefaultRoute(ctx, config)

// flush dns caches
if err := execs.RunResolvectl(ctx, "flush-caches"); err != nil {
Expand Down Expand Up @@ -210,21 +232,187 @@ func (v *VPNSetup) teardownDNS(ctx context.Context, vpnconf *vpnconfig.Config) {
}
}

// checkDNSProtocols checks the configured DNS protocols, only checks default-route
func (v *VPNSetup) checkDNSProtocols(protocols []string) bool {
// check if default route is set
ok := false
for _, protocol := range protocols {
if protocol == "+DefaultRoute" {
ok = true
}
}

return ok
}

// checkDNSServers checks the configured DNS servers
func (v *VPNSetup) checkDNSServers(servers []string) bool {
// check dns server ip
if len(servers) != 1 || servers[0] != v.dnsProxyConf.Address {
// server not correct
return false
}

return true
}

// checkDNSDomain checks the configured DNS domains
func (v *VPNSetup) checkDNSDomain(config *vpnconfig.Config, domains []string) bool {
// get domains in config
inConfig := strings.Fields(config.DNS.DefaultDomain)
inConfig = append(inConfig, "~.")

// check domains in config
for _, c := range inConfig {
found := false
for _, d := range domains {
if c == d {
found = true
}
}

if !found {
// domains not correct
return false
}
}

return true
}

// ensureDNS ensures the DNS config
func (v *VPNSetup) ensureDNS(ctx context.Context, config *vpnconfig.Config) bool {
log.Debug("VPNSetup checking DNS settings")

// get dns settings
device := config.Device.Name
stdout, err := execs.RunResolvectlOutput(ctx, "status", device, "--no-pager")
if err != nil {
log.WithError(err).WithField("device", device).Error("VPNSetup error getting DNS settings")
return false
}

// parse and check dns settings line by line
var protOK, srvOK, domOK bool
lines := strings.Split(string(stdout), "\n")
for _, line := range lines {
// try to find separator ":"
before, after, found := strings.Cut(strings.TrimSpace(line), ":")
if !found {
continue
}

// get fields after separator
f := strings.Fields(after)

// check settings if present
switch before {
case "Protocols":
protOK = v.checkDNSProtocols(f)

case "DNS Servers":
srvOK = v.checkDNSServers(f)

case "DNS Domain":
domOK = v.checkDNSDomain(config, f)
}
}

// reset settings if incorrect/not present

if !protOK {
// protocols are not correct
log.Error("VPNSetup found invalid DNS protocols, trying to fix")

// reset default route for device
v.setupDNSDefaultRoute(ctx, config)
}

if !srvOK {
// servers are not correct
log.Error("VPNSetup found invalid DNS servers, trying to fix")

// reset dns server
v.setupDNSServer(ctx, config)
}

if !domOK {
// domains are not correct
log.Error("VPNSetup found invalid DNS domains, trying to fix")

// reset domains for device
v.setupDNSDomains(ctx, config)
}

// combine results
return protOK && srvOK && domOK
}

// ensureConfig ensured that the VPN config is and stays active
func (v *VPNSetup) ensureConfig(ctx context.Context, vpnconf *vpnconfig.Config) {
defer close(v.ensureClosed)

timerInvalid := time.Second
timerValid := 15 * time.Second
timer := timerInvalid
for {
select {
case <-time.After(timer):
log.Debug("VPNSetup checking VPN configuration")

// ensure DNS settings
if ok := v.ensureDNS(ctx, vpnconf); !ok {
timer = timerInvalid
break
}

// vpn config is OK
timer = timerValid

case <-v.ensureDone:
return
}
}
}

// startEnsure starts ensuring the VPN config
func (v *VPNSetup) startEnsure(ctx context.Context, vpnconf *vpnconfig.Config) {
v.ensureDone = make(chan struct{})
v.ensureClosed = make(chan struct{})
go v.ensureConfig(ctx, vpnconf)
}

// stopEnsure stops ensuring the VPN config
func (v *VPNSetup) stopEnsure() {
close(v.ensureDone)
<-v.ensureClosed
}

// setup sets up the vpn configuration
func (v *VPNSetup) setup(ctx context.Context, vpnconf *vpnconfig.Config) {
// setup device, routing, dns
setupVPNDevice(ctx, vpnconf)
v.setupRouting(vpnconf)
v.setupDNS(ctx, vpnconf)

// ensure VPN config
v.startEnsure(ctx, vpnconf)

// signal setup complete
v.sendEvent(&Event{EventSetupOK})
}

// teardown tears down the vpn configuration
func (v *VPNSetup) teardown(ctx context.Context, vpnconf *vpnconfig.Config) {
// stop ensuring VPN config
v.stopEnsure()

// tear down device, routing, dns
teardownVPNDevice(ctx, vpnconf)
v.teardownRouting()
v.teardownDNS(ctx, vpnconf)

// signal teardown complete
v.sendEvent(&Event{EventTeardownOK})
}

Expand Down
Loading

0 comments on commit 910a9a7

Please sign in to comment.