This requires the rsc/plan9 ndb DNS changes for now: https://9fans.topicbox.com/groups/9fans/T9c9d81b5801a0820/ndb-suffix-specific-dns-changesmaine8c148ff091d0642ae49Updates #5794 Change-Id: I0e242c1fe7bb4404e23604e03a31f89f0d18e70d Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
parent
5e305032a9
commit
84c82ac4be
@ -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 |
||||
} |
||||
@ -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) |
||||
} |
||||
}) |
||||
} |
||||
|
||||
} |
||||
Loading…
Reference in new issue