mirror of
https://github.com/tailscale/tailscale.git
synced 2025-04-01 22:12:31 +00:00
net/dns: support resolving SplitDNS via dns.Manager for userspace-networking
- Add a Quad100conn for the DNS resolver. This uses the dns.Manager to perform the resolution and a fake IO interface for it. - Add a UserDialCustomResolverDial to Dialer so we can override the IP resolution in UserDial, which is used by proxy handlers, to use the aforementioned Quad100conn - Update the dns Forwarder to use UserDial instead so it can reach into the tailnet - Change Dialer.UserDial to use the quad100 resolver after MagicDNS Updates #4677 Updates #9619 Signed-off-by: Samy Djemaï <53857555+SamyDjemai@users.noreply.github.com> Co-authored-by: Zero Cho <itszero@gmail.com> Co-authored-by: Matt Morrison <matt.morrison@ditto.live>
This commit is contained in:
parent
f2f7fd12eb
commit
4f56d5fdec
@ -577,6 +577,8 @@ func getLocalBackend(ctx context.Context, logf logger.Logf, logID logid.PublicID
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if socksListener != nil || httpProxyListener != nil {
|
if socksListener != nil || httpProxyListener != nil {
|
||||||
|
dialer.UserDialCustomResolverDial = dns.Quad100ResolverDial(ctx, sys.DNSManager.Get())
|
||||||
|
|
||||||
var addrs []string
|
var addrs []string
|
||||||
if httpProxyListener != nil {
|
if httpProxyListener != nil {
|
||||||
hs := &http.Server{Handler: httpProxyHandler(dialer.UserDial)}
|
hs := &http.Server{Handler: httpProxyHandler(dialer.UserDial)}
|
||||||
|
67
net/dns/quad100.go
Normal file
67
net/dns/quad100.go
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
// Copyright (c) Tailscale Inc & AUTHORS
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
"net/netip"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ManagerConn struct {
|
||||||
|
Ctx context.Context
|
||||||
|
DnsManager *Manager
|
||||||
|
|
||||||
|
rbuf bytes.Buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
_ net.Conn = (*ManagerConn)(nil)
|
||||||
|
_ net.PacketConn = (*ManagerConn)(nil) // be a PacketConn to change net.Resolver semantics
|
||||||
|
)
|
||||||
|
|
||||||
|
func (*ManagerConn) Close() error { return nil }
|
||||||
|
func (*ManagerConn) LocalAddr() net.Addr { return todoAddr{} }
|
||||||
|
func (*ManagerConn) RemoteAddr() net.Addr { return todoAddr{} }
|
||||||
|
func (*ManagerConn) SetDeadline(t time.Time) error { return nil }
|
||||||
|
func (*ManagerConn) SetReadDeadline(t time.Time) error { return nil }
|
||||||
|
func (*ManagerConn) SetWriteDeadline(t time.Time) error { return nil }
|
||||||
|
|
||||||
|
func (c *ManagerConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
|
||||||
|
return c.Write(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ManagerConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
|
||||||
|
n, err = c.Read(p)
|
||||||
|
return n, todoAddr{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ManagerConn) Read(p []byte) (n int, err error) {
|
||||||
|
return c.rbuf.Read(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ManagerConn) 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" }
|
||||||
|
|
||||||
|
func Quad100ResolverDial(ctx context.Context, mgr *Manager) func(ctx context.Context, network, address string) (net.Conn, error) {
|
||||||
|
return func(ctx context.Context, network, address string) (net.Conn, error) {
|
||||||
|
return &ManagerConn{
|
||||||
|
Ctx: ctx,
|
||||||
|
DnsManager: mgr,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
}
|
115
net/dns/quad100_test.go
Normal file
115
net/dns/quad100_test.go
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
// Copyright (c) Tailscale Inc & AUTHORS
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
dns "golang.org/x/net/dns/dnsmessage"
|
||||||
|
"tailscale.com/health"
|
||||||
|
"tailscale.com/net/netmon"
|
||||||
|
"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, new(health.Tracker), tsdial.NewDialer(netmon.NewStatic()), 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 := &ManagerConn{
|
||||||
|
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 TestQuad100ResolverDial(t *testing.T) {
|
||||||
|
f := fakeOSConfigurator{
|
||||||
|
SplitDNS: true,
|
||||||
|
BaseConfig: OSConfig{
|
||||||
|
Nameservers: mustIPs("8.8.8.8"),
|
||||||
|
SearchDomains: fqdns("coffee.shop"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
m := NewManager(t.Logf, &f, new(health.Tracker), tsdial.NewDialer(netmon.NewStatic()), 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()
|
||||||
|
|
||||||
|
var r net.Resolver
|
||||||
|
r.Dial = Quad100ResolverDial(context.Background(), m)
|
||||||
|
|
||||||
|
ips, err := r.LookupHost(context.Background(), "matt.ts.net")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("could not resolve host: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ips[0] != "2.3.4.5" {
|
||||||
|
t.Fatalf("dns query did not return expected result")
|
||||||
|
}
|
||||||
|
}
|
@ -63,6 +63,10 @@ type Dialer struct {
|
|||||||
// If nil, it's not used.
|
// If nil, it's not used.
|
||||||
NetstackDialUDP func(context.Context, netip.AddrPort) (net.Conn, error)
|
NetstackDialUDP func(context.Context, netip.AddrPort) (net.Conn, error)
|
||||||
|
|
||||||
|
// UserDialCustomResolverDial if non-nil is invoked by UserDial to resolve a destination address.
|
||||||
|
// It is invoked after the in-memory tailnet machine map.
|
||||||
|
UserDialCustomResolverDial func(context.Context, string, string) (net.Conn, error)
|
||||||
|
|
||||||
peerClientOnce sync.Once
|
peerClientOnce sync.Once
|
||||||
peerClient *http.Client
|
peerClient *http.Client
|
||||||
|
|
||||||
@ -308,16 +312,17 @@ func (d *Dialer) userDialResolve(ctx context.Context, network, addr string) (net
|
|||||||
return ipp, err
|
return ipp, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise, hit the network.
|
|
||||||
|
|
||||||
// TODO(bradfitz): wire up net/dnscache too.
|
// TODO(bradfitz): wire up net/dnscache too.
|
||||||
|
|
||||||
|
// Try tsdns resolver next to resolve SplitDNS
|
||||||
host, port, err := splitHostPort(addr)
|
host, port, err := splitHostPort(addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// addr is malformed.
|
// addr is malformed.
|
||||||
return netip.AddrPort{}, err
|
return netip.AddrPort{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Otherwise, hit the network.
|
||||||
|
|
||||||
var r net.Resolver
|
var r net.Resolver
|
||||||
if exitDNSDoH != "" && runtime.GOOS != "windows" { // Windows: https://github.com/golang/go/issues/33097
|
if exitDNSDoH != "" && runtime.GOOS != "windows" { // Windows: https://github.com/golang/go/issues/33097
|
||||||
r.PreferGo = true
|
r.PreferGo = true
|
||||||
@ -329,6 +334,9 @@ func (d *Dialer) userDialResolve(ctx context.Context, network, addr string) (net
|
|||||||
dnsCache: d.dnsCache,
|
dnsCache: d.dnsCache,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
} else if d.UserDialCustomResolverDial != nil {
|
||||||
|
r.PreferGo = true
|
||||||
|
r.Dial = d.UserDialCustomResolverDial
|
||||||
}
|
}
|
||||||
|
|
||||||
ips, err := r.LookupIP(ctx, ipNetOfNetwork(network), host)
|
ips, err := r.LookupIP(ctx, ipNetOfNetwork(network), host)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user