This commit is contained in:
Zero Cho 2024-04-24 13:58:25 -05:00 committed by GitHub
commit 405a5a1512
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 222 additions and 3 deletions

View File

@ -548,6 +548,8 @@ func getLocalBackend(ctx context.Context, logf logger.Logf, logID logid.PublicID
}
}
if socksListener != nil || httpProxyListener != nil {
dialer.UserDialCustomResolver = dns.Quad100Resolver(ctx, sys.DNSManager.Get())
var addrs []string
if httpProxyListener != nil {
hs := &http.Server{Handler: httpProxyHandler(dialer.UserDial)}

View File

@ -93,6 +93,12 @@ func (m *Manager) Set(cfg Config) error {
if err := m.resolver.SetConfig(rcfg); err != nil {
return err
}
if err != nil {
m.logf("err: %s", err)
return err
}
if err := m.os.SetDNS(ocfg); err != nil {
health.SetDNSOSHealth(err)
return err
@ -249,7 +255,7 @@ func (m *Manager) compileConfig(cfg Config) (rcfg resolver.Config, ocfg OSConfig
// builds.
} else {
health.SetDNSOSHealth(err)
return resolver.Config{}, OSConfig{}, err
return rcfg, OSConfig{}, err
}
}

85
net/dns/quad100.go Normal file
View File

@ -0,0 +1,85 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
package dns
import (
"bytes"
"context"
"fmt"
"net"
"net/netip"
"time"
)
type Quad100conn struct {
Ctx context.Context
DnsManager *Manager
rbuf bytes.Buffer
}
var (
_ net.Conn = (*Quad100conn)(nil)
_ net.PacketConn = (*Quad100conn)(nil) // be a PacketConn to change net.Resolver semantics
)
func (*Quad100conn) Close() error { return nil }
func (*Quad100conn) LocalAddr() net.Addr { return todoAddr{} }
func (*Quad100conn) RemoteAddr() net.Addr { return todoAddr{} }
func (*Quad100conn) SetDeadline(t time.Time) error { return nil }
func (*Quad100conn) SetReadDeadline(t time.Time) error { return nil }
func (*Quad100conn) SetWriteDeadline(t time.Time) error { return nil }
func (c *Quad100conn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
return c.Write(p)
}
func (c *Quad100conn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
n, err = c.Read(p)
return n, todoAddr{}, err
}
func (c *Quad100conn) Read(p []byte) (n int, err error) {
return c.rbuf.Read(p)
}
func (c *Quad100conn) Write(packet []byte) (n int, err error) {
pkt, err := c.DnsManager.Query(c.Ctx, packet, "tcp", netip.AddrPort{})
if err != nil {
return 0, err
}
c.rbuf.Write(pkt)
return len(packet), nil
}
type todoAddr struct{}
func (todoAddr) Network() string { return "unused" }
func (todoAddr) String() string { return "unused-todoAddr" }
// Quad100Resolver sets up a DNS resolver that uses the Quad100Conn to send DNS
// queries to MagicDNS.
func Quad100Resolver(ctx context.Context, mgr *Manager) func(host string) (netip.Addr, error) {
return func(host string) (netip.Addr, error) {
var r net.Resolver
r.PreferGo = true
r.Dial = func(ctx context.Context, network, address string) (net.Conn, error) {
return &Quad100conn{
Ctx: ctx,
DnsManager: mgr,
}, nil
}
ips, err := r.LookupIP(ctx, "ip4", host)
if err != nil {
mgr.logf("dns lookup err: %v", err)
return netip.Addr{}, err
}
if len(ips) == 0 {
return netip.Addr{}, fmt.Errorf("DNS lookup returned no results for %q", host)
}
ip, _ := netip.AddrFromSlice(ips[0])
return ip, nil
}
}

111
net/dns/quad100_test.go Normal file
View File

@ -0,0 +1,111 @@
package dns
import (
"context"
"net"
"testing"
dns "golang.org/x/net/dns/dnsmessage"
"tailscale.com/net/tsdial"
"tailscale.com/util/dnsname"
)
func TestQuad100Conn(t *testing.T) {
f := fakeOSConfigurator{
SplitDNS: true,
BaseConfig: OSConfig{
Nameservers: mustIPs("8.8.8.8"),
SearchDomains: fqdns("coffee.shop"),
},
}
m := NewManager(t.Logf, &f, nil, new(tsdial.Dialer), nil, nil)
m.resolver.TestOnlySetHook(f.SetResolver)
m.Set(Config{
Hosts: hosts(
"dave.ts.net.", "1.2.3.4",
"matt.ts.net.", "2.3.4.5"),
Routes: upstreams("ts.net", ""),
SearchDomains: fqdns("tailscale.com", "universe.tf"),
})
defer m.Down()
q100 := &Quad100conn{
Ctx: context.Background(),
DnsManager: m,
}
defer q100.Close()
var b []byte
domain := dnsname.FQDN("matt.ts.net.")
// Send a query
b = mkDNSRequest(domain, dns.TypeA, addEDNS)
_, err := q100.Write(b)
if err != nil {
t.Fatal(err)
}
resp := make([]byte, 100)
if _, err := q100.Read(resp); err != nil {
t.Fatalf("reading data: %v", err)
}
var parser dns.Parser
if _, err := parser.Start(resp); err != nil {
t.Errorf("parser.Start() failed: %v", err)
}
_, err = parser.Question()
if err != nil {
t.Errorf("parser.Question(): %v", err)
}
if err := parser.SkipAllQuestions(); err != nil {
t.Errorf("parser.SkipAllQuestions(): %v", err)
}
ah, err := parser.AnswerHeader()
if err != nil {
t.Errorf("parser.AnswerHeader(): %v", err)
}
if ah.Type != dns.TypeA {
t.Errorf("unexpected answer type: got %v, want %v", ah.Type, dns.TypeA)
}
res, err := parser.AResource()
if err != nil {
t.Errorf("parser.AResource(): %v", err)
}
if net.IP(res.A[:]).String() != "2.3.4.5" {
t.Fatalf("dns query did not return expected result")
}
}
func TestQuad100Resolver(t *testing.T) {
f := fakeOSConfigurator{
SplitDNS: true,
BaseConfig: OSConfig{
Nameservers: mustIPs("8.8.8.8"),
SearchDomains: fqdns("coffee.shop"),
},
}
m := NewManager(t.Logf, &f, nil, new(tsdial.Dialer), nil, nil)
m.resolver.TestOnlySetHook(f.SetResolver)
m.Set(Config{
Hosts: hosts(
"dave.ts.net.", "1.2.3.4",
"matt.ts.net.", "2.3.4.5"),
Routes: upstreams("ts.net", ""),
SearchDomains: fqdns("tailscale.com", "universe.tf"),
})
defer m.Down()
resolver := Quad100Resolver(context.Background(), m)
ip, err := resolver("matt.ts.net")
if err != nil {
t.Errorf("could not resolve host: %v", err)
}
if ip.String() != "2.3.4.5" {
t.Fatalf("dns query did not return expected result")
}
}

View File

@ -720,6 +720,8 @@ func (f *forwarder) sendTCP(ctx context.Context, fq *forwardQuery, rr resolverAn
ctx, cancel := context.WithTimeout(ctx, tcpQueryTimeout)
defer cancel()
// Keeping this as SystemDial per discussion in https://github.com/tailscale/tailscale/pull/10380
// This would mean SplitDNS via upstreams only reachable via UserDial would not work currently.
conn, err := f.dialer.SystemDial(ctx, tcpFam, ipp.String())
if err != nil {
return nil, err

View File

@ -43,6 +43,10 @@ type Dialer struct {
// If nil, it's not used.
NetstackDialTCP func(context.Context, netip.AddrPort) (net.Conn, error)
// UserDialCustomResolver if non-nil is invoked by UserDial to resolve a destination address.
// It is invoked after the in-memory tailnet machine map.
UserDialCustomResolver func(string) (netip.Addr, error)
peerClientOnce sync.Once
peerClient *http.Client
@ -255,16 +259,25 @@ func (d *Dialer) userDialResolve(ctx context.Context, network, addr string) (net
return ipp, err
}
// Otherwise, hit the network.
// TODO(bradfitz): wire up net/dnscache too.
// Try tsdns resolver next to resolve SplitDNS
host, port, err := splitHostPort(addr)
if err != nil {
// addr is malformed.
return netip.AddrPort{}, err
}
if d.UserDialCustomResolver != nil {
ip, err := d.UserDialCustomResolver(host)
if err == nil {
ipp := netip.AddrPortFrom(ip, port)
return ipp, err
}
}
// Otherwise, hit the network.
var r net.Resolver
if exitDNSDoH != "" && runtime.GOOS != "windows" { // Windows: https://github.com/golang/go/issues/33097
r.PreferGo = true