cloudflare/cloudflared

Public

mirrored from https://github.com/cloudflare/cloudflaredAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
2020.5.0

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

edgediscovery/allregions/discovery.go

135lines · modecode

1package allregions
2
3import (
4 "context"
5 "crypto/tls"
6 "fmt"
7 "net"
8 "time"
9
10 "github.com/pkg/errors"
11 "github.com/sirupsen/logrus"
12)
13
14const (
15 // Used to discover HA origintunneld servers
16 srvService = "origintunneld"
17 srvProto = "tcp"
18 srvName = "argotunnel.com"
19
20 // Used to fallback to DoT when we can't use the default resolver to
21 // discover HA origintunneld servers (GitHub issue #75).
22 dotServerName = "cloudflare-dns.com"
23 dotServerAddr = "1.1.1.1:853"
24 dotTimeout = time.Duration(15 * time.Second)
25
26 // SRV record resolution TTL
27 resolveEdgeAddrTTL = 1 * time.Hour
28
29 subsystemEdgeAddrResolver = "edgeAddrResolver"
30)
31
32// Redeclare network functions so they can be overridden in tests.
33var (
34 netLookupSRV = net.LookupSRV
35 netLookupIP = net.LookupIP
36)
37
38// If the call to net.LookupSRV fails, try to fall back to DoT from Cloudflare directly.
39//
40// Note: Instead of DoT, we could also have used DoH. Either of these:
41// - directly via the JSON API (https://1.1.1.1/dns-query?ct=application/dns-json&name=_origintunneld._tcp.argotunnel.com&type=srv)
42// - indirectly via `tunneldns.NewUpstreamHTTPS()`
43// But both of these cases miss out on a key feature from the stdlib:
44// "The returned records are sorted by priority and randomized by weight within a priority."
45// (https://golang.org/pkg/net/#Resolver.LookupSRV)
46// Does this matter? I don't know. It may someday. Let's use DoT so we don't need to worry about it.
47// See also: Go feature request for stdlib-supported DoH: https://github.com/golang/go/issues/27552
48var fallbackLookupSRV = lookupSRVWithDOT
49
50var friendlyDNSErrorLines = []string{
51 `Please try the following things to diagnose this issue:`,
52 ` 1. ensure that argotunnel.com is returning "origintunneld" service records.`,
53 ` Run your system's equivalent of: dig srv _origintunneld._tcp.argotunnel.com`,
54 ` 2. ensure that your DNS resolver is not returning compressed SRV records.`,
55 ` See GitHub issue https://github.com/golang/go/issues/27546`,
56 ` For example, you could use Cloudflare's 1.1.1.1 as your resolver:`,
57 ` https://developers.cloudflare.com/1.1.1.1/setting-up-1.1.1.1/`,
58}
59
60// EdgeDiscovery implements HA service discovery lookup.
61func edgeDiscovery(logger *logrus.Entry) ([][]*net.TCPAddr, error) {
62 _, addrs, err := netLookupSRV(srvService, srvProto, srvName)
63 if err != nil {
64 _, fallbackAddrs, fallbackErr := fallbackLookupSRV(srvService, srvProto, srvName)
65 if fallbackErr != nil || len(fallbackAddrs) == 0 {
66 // use the original DNS error `err` in messages, not `fallbackErr`
67 logger.Errorln("Error looking up Cloudflare edge IPs: the DNS query failed:", err)
68 for _, s := range friendlyDNSErrorLines {
69 logger.Errorln(s)
70 }
71 return nil, errors.Wrapf(err, "Could not lookup srv records on _%v._%v.%v", srvService, srvProto, srvName)
72 }
73 // Accept the fallback results and keep going
74 addrs = fallbackAddrs
75 }
76
77 var resolvedIPsPerCNAME [][]*net.TCPAddr
78 for _, addr := range addrs {
79 ips, err := resolveSRVToTCP(addr)
80 if err != nil {
81 return nil, err
82 }
83 resolvedIPsPerCNAME = append(resolvedIPsPerCNAME, ips)
84 }
85
86 return resolvedIPsPerCNAME, nil
87}
88
89func lookupSRVWithDOT(service, proto, name string) (cname string, addrs []*net.SRV, err error) {
90 // Inspiration: https://github.com/artyom/dot/blob/master/dot.go
91 r := &net.Resolver{
92 PreferGo: true,
93 Dial: func(ctx context.Context, _ string, _ string) (net.Conn, error) {
94 var dialer net.Dialer
95 conn, err := dialer.DialContext(ctx, "tcp", dotServerAddr)
96 if err != nil {
97 return nil, err
98 }
99 tlsConfig := &tls.Config{ServerName: dotServerName}
100 return tls.Client(conn, tlsConfig), nil
101 },
102 }
103 ctx, cancel := context.WithTimeout(context.Background(), dotTimeout)
104 defer cancel()
105 return r.LookupSRV(ctx, srvService, srvProto, srvName)
106}
107
108func resolveSRVToTCP(srv *net.SRV) ([]*net.TCPAddr, error) {
109 ips, err := netLookupIP(srv.Target)
110 if err != nil {
111 return nil, errors.Wrapf(err, "Couldn't resolve SRV record %v", srv)
112 }
113 if len(ips) == 0 {
114 return nil, fmt.Errorf("SRV record %v had no IPs", srv)
115 }
116 addrs := make([]*net.TCPAddr, len(ips))
117 for i, ip := range ips {
118 addrs[i] = &net.TCPAddr{IP: ip, Port: int(srv.Port)}
119 }
120 return addrs, nil
121}
122
123// ResolveAddrs resolves TCP address given a list of addresses. Address can be a hostname, however, it will return at most one
124// of the hostname's IP addresses
125func ResolveAddrs(addrs []string) ([]*net.TCPAddr, error) {
126 var tcpAddrs []*net.TCPAddr
127 for _, addr := range addrs {
128 tcpAddr, err := net.ResolveTCPAddr("tcp", addr)
129 if err != nil {
130 return nil, err
131 }
132 tcpAddrs = append(tcpAddrs, tcpAddr)
133 }
134 return tcpAddrs, nil
135}
136