Просмотр исходного кода

net/dns: add Plan 9 support

This requires the rsc/plan9 ndb DNS changes for now:

https://9fans.topicbox.com/groups/9fans/T9c9d81b5801a0820/ndb-suffix-specific-dns-changes
https://github.com/rsc/plan9/commit/e8c148ff092a5780d04aa2fd4a07a5732207b698
https://github.com/rsc/plan9/commit/1d0642ae493bf5ce798a6aa64a745bc6316baa11

Updates #5794

Change-Id: I0e242c1fe7bb4404e23604e03a31f89f0d18e70d
Signed-off-by: Brad Fitzpatrick <[email protected]>
Brad Fitzpatrick 11 месяцев назад
Родитель
Сommit
84c82ac4be
4 измененных файлов с 269 добавлено и 2 удалено
  1. 1 1
      net/dns/manager.go
  2. 1 1
      net/dns/manager_default.go
  3. 181 0
      net/dns/manager_plan9.go
  4. 86 0
      net/dns/manager_plan9_test.go

+ 1 - 1
net/dns/manager.go

@@ -284,7 +284,7 @@ func (m *Manager) compileConfig(cfg Config) (rcfg resolver.Config, ocfg OSConfig
 
 	// Deal with trivial configs first.
 	switch {
-	case !cfg.needsOSResolver():
+	case !cfg.needsOSResolver() || runtime.GOOS == "plan9":
 		// Set search domains, but nothing else. This also covers the
 		// case where cfg is entirely zero, in which case these
 		// configs clear all Tailscale DNS settings.

+ 1 - 1
net/dns/manager_default.go

@@ -1,7 +1,7 @@
 // Copyright (c) Tailscale Inc & AUTHORS
 // SPDX-License-Identifier: BSD-3-Clause
 
-//go:build !linux && !freebsd && !openbsd && !windows && !darwin && !illumos && !solaris
+//go:build !linux && !freebsd && !openbsd && !windows && !darwin && !illumos && !solaris && !plan9
 
 package dns
 

+ 181 - 0
net/dns/manager_plan9.go

@@ -0,0 +1,181 @@
+// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+// TODO: man 6 ndb | grep -e 'suffix.*same line'
+// to detect Russ's https://9fans.topicbox.com/groups/9fans/T9c9d81b5801a0820/ndb-suffix-specific-dns-changes
+
+package dns
+
+import (
+	"bufio"
+	"bytes"
+	"fmt"
+	"io"
+	"net/netip"
+	"os"
+	"regexp"
+	"strings"
+	"unicode"
+
+	"tailscale.com/control/controlknobs"
+	"tailscale.com/health"
+	"tailscale.com/types/logger"
+	"tailscale.com/util/set"
+)
+
+func NewOSConfigurator(logf logger.Logf, ht *health.Tracker, knobs *controlknobs.Knobs, interfaceName string) (OSConfigurator, error) {
+	return &plan9DNSManager{
+		logf:  logf,
+		ht:    ht,
+		knobs: knobs,
+	}, nil
+}
+
+type plan9DNSManager struct {
+	logf  logger.Logf
+	ht    *health.Tracker
+	knobs *controlknobs.Knobs
+}
+
+// netNDBBytesWithoutTailscale returns raw (the contents of /net/ndb) with any
+// Tailscale bits removed.
+func netNDBBytesWithoutTailscale(raw []byte) ([]byte, error) {
+	var ret bytes.Buffer
+	bs := bufio.NewScanner(bytes.NewReader(raw))
+	removeLine := set.Set[string]{}
+	for bs.Scan() {
+		t := bs.Text()
+		if rest, ok := strings.CutPrefix(t, "#tailscaled-added-line:"); ok {
+			removeLine.Add(strings.TrimSpace(rest))
+			continue
+		}
+		trimmed := strings.TrimSpace(t)
+		if removeLine.Contains(trimmed) {
+			removeLine.Delete(trimmed)
+			continue
+		}
+
+		// Also remove any DNS line referencing *.ts.net. This is
+		// Tailscale-specific (and won't work with, say, Headscale), but
+		// the Headscale case will be covered by the #tailscaled-added-line
+		// logic above, assuming the user didn't delete those comments.
+		if (strings.HasPrefix(trimmed, "dns=") || strings.Contains(trimmed, "dnsdomain=")) &&
+			strings.HasSuffix(trimmed, ".ts.net") {
+			continue
+		}
+
+		ret.WriteString(t)
+		ret.WriteByte('\n')
+	}
+	return ret.Bytes(), bs.Err()
+}
+
+// setNDBSuffix adds lines to tsFree (the contents of /net/ndb already cleaned
+// of Tailscale-added lines) to add the optional DNS search domain (e.g.
+// "foo.ts.net") and DNS server to it.
+func setNDBSuffix(tsFree []byte, suffix string) []byte {
+	suffix = strings.TrimSuffix(suffix, ".")
+	if suffix == "" {
+		return tsFree
+	}
+	var buf bytes.Buffer
+	bs := bufio.NewScanner(bytes.NewReader(tsFree))
+	var added []string
+	addLine := func(s string) {
+		added = append(added, strings.TrimSpace(s))
+		buf.WriteString(s)
+	}
+	for bs.Scan() {
+		buf.Write(bs.Bytes())
+		buf.WriteByte('\n')
+
+		t := bs.Text()
+		if suffix != "" && len(added) == 0 && strings.HasPrefix(t, "\tdns=") {
+			addLine(fmt.Sprintf("\tdns=100.100.100.100 suffix=%s\n", suffix))
+			addLine(fmt.Sprintf("\tdnsdomain=%s\n", suffix))
+		}
+	}
+	bufTrim := bytes.TrimLeftFunc(buf.Bytes(), unicode.IsSpace)
+	if len(added) == 0 {
+		return bufTrim
+	}
+	var ret bytes.Buffer
+	for _, s := range added {
+		ret.WriteString("#tailscaled-added-line: ")
+		ret.WriteString(s)
+		ret.WriteString("\n")
+	}
+	ret.WriteString("\n")
+	ret.Write(bufTrim)
+	return ret.Bytes()
+}
+
+func (m *plan9DNSManager) SetDNS(c OSConfig) error {
+	ndbOnDisk, err := os.ReadFile("/net/ndb")
+	if err != nil {
+		return err
+	}
+
+	tsFree, err := netNDBBytesWithoutTailscale(ndbOnDisk)
+	if err != nil {
+		return err
+	}
+
+	var suffix string
+	if len(c.SearchDomains) > 0 {
+		suffix = string(c.SearchDomains[0])
+	}
+
+	newBuf := setNDBSuffix(tsFree, suffix)
+	if !bytes.Equal(newBuf, ndbOnDisk) {
+		if err := os.WriteFile("/net/ndb", newBuf, 0644); err != nil {
+			return fmt.Errorf("writing /net/ndb: %w", err)
+		}
+		if f, err := os.OpenFile("/net/dns", os.O_RDWR, 0); err == nil {
+			if _, err := io.WriteString(f, "refresh\n"); err != nil {
+				f.Close()
+				return fmt.Errorf("/net/dns refresh write: %w", err)
+			}
+			if err := f.Close(); err != nil {
+				return fmt.Errorf("/net/dns refresh close: %w", err)
+			}
+		}
+	}
+
+	return nil
+}
+
+func (m *plan9DNSManager) SupportsSplitDNS() bool { return false }
+
+func (m *plan9DNSManager) Close() error {
+	// TODO(bradfitz): remove the Tailscale bits from /net/ndb ideally
+	return nil
+}
+
+var dnsRegex = regexp.MustCompile(`\bdns=(\d+\.\d+\.\d+\.\d+)\b`)
+
+func (m *plan9DNSManager) GetBaseConfig() (OSConfig, error) {
+	var oc OSConfig
+	f, err := os.Open("/net/ndb")
+	if err != nil {
+		return oc, err
+	}
+	defer f.Close()
+	bs := bufio.NewScanner(f)
+	for bs.Scan() {
+		m := dnsRegex.FindSubmatch(bs.Bytes())
+		if m == nil {
+			continue
+		}
+		addr, err := netip.ParseAddr(string(m[1]))
+		if err != nil {
+			continue
+		}
+		oc.Nameservers = append(oc.Nameservers, addr)
+	}
+	if err := bs.Err(); err != nil {
+		return oc, err
+	}
+
+	return oc, nil
+}

+ 86 - 0
net/dns/manager_plan9_test.go

@@ -0,0 +1,86 @@
+// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+//go:build plan9
+
+package dns
+
+import "testing"
+
+func TestNetNDBBytesWithoutTailscale(t *testing.T) {
+	tests := []struct {
+		name string
+		raw  string
+		want string
+	}{
+		{
+			name: "empty",
+			raw:  "",
+			want: "",
+		},
+		{
+			name: "no-tailscale",
+			raw:  "# This is a comment\nip=10.0.2.15 ipmask=255.255.255.0 ipgw=10.0.2.2\n\tsys=gnot\n",
+			want: "# This is a comment\nip=10.0.2.15 ipmask=255.255.255.0 ipgw=10.0.2.2\n\tsys=gnot\n",
+		},
+		{
+			name: "remove-by-comments",
+			raw:  "# This is a comment\n#tailscaled-added-line: dns=100.100.100.100\nip=10.0.2.15 ipmask=255.255.255.0 ipgw=10.0.2.2\n\tdns=100.100.100.100\n\tsys=gnot\n",
+			want: "# This is a comment\nip=10.0.2.15 ipmask=255.255.255.0 ipgw=10.0.2.2\n\tsys=gnot\n",
+		},
+		{
+			name: "remove-by-ts.net",
+			raw:  "Some line\n\tdns=100.100.100.100 suffix=foo.ts.net\n\tfoo=bar\n",
+			want: "Some line\n\tfoo=bar\n",
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			got, err := netNDBBytesWithoutTailscale([]byte(tt.raw))
+			if err != nil {
+				t.Fatal(err)
+			}
+			if string(got) != tt.want {
+				t.Errorf("GOT:\n%s\n\nWANT:\n%s\n", string(got), tt.want)
+			}
+		})
+	}
+}
+
+func TestSetNDBSuffix(t *testing.T) {
+	tests := []struct {
+		name string
+		raw  string
+		want string
+	}{
+		{
+			name: "empty",
+			raw:  "",
+			want: "",
+		},
+		{
+			name: "set",
+			raw:  "ip=10.0.2.15 ipmask=255.255.255.0 ipgw=10.0.2.2\n\tsys=gnot\n\tdns=100.100.100.100\n\n# foo\n",
+			want: `#tailscaled-added-line: dns=100.100.100.100 suffix=foo.ts.net
+#tailscaled-added-line: dnsdomain=foo.ts.net
+
+ip=10.0.2.15 ipmask=255.255.255.0 ipgw=10.0.2.2
+	sys=gnot
+	dns=100.100.100.100
+	dns=100.100.100.100 suffix=foo.ts.net
+	dnsdomain=foo.ts.net
+
+# foo
+`,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			got := setNDBSuffix([]byte(tt.raw), "foo.ts.net")
+			if string(got) != tt.want {
+				t.Errorf("wrong value\n GOT %q:\n%s\n\nWANT %q:\n%s\n", got, got, tt.want, tt.want)
+			}
+		})
+	}
+
+}