You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tailscale/net/dns/resolvd.go

164 lines
3.8 KiB
Go

// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
//go:build openbsd
package dns
import (
"bytes"
"os"
"os/exec"
"regexp"
"strings"
"tailscale.com/net/dns/resolvconffile"
"tailscale.com/types/logger"
)
func newResolvdManager(logf logger.Logf, interfaceName string) (*resolvdManager, error) {
return &resolvdManager{
logf: logf,
ifName: interfaceName,
fs: directFS{},
}, nil
}
// resolvdManager is an OSConfigurator which uses route(1) to teach OpenBSD's
// resolvd(8) about DNS servers.
type resolvdManager struct {
logf logger.Logf
ifName string
fs directFS
}
func (m *resolvdManager) SetDNS(config OSConfig) error {
args := []string{
"nameserver",
m.ifName,
}
// first, read in the resolvConf
origResolv, err := m.fs.ReadFile(resolvConf)
if err != nil {
return err
}
// search for our sentinel in resolvConf, if it's not present
// it's our first call to SetDNS, and we should make backupConf
resolvConfOwned := bytes.Contains(origResolv, []byte("generated by tailscale"))
if !resolvConfOwned {
_, err = m.fs.Stat(backupConf)
if err != nil && os.IsNotExist(err) {
_, err = m.readAndCopy(resolvConf, backupConf, 0644)
if err != nil {
return err
}
}
}
// remove resolvd nameservers and search lines
origResolv = removeSearchLines(origResolv)
origResolv = removeResolvdNameserverLines(origResolv)
// and clean up whitespace
removeBlanks := regexp.MustCompile(`(?m)^\s*\n`)
origResolv = removeBlanks.ReplaceAll(origResolv, []byte(""))
for _, ns := range config.Nameservers {
args = append(args, ns.String())
}
var newSearch = []string{
"search",
}
for _, s := range config.SearchDomains {
newSearch = append(newSearch, s.WithoutTrailingDot())
}
var resolvConfHeader = []byte("")
if !resolvConfOwned {
// if we don't have a header, insert one
resolvConfHeader = []byte(
"# resolv.conf(5) file generated by tailscale\n" +
"# For more info, see https://tailscale.com/s/resolvconf-overwrite\n" +
"# DO NOT EDIT THIS FILE BY HAND -- CHANGES WILL BE OVERWRITTEN\n\n")
}
newResolvConf := append(resolvConfHeader, origResolv...)
if len(newSearch) > 1 {
newResolvConf = append(newResolvConf, []byte(strings.Join(newSearch, " "))...)
newResolvConf = append(newResolvConf, '\n')
}
err = m.fs.WriteFile(resolvConf, newResolvConf, 0644)
if err != nil {
return err
}
cmd := exec.Command("/sbin/route", args...)
return cmd.Run()
}
func (m *resolvdManager) SupportsSplitDNS() bool {
return false
}
func (m *resolvdManager) GetBaseConfig() (OSConfig, error) {
cfg, err := m.readResolvConf()
if err != nil {
return OSConfig{}, err
}
return cfg, nil
}
func (m *resolvdManager) Close() error {
// resolvd handles teardown of nameservers so we only need to write back the original
// config and be done.
_, err := m.readAndCopy(backupConf, resolvConf, 0644)
if err != nil {
return err
}
return m.fs.Remove(backupConf)
}
func (m *resolvdManager) readAndCopy(a, b string, mode os.FileMode) ([]byte, error) {
orig, err := m.fs.ReadFile(a)
if err != nil {
return nil, err
}
err = m.fs.WriteFile(b, orig, mode)
if err != nil {
return nil, err
}
return orig, nil
}
func (m resolvdManager) readResolvConf() (config OSConfig, err error) {
b, err := m.fs.ReadFile(resolvConf)
if err != nil {
return OSConfig{}, err
}
rconf, err := resolvconffile.Parse(bytes.NewReader(b))
if err != nil {
return config, err
}
return OSConfig{
Nameservers: rconf.Nameservers,
SearchDomains: rconf.SearchDomains,
}, nil
}
func removeResolvdNameserverLines(orig []byte) []byte {
re := regexp.MustCompile(`(?m)^nameserver\s+[^\s]+\s+#\s+resolvd:.*$`)
return re.ReplaceAll(orig, []byte(""))
}
func removeSearchLines(orig []byte) []byte {
re := regexp.MustCompile(`(?ms)^search\s+.+$`)
return re.ReplaceAll(orig, []byte(""))
}