From d22e214000d7fbcd9ab62be598909d7193371076 Mon Sep 17 00:00:00 2001 From: Nick Vollmar Date: Thu, 7 Mar 2019 21:12:24 -0500 Subject: [PATCH] TUN-1522: If we can't get SRV from default resolver, get them from 1.1.1.1 DoT --- origin/discovery.go | 65 ++++++++++++++++++++++++++++++++++++++++++-- origin/supervisor.go | 4 +-- origin/tunnel.go | 2 +- 3 files changed, 66 insertions(+), 5 deletions(-) diff --git a/origin/discovery.go b/origin/discovery.go index 388b5b1f..6f2fbfad 100644 --- a/origin/discovery.go +++ b/origin/discovery.go @@ -1,8 +1,14 @@ package origin import ( + "context" + "crypto/tls" "fmt" "net" + "time" + + "github.com/pkg/errors" + log "github.com/sirupsen/logrus" ) const ( @@ -10,9 +16,25 @@ const ( srvService = "warp" srvProto = "tcp" srvName = "cloudflarewarp.com" + + // Used to fallback to DoT when we can't use the default resolver to + // discover HA Warp servers (GitHub issue #75). + dotServerName = "cloudflare-dns.com" + dotServerAddr = "1.1.1.1:853" + dotTimeout = time.Duration(15 * time.Second) ) -func ResolveEdgeIPs(addresses []string) ([]*net.TCPAddr, error) { +var friendlyDNSErrorLines = []string{ + `Please try the following things to diagnose this issue:`, + ` 1. ensure that cloudflarewarp.com is returning "warp" service records.`, + ` Run your system's equivalent of: dig srv _warp._tcp.cloudflarewarp.com`, + ` 2. ensure that your DNS resolver is not returning compressed SRV records.`, + ` See GitHub issue https://github.com/golang/go/issues/27546`, + ` For example, you could use Cloudflare's 1.1.1.1 as your resolver:`, + ` https://developers.cloudflare.com/1.1.1.1/setting-up-1.1.1.1/`, +} + +func ResolveEdgeIPs(logger *log.Logger, addresses []string) ([]*net.TCPAddr, error) { if len(addresses) > 0 { var tcpAddrs []*net.TCPAddr for _, address := range addresses { @@ -28,7 +50,30 @@ func ResolveEdgeIPs(addresses []string) ([]*net.TCPAddr, error) { // HA service discovery lookup _, addrs, err := net.LookupSRV(srvService, srvProto, srvName) if err != nil { - return nil, err + // Try to fall back to DoT from Cloudflare directly. + // + // Note: Instead of DoT, we could also have used DoH. Either of these: + // - directly via the JSON API (https://1.1.1.1/dns-query?ct=application/dns-json&name=_warp._tcp.cloudflarewarp.com&type=srv) + // - indirectly via `tunneldns.NewUpstreamHTTPS()` + // But both of these cases miss out on a key feature from the stdlib: + // "The returned records are sorted by priority and randomized by weight within a priority." + // (https://golang.org/pkg/net/#Resolver.LookupSRV) + // Does this matter? I don't know. It may someday. Let's use DoT so we don't need to worry about it. + // See also: Go feature request for stdlib-supported DoH: https://github.com/golang/go/issues/27552 + r := fallbackResolver(dotServerName, dotServerAddr) + ctx, cancel := context.WithTimeout(context.Background(), dotTimeout) + defer cancel() + _, fallbackAddrs, fallbackErr := r.LookupSRV(ctx, srvService, srvProto, srvName) + if fallbackErr != nil || len(fallbackAddrs) == 0 { + // use the original DNS error `err` in messages, not `fallbackErr` + logger.Errorln("Error looking up Cloudflare edge IPs: the DNS query failed:", err) + for _, s := range friendlyDNSErrorLines { + logger.Errorln(s) + } + return nil, errors.Wrap(err, "Could not lookup srv records on _warp._tcp.cloudflarewarp.com") + } + // Accept the fallback results and keep going + addrs = fallbackAddrs } var resolvedIPsPerCNAME [][]*net.TCPAddr var lookupErr error @@ -80,3 +125,19 @@ func FlattenServiceIPs(ipsByService [][]*net.TCPAddr) []*net.TCPAddr { } return result } + +// Inspiration: https://github.com/artyom/dot/blob/master/dot.go +func fallbackResolver(serverName, serverAddress string) *net.Resolver { + return &net.Resolver{ + PreferGo: true, + Dial: func(ctx context.Context, _ string, _ string) (net.Conn, error) { + var dialer net.Dialer + conn, err := dialer.DialContext(ctx, "tcp", serverAddress) + if err != nil { + return nil, err + } + tlsConfig := &tls.Config{ServerName: serverName} + return tls.Client(conn, tlsConfig), nil + }, + } +} diff --git a/origin/supervisor.go b/origin/supervisor.go index 5f60a392..53042f70 100644 --- a/origin/supervisor.go +++ b/origin/supervisor.go @@ -124,7 +124,7 @@ func (s *Supervisor) Run(ctx context.Context, connectedSignal *signal.Signal, u func (s *Supervisor) initialize(ctx context.Context, connectedSignal *signal.Signal, u uuid.UUID) error { logger := s.config.Logger - edgeIPs, err := ResolveEdgeIPs(s.config.EdgeAddrs) + edgeIPs, err := ResolveEdgeIPs(logger, s.config.EdgeAddrs) if err != nil { logger.Infof("ResolveEdgeIPs err") return err @@ -223,7 +223,7 @@ func (s *Supervisor) refreshEdgeIPs() { } s.resolverC = make(chan resolveResult) go func() { - edgeIPs, err := ResolveEdgeIPs(s.config.EdgeAddrs) + edgeIPs, err := ResolveEdgeIPs(s.config.Logger, s.config.EdgeAddrs) s.resolverC <- resolveResult{edgeIPs: edgeIPs, err: err} }() } diff --git a/origin/tunnel.go b/origin/tunnel.go index 17d10383..e728ee8b 100644 --- a/origin/tunnel.go +++ b/origin/tunnel.go @@ -146,7 +146,7 @@ func StartTunnelDaemon(config *TunnelConfig, shutdownC <-chan struct{}, connecte if config.HAConnections > 1 { return NewSupervisor(config).Run(ctx, connectedSignal, u) } else { - addrs, err := ResolveEdgeIPs(config.EdgeAddrs) + addrs, err := ResolveEdgeIPs(config.Logger, config.EdgeAddrs) if err != nil { return err }