diff --git a/wgengine/router_openbsd.go b/wgengine/router_openbsd.go index 68ad331a7..83915ced0 100644 --- a/wgengine/router_openbsd.go +++ b/wgengine/router_openbsd.go @@ -5,24 +5,25 @@ package wgengine import ( + "bytes" "fmt" + "io/ioutil" "log" + "os" "os/exec" + "path/filepath" + "strings" "github.com/tailscale/wireguard-go/device" "github.com/tailscale/wireguard-go/tun" "github.com/tailscale/wireguard-go/wgcfg" + "tailscale.com/atomicfile" "tailscale.com/types/logger" ) -// For now this router only supports the userspace WireGuard implementations. -// -// There is an experimental kernel version in the works: +// For now this router only supports the WireGuard userspace implementation. +// There is an experimental kernel version in the works for OpenBSD: // https://git.zx2c4.com/wireguard-openbsd. -// -// TODO(mbaillie): netlink-style monitoring might be possible through -// `ifstated(8)`/`devd(8)`, or become possible with the OpenBSD kernel -// implementation. This merits further investigation. type openbsdRouter struct { logf logger.Logf @@ -42,7 +43,6 @@ func newUserspaceRouter(logf logger.Logf, _ *device.Device, tundev tun.Device) ( }, nil } -// TODO(mbaillie): extract as identical to linux version func cmd(args ...string) *exec.Cmd { if len(args) == 0 { log.Fatalf("exec.Cmd(%#v) invalid; need argv[0]\n", args) @@ -167,12 +167,95 @@ func (r *openbsdRouter) Close() error { r.logf("failed to restore system resolv.conf: %v", err) } - // TODO(mbaillie): wipe routes + return nil +} + +const ( + tsConf = "/etc/resolv.tailscale.conf" + backupConf = "/etc/resolv.pre-tailscale-backup.conf" + resolvConf = "/etc/resolv.conf" +) + +func (r *openbsdRouter) replaceResolvConf(servers []wgcfg.IP, domains []string) error { + if len(servers) == 0 { + return r.restoreResolvConf() + } + + // Write the tsConf file. + buf := new(bytes.Buffer) + fmt.Fprintf(buf, "# resolv.conf(5) file generated by tailscale\n") + fmt.Fprintf(buf, "# DO NOT EDIT THIS FILE BY HAND -- CHANGES WILL BE OVERWRITTEN\n\n") + for _, ns := range servers { + fmt.Fprintf(buf, "nameserver %s\n", ns) + } + if len(domains) > 0 { + fmt.Fprintf(buf, "search "+strings.Join(domains, " ")+"\n") + } + tf, err := ioutil.TempFile(filepath.Dir(tsConf), filepath.Base(tsConf)+".*") + if err != nil { + return err + } + tempName := tf.Name() + tf.Close() + + if err := atomicfile.WriteFile(tempName, buf.Bytes(), 0644); err != nil { + return err + } + if err := os.Rename(tempName, tsConf); err != nil { + return err + } + + if linkPath, err := os.Readlink(resolvConf); err != nil { + // Remove any old backup that may exist. + os.Remove(backupConf) + + // Backup the existing /etc/resolv.conf file. + contents, err := ioutil.ReadFile(resolvConf) + if os.IsNotExist(err) { + // No existing /etc/resolv.conf file to backup. + // Nothing to do. + return nil + } else if err != nil { + return err + } + if err := atomicfile.WriteFile(backupConf, contents, 0644); err != nil { + return err + } + } else if linkPath != tsConf { + // Backup the existing symlink. + os.Remove(backupConf) + if err := os.Symlink(linkPath, backupConf); err != nil { + return err + } + } else { + // Nothing to do, resolvConf already points to tsConf. + return nil + } + + os.Remove(resolvConf) + if err := os.Symlink(tsConf, resolvConf); err != nil { + return nil + } return nil } -// TODO(mbaillie): these are no-ops for now. They could re-use the Linux funcs -// (sans systemd parts), but I note Linux DNS is disabled(?) so leaving for now. -func (r *openbsdRouter) replaceResolvConf(_ []wgcfg.IP, _ []string) error { return nil } -func (r *openbsdRouter) restoreResolvConf() error { return nil } +func (r *openbsdRouter) restoreResolvConf() error { + if _, err := os.Stat(backupConf); err != nil { + if os.IsNotExist(err) { + return nil // No backup resolv.conf to restore. + } + return err + } + if ln, err := os.Readlink(resolvConf); err != nil { + return err + } else if ln != tsConf { + return fmt.Errorf("resolv.conf is not a symlink to %s", tsConf) + } + if err := os.Rename(backupConf, resolvConf); err != nil { + return err + } + os.Remove(tsConf) // Best effort removal. + + return nil +}