From d5405c66b753a136370c91b3a4c34c76e93a2c97 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Tue, 30 Nov 2021 15:53:34 -0800 Subject: [PATCH] net/tsdial: start of new package to unify all outbound dialing complexity For now this just deletes the net/socks5/tssocks implementation (and the DNSMap stuff from wgengine/netstack) and moves it into net/tsdial. Then initialize a Dialer early in tailscaled, currently only use for the outbound and SOCKS5 proxies. It will be plumbed more later. Notably, it needs to get down into the DNS forwarder for exit node DNS forwading in netstack mode. But it will also absorb all the peerapi setsockopt and netns Dial and tlsdial complexity too. Updates #1713 Change-Id: Ibc6d56ae21a22655b2fa1002d8fc3f2b2ae8b6df Signed-off-by: Brad Fitzpatrick --- cmd/tailscaled/depaware.txt | 6 +- cmd/tailscaled/tailscaled.go | 28 ++++- net/socks5/tssocks/tssocks.go | 90 -------------- net/tsdial/dnsmap.go | 114 ++++++++++++++++++ .../tsdial/dnsmap_test.go | 2 +- net/tsdial/tsdial.go | 61 ++++++++++ .../tailscaled_deps_test_darwin.go | 4 +- .../tailscaled_deps_test_freebsd.go | 4 +- .../integration/tailscaled_deps_test_linux.go | 4 +- .../tailscaled_deps_test_openbsd.go | 4 +- .../tailscaled_deps_test_windows.go | 4 +- wgengine/netstack/netstack.go | 98 +-------------- 12 files changed, 221 insertions(+), 198 deletions(-) delete mode 100644 net/socks5/tssocks/tssocks.go create mode 100644 net/tsdial/dnsmap.go rename wgengine/netstack/netstack_test.go => net/tsdial/dnsmap_test.go (99%) create mode 100644 net/tsdial/tsdial.go diff --git a/cmd/tailscaled/depaware.txt b/cmd/tailscaled/depaware.txt index 96b0bfec0..624316edd 100644 --- a/cmd/tailscaled/depaware.txt +++ b/cmd/tailscaled/depaware.txt @@ -200,11 +200,11 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de tailscale.com/net/packet from tailscale.com/net/tstun+ tailscale.com/net/portmapper from tailscale.com/cmd/tailscaled+ tailscale.com/net/proxymux from tailscale.com/cmd/tailscaled - tailscale.com/net/socks5 from tailscale.com/net/socks5/tssocks - tailscale.com/net/socks5/tssocks from tailscale.com/cmd/tailscaled + tailscale.com/net/socks5 from tailscale.com/cmd/tailscaled tailscale.com/net/stun from tailscale.com/net/netcheck+ tailscale.com/net/tlsdial from tailscale.com/control/controlclient+ tailscale.com/net/tsaddr from tailscale.com/ipn/ipnlocal+ + tailscale.com/net/tsdial from tailscale.com/cmd/tailscaled+ 💣 tailscale.com/net/tshttpproxy from tailscale.com/cmd/tailscaled+ tailscale.com/net/tstun from tailscale.com/cmd/tailscaled+ 💣 tailscale.com/paths from tailscale.com/client/tailscale+ @@ -252,7 +252,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de tailscale.com/wgengine/filter from tailscale.com/control/controlclient+ tailscale.com/wgengine/magicsock from tailscale.com/wgengine+ tailscale.com/wgengine/monitor from tailscale.com/cmd/tailscaled+ - tailscale.com/wgengine/netstack from tailscale.com/cmd/tailscaled+ + tailscale.com/wgengine/netstack from tailscale.com/cmd/tailscaled tailscale.com/wgengine/router from tailscale.com/cmd/tailscaled+ tailscale.com/wgengine/wgcfg from tailscale.com/ipn/ipnlocal+ tailscale.com/wgengine/wgcfg/nmcfg from tailscale.com/ipn/ipnlocal diff --git a/cmd/tailscaled/tailscaled.go b/cmd/tailscaled/tailscaled.go index c9f2a50eb..f9447fb01 100644 --- a/cmd/tailscaled/tailscaled.go +++ b/cmd/tailscaled/tailscaled.go @@ -28,6 +28,7 @@ import ( "syscall" "time" + "inet.af/netaddr" "tailscale.com/ipn" "tailscale.com/ipn/ipnserver" "tailscale.com/logpolicy" @@ -35,12 +36,14 @@ import ( "tailscale.com/net/dns" "tailscale.com/net/netns" "tailscale.com/net/proxymux" - "tailscale.com/net/socks5/tssocks" + "tailscale.com/net/socks5" + "tailscale.com/net/tsdial" "tailscale.com/net/tstun" "tailscale.com/paths" "tailscale.com/safesocket" "tailscale.com/types/flagtype" "tailscale.com/types/logger" + "tailscale.com/types/netmap" "tailscale.com/util/clientmetric" "tailscale.com/util/multierr" "tailscale.com/util/osshare" @@ -324,17 +327,34 @@ func run() error { log.Fatalf("failed to start netstack: %v", err) } + dialer := new(tsdial.Dialer) + if useNetstack { + dialer.UseNetstackForIP = func(ip netaddr.IP) bool { + _, ok := e.PeerForIP(ip) + return ok + } + dialer.NetstackDialTCP = func(ctx context.Context, dst netaddr.IPPort) (net.Conn, error) { + return ns.DialContextTCP(ctx, dst.String()) + } + } + e.AddNetworkMapCallback(func(nm *netmap.NetworkMap) { + dialer.SetDNSMap(tsdial.DNSMapFromNetworkMap(nm)) + }) + if socksListener != nil || httpProxyListener != nil { - srv := tssocks.NewServer(logger.WithPrefix(logf, "socks5: "), e, ns) if httpProxyListener != nil { - hs := &http.Server{Handler: httpProxyHandler(srv.Dialer)} + hs := &http.Server{Handler: httpProxyHandler(dialer.DialContext)} go func() { log.Fatalf("HTTP proxy exited: %v", hs.Serve(httpProxyListener)) }() } if socksListener != nil { + ss := &socks5.Server{ + Logf: logger.WithPrefix(logf, "socks5: "), + Dialer: dialer.DialContext, + } go func() { - log.Fatalf("SOCKS5 server exited: %v", srv.Serve(socksListener)) + log.Fatalf("SOCKS5 server exited: %v", ss.Serve(socksListener)) }() } } diff --git a/net/socks5/tssocks/tssocks.go b/net/socks5/tssocks/tssocks.go deleted file mode 100644 index c8d021ebe..000000000 --- a/net/socks5/tssocks/tssocks.go +++ /dev/null @@ -1,90 +0,0 @@ -// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// Package tssocks is the glue between Tailscale and the net/socks5 package. -package tssocks - -import ( - "context" - "net" - "sync" - - "inet.af/netaddr" - "tailscale.com/net/socks5" - "tailscale.com/types/logger" - "tailscale.com/types/netmap" - "tailscale.com/wgengine" - "tailscale.com/wgengine/netstack" -) - -// NewServer returns a new SOCKS5 server configured to dial out to -// Tailscale addresses. -// -// The returned server is not yet listening. The caller must call -// Serve with a listener. -// -// If ns is non-nil, it is used for dialing when needed. -func NewServer(logf logger.Logf, e wgengine.Engine, ns *netstack.Impl) *socks5.Server { - d := &dialer{ns: ns, eng: e} - e.AddNetworkMapCallback(d.onNewNetmap) - return &socks5.Server{ - Logf: logf, - Dialer: d.DialContext, - } -} - -// dialer is the Tailscale SOCKS5 dialer. -type dialer struct { - ns *netstack.Impl - eng wgengine.Engine - - mu sync.Mutex - dns netstack.DNSMap -} - -func (d *dialer) onNewNetmap(nm *netmap.NetworkMap) { - d.mu.Lock() - defer d.mu.Unlock() - d.dns = netstack.DNSMapFromNetworkMap(nm) -} - -func (d *dialer) resolve(ctx context.Context, addr string) (netaddr.IPPort, error) { - d.mu.Lock() - dns := d.dns - d.mu.Unlock() - return dns.Resolve(ctx, addr) -} - -func (d *dialer) DialContext(ctx context.Context, network, addr string) (net.Conn, error) { - ipp, err := d.resolve(ctx, addr) - if err != nil { - return nil, err - } - if d.ns != nil && d.useNetstackForIP(ipp.IP()) { - return d.ns.DialContextTCP(ctx, ipp.String()) - } - var stdDialer net.Dialer - return stdDialer.DialContext(ctx, network, ipp.String()) -} - -func (d *dialer) useNetstackForIP(ip netaddr.IP) bool { - if d.ns == nil || !d.ns.ProcessLocalIPs { - // If netstack isn't used at all (nil), then obviously don't use it. - // - // But the ProcessLocalIPs check is more subtle: it really means - // whether we should use netstack for incoming traffic to ourselves. - // It's only ever true if we're running in full netstack mode (no TUN), - // so we can also use it as a proxy here for whether TUN is available. - // If it's false, there's tun and OS routes to things we need, - // so we don't want to dial with netstack. - return false - } - // Otherwise, we're in netstack mode, so dial via netstack if there's - // any peer handling that IP (including exit nodes). - // - // Otherwise assume it's something else (e.g. dialing - // google.com:443 via SOCKS) that the caller can dial directly. - _, ok := d.eng.PeerForIP(ip) - return ok -} diff --git a/net/tsdial/dnsmap.go b/net/tsdial/dnsmap.go new file mode 100644 index 000000000..8541d795b --- /dev/null +++ b/net/tsdial/dnsmap.go @@ -0,0 +1,114 @@ +// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package tsdial + +import ( + "context" + "fmt" + "net" + "strconv" + "strings" + + "inet.af/netaddr" + "tailscale.com/types/netmap" + "tailscale.com/util/dnsname" +) + +// DNSMap maps MagicDNS names (both base + FQDN) to their first IP. +// It must not be mutated once created. +// +// Example keys are "foo.domain.tld.beta.tailscale.net" and "foo", +// both without trailing dots. +type DNSMap map[string]netaddr.IP + +func DNSMapFromNetworkMap(nm *netmap.NetworkMap) DNSMap { + ret := make(DNSMap) + suffix := nm.MagicDNSSuffix() + have4 := false + if nm.Name != "" && len(nm.Addresses) > 0 { + ip := nm.Addresses[0].IP() + ret[strings.TrimRight(nm.Name, ".")] = ip + if dnsname.HasSuffix(nm.Name, suffix) { + ret[dnsname.TrimSuffix(nm.Name, suffix)] = ip + } + for _, a := range nm.Addresses { + if a.IP().Is4() { + have4 = true + } + } + } + for _, p := range nm.Peers { + if p.Name == "" { + continue + } + for _, a := range p.Addresses { + ip := a.IP() + if ip.Is4() && !have4 { + continue + } + ret[strings.TrimRight(p.Name, ".")] = ip + if dnsname.HasSuffix(p.Name, suffix) { + ret[dnsname.TrimSuffix(p.Name, suffix)] = ip + } + break + } + } + for _, rec := range nm.DNS.ExtraRecords { + if rec.Type != "" { + continue + } + ip, err := netaddr.ParseIP(rec.Value) + if err != nil { + continue + } + ret[strings.TrimRight(rec.Name, ".")] = ip + } + return ret +} + +// Resolve resolves addr into an IP:port using first the MagicDNS contents +// of m, else using the system resolver. +func (m DNSMap) Resolve(ctx context.Context, addr string) (netaddr.IPPort, error) { + ipp, pippErr := netaddr.ParseIPPort(addr) + if pippErr == nil { + return ipp, nil + } + host, port, err := net.SplitHostPort(addr) + if err != nil { + // addr is malformed. + return netaddr.IPPort{}, err + } + if _, err := netaddr.ParseIP(host); err == nil { + // The host part of addr was an IP, so the netaddr.ParseIPPort above should've + // passed. Must've been a bad port number. Return the original error. + return netaddr.IPPort{}, pippErr + } + port16, err := strconv.ParseUint(port, 10, 16) + if err != nil { + return netaddr.IPPort{}, fmt.Errorf("invalid port in address %q", addr) + } + + // Host is not an IP, so assume it's a DNS name. + + // Try MagicDNS first, otherwise a real DNS lookup. + ip := m[host] + if !ip.IsZero() { + return netaddr.IPPortFrom(ip, uint16(port16)), nil + } + + // TODO(bradfitz): wire up net/dnscache too. + + // No MagicDNS name so try real DNS. + var r net.Resolver + ips, err := r.LookupIP(ctx, "ip", host) + if err != nil { + return netaddr.IPPort{}, err + } + if len(ips) == 0 { + return netaddr.IPPort{}, fmt.Errorf("DNS lookup returned no results for %q", host) + } + ip, _ = netaddr.FromStdIP(ips[0]) + return netaddr.IPPortFrom(ip, uint16(port16)), nil +} diff --git a/wgengine/netstack/netstack_test.go b/net/tsdial/dnsmap_test.go similarity index 99% rename from wgengine/netstack/netstack_test.go rename to net/tsdial/dnsmap_test.go index 9b4792190..e3cfc5e76 100644 --- a/wgengine/netstack/netstack_test.go +++ b/net/tsdial/dnsmap_test.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package netstack +package tsdial import ( "reflect" diff --git a/net/tsdial/tsdial.go b/net/tsdial/tsdial.go new file mode 100644 index 000000000..060c945eb --- /dev/null +++ b/net/tsdial/tsdial.go @@ -0,0 +1,61 @@ +// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package tsdial provides a Dialer type that can dial out of tailscaled. +package tsdial + +import ( + "context" + "errors" + "net" + "sync" + + "inet.af/netaddr" +) + +// Dialer dials out of tailscaled, while taking care of details while +// handling the dozens of edge cases depending on the server mode +// (TUN, netstack), the OS network sandboxing style (macOS/iOS +// Extension, none), user-selected route acceptance prefs, etc. +type Dialer struct { + // UseNetstackForIP if non-nil is whether NetstackDialTCP (if + // it's non-nil) should be used to dial the provided IP. + UseNetstackForIP func(netaddr.IP) bool + + // NetstackDialTCP dials the provided IPPort using netstack. + // If nil, it's not used. + NetstackDialTCP func(context.Context, netaddr.IPPort) (net.Conn, error) + + mu sync.Mutex + dns DNSMap +} + +func (d *Dialer) SetDNSMap(m DNSMap) { + d.mu.Lock() + defer d.mu.Unlock() + d.dns = m +} + +func (d *Dialer) resolve(ctx context.Context, addr string) (netaddr.IPPort, error) { + d.mu.Lock() + dns := d.dns + d.mu.Unlock() + return dns.Resolve(ctx, addr) +} + +func (d *Dialer) DialContext(ctx context.Context, network, addr string) (net.Conn, error) { + ipp, err := d.resolve(ctx, addr) + if err != nil { + return nil, err + } + if d.UseNetstackForIP != nil && d.UseNetstackForIP(ipp.IP()) { + if d.NetstackDialTCP == nil { + return nil, errors.New("Dialer not initialized correctly") + } + return d.NetstackDialTCP(ctx, ipp) + } + // TODO(bradfitz): netns, etc + var stdDialer net.Dialer + return stdDialer.DialContext(ctx, network, ipp.String()) +} diff --git a/tstest/integration/tailscaled_deps_test_darwin.go b/tstest/integration/tailscaled_deps_test_darwin.go index e71463354..c6c7c3c5e 100644 --- a/tstest/integration/tailscaled_deps_test_darwin.go +++ b/tstest/integration/tailscaled_deps_test_darwin.go @@ -23,7 +23,8 @@ import ( _ "tailscale.com/net/netns" _ "tailscale.com/net/portmapper" _ "tailscale.com/net/proxymux" - _ "tailscale.com/net/socks5/tssocks" + _ "tailscale.com/net/socks5" + _ "tailscale.com/net/tsdial" _ "tailscale.com/net/tshttpproxy" _ "tailscale.com/net/tstun" _ "tailscale.com/paths" @@ -32,6 +33,7 @@ import ( _ "tailscale.com/types/flagtype" _ "tailscale.com/types/key" _ "tailscale.com/types/logger" + _ "tailscale.com/types/netmap" _ "tailscale.com/util/clientmetric" _ "tailscale.com/util/multierr" _ "tailscale.com/util/osshare" diff --git a/tstest/integration/tailscaled_deps_test_freebsd.go b/tstest/integration/tailscaled_deps_test_freebsd.go index e71463354..c6c7c3c5e 100644 --- a/tstest/integration/tailscaled_deps_test_freebsd.go +++ b/tstest/integration/tailscaled_deps_test_freebsd.go @@ -23,7 +23,8 @@ import ( _ "tailscale.com/net/netns" _ "tailscale.com/net/portmapper" _ "tailscale.com/net/proxymux" - _ "tailscale.com/net/socks5/tssocks" + _ "tailscale.com/net/socks5" + _ "tailscale.com/net/tsdial" _ "tailscale.com/net/tshttpproxy" _ "tailscale.com/net/tstun" _ "tailscale.com/paths" @@ -32,6 +33,7 @@ import ( _ "tailscale.com/types/flagtype" _ "tailscale.com/types/key" _ "tailscale.com/types/logger" + _ "tailscale.com/types/netmap" _ "tailscale.com/util/clientmetric" _ "tailscale.com/util/multierr" _ "tailscale.com/util/osshare" diff --git a/tstest/integration/tailscaled_deps_test_linux.go b/tstest/integration/tailscaled_deps_test_linux.go index e71463354..c6c7c3c5e 100644 --- a/tstest/integration/tailscaled_deps_test_linux.go +++ b/tstest/integration/tailscaled_deps_test_linux.go @@ -23,7 +23,8 @@ import ( _ "tailscale.com/net/netns" _ "tailscale.com/net/portmapper" _ "tailscale.com/net/proxymux" - _ "tailscale.com/net/socks5/tssocks" + _ "tailscale.com/net/socks5" + _ "tailscale.com/net/tsdial" _ "tailscale.com/net/tshttpproxy" _ "tailscale.com/net/tstun" _ "tailscale.com/paths" @@ -32,6 +33,7 @@ import ( _ "tailscale.com/types/flagtype" _ "tailscale.com/types/key" _ "tailscale.com/types/logger" + _ "tailscale.com/types/netmap" _ "tailscale.com/util/clientmetric" _ "tailscale.com/util/multierr" _ "tailscale.com/util/osshare" diff --git a/tstest/integration/tailscaled_deps_test_openbsd.go b/tstest/integration/tailscaled_deps_test_openbsd.go index e71463354..c6c7c3c5e 100644 --- a/tstest/integration/tailscaled_deps_test_openbsd.go +++ b/tstest/integration/tailscaled_deps_test_openbsd.go @@ -23,7 +23,8 @@ import ( _ "tailscale.com/net/netns" _ "tailscale.com/net/portmapper" _ "tailscale.com/net/proxymux" - _ "tailscale.com/net/socks5/tssocks" + _ "tailscale.com/net/socks5" + _ "tailscale.com/net/tsdial" _ "tailscale.com/net/tshttpproxy" _ "tailscale.com/net/tstun" _ "tailscale.com/paths" @@ -32,6 +33,7 @@ import ( _ "tailscale.com/types/flagtype" _ "tailscale.com/types/key" _ "tailscale.com/types/logger" + _ "tailscale.com/types/netmap" _ "tailscale.com/util/clientmetric" _ "tailscale.com/util/multierr" _ "tailscale.com/util/osshare" diff --git a/tstest/integration/tailscaled_deps_test_windows.go b/tstest/integration/tailscaled_deps_test_windows.go index 442e15e35..d1ef8256c 100644 --- a/tstest/integration/tailscaled_deps_test_windows.go +++ b/tstest/integration/tailscaled_deps_test_windows.go @@ -27,7 +27,8 @@ import ( _ "tailscale.com/net/netns" _ "tailscale.com/net/portmapper" _ "tailscale.com/net/proxymux" - _ "tailscale.com/net/socks5/tssocks" + _ "tailscale.com/net/socks5" + _ "tailscale.com/net/tsdial" _ "tailscale.com/net/tshttpproxy" _ "tailscale.com/net/tstun" _ "tailscale.com/paths" @@ -36,6 +37,7 @@ import ( _ "tailscale.com/types/flagtype" _ "tailscale.com/types/key" _ "tailscale.com/types/logger" + _ "tailscale.com/types/netmap" _ "tailscale.com/util/clientmetric" _ "tailscale.com/util/multierr" _ "tailscale.com/util/osshare" diff --git a/wgengine/netstack/netstack.go b/wgengine/netstack/netstack.go index 8002cf627..86f61de65 100644 --- a/wgengine/netstack/netstack.go +++ b/wgengine/netstack/netstack.go @@ -33,10 +33,10 @@ import ( "inet.af/netstack/waiter" "tailscale.com/net/packet" "tailscale.com/net/tsaddr" + "tailscale.com/net/tsdial" "tailscale.com/net/tstun" "tailscale.com/types/logger" "tailscale.com/types/netmap" - "tailscale.com/util/dnsname" "tailscale.com/wgengine" "tailscale.com/wgengine/filter" "tailscale.com/wgengine/magicsock" @@ -79,7 +79,7 @@ type Impl struct { atomicIsLocalIPFunc atomic.Value // of func(netaddr.IP) bool mu sync.Mutex - dns DNSMap + dns tsdial.DNSMap // connsOpenBySubnetIP keeps track of number of connections open // for each subnet IP temporarily registered on netstack for active // TCP connections, so they can be unregistered when connections are @@ -179,59 +179,10 @@ func (ns *Impl) Start() error { return nil } -// DNSMap maps MagicDNS names (both base + FQDN) to their first IP. -// It should not be mutated once created. -type DNSMap map[string]netaddr.IP - -func DNSMapFromNetworkMap(nm *netmap.NetworkMap) DNSMap { - ret := make(DNSMap) - suffix := nm.MagicDNSSuffix() - have4 := false - if nm.Name != "" && len(nm.Addresses) > 0 { - ip := nm.Addresses[0].IP() - ret[strings.TrimRight(nm.Name, ".")] = ip - if dnsname.HasSuffix(nm.Name, suffix) { - ret[dnsname.TrimSuffix(nm.Name, suffix)] = ip - } - for _, a := range nm.Addresses { - if a.IP().Is4() { - have4 = true - } - } - } - for _, p := range nm.Peers { - if p.Name == "" { - continue - } - for _, a := range p.Addresses { - ip := a.IP() - if ip.Is4() && !have4 { - continue - } - ret[strings.TrimRight(p.Name, ".")] = ip - if dnsname.HasSuffix(p.Name, suffix) { - ret[dnsname.TrimSuffix(p.Name, suffix)] = ip - } - break - } - } - for _, rec := range nm.DNS.ExtraRecords { - if rec.Type != "" { - continue - } - ip, err := netaddr.ParseIP(rec.Value) - if err != nil { - continue - } - ret[strings.TrimRight(rec.Name, ".")] = ip - } - return ret -} - func (ns *Impl) updateDNS(nm *netmap.NetworkMap) { ns.mu.Lock() defer ns.mu.Unlock() - ns.dns = DNSMapFromNetworkMap(nm) + ns.dns = tsdial.DNSMapFromNetworkMap(nm) } func (ns *Impl) addSubnetAddress(ip netaddr.IP) { @@ -347,49 +298,6 @@ func (ns *Impl) updateIPs(nm *netmap.NetworkMap) { } } -// Resolve resolves addr into an IP:port using first the MagicDNS contents -// of m, else using the system resolver. -func (m DNSMap) Resolve(ctx context.Context, addr string) (netaddr.IPPort, error) { - ipp, pippErr := netaddr.ParseIPPort(addr) - if pippErr == nil { - return ipp, nil - } - host, port, err := net.SplitHostPort(addr) - if err != nil { - // addr is malformed. - return netaddr.IPPort{}, err - } - if net.ParseIP(host) != nil { - // The host part of addr was an IP, so the netaddr.ParseIPPort above should've - // passed. Must've been a bad port number. Return the original error. - return netaddr.IPPort{}, pippErr - } - port16, err := strconv.ParseUint(port, 10, 16) - if err != nil { - return netaddr.IPPort{}, fmt.Errorf("invalid port in address %q", addr) - } - - // Host is not an IP, so assume it's a DNS name. - - // Try MagicDNS first, else otherwise a real DNS lookup. - ip := m[host] - if !ip.IsZero() { - return netaddr.IPPortFrom(ip, uint16(port16)), nil - } - - // No MagicDNS name so try real DNS. - var r net.Resolver - ips, err := r.LookupIP(ctx, "ip", host) - if err != nil { - return netaddr.IPPort{}, err - } - if len(ips) == 0 { - return netaddr.IPPort{}, fmt.Errorf("DNS lookup returned no results for %q", host) - } - ip, _ = netaddr.FromStdIP(ips[0]) - return netaddr.IPPortFrom(ip, uint16(port16)), nil -} - func (ns *Impl) DialContextTCP(ctx context.Context, addr string) (*gonet.TCPConn, error) { ns.mu.Lock() dnsMap := ns.dns