cloudflare/cloudflared

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
2019.10.0

Branches

Tags

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

Clone

HTTPS

Download ZIP

connection/discovery.go

254lines · modecode

1package connection
2
3import (
4 "context"
5 "crypto/tls"
6 "fmt"
7 "net"
8 "sync"
9 "time"
10
11 "github.com/pkg/errors"
12 "github.com/sirupsen/logrus"
13)
14
15const (
16 // Used to discover HA origintunneld servers
17 srvService = "origintunneld"
18 srvProto = "tcp"
19 srvName = "argotunnel.com"
20
21 // Used to fallback to DoT when we can't use the default resolver to
22 // discover HA origintunneld servers (GitHub issue #75).
23 dotServerName = "cloudflare-dns.com"
24 dotServerAddr = "1.1.1.1:853"
25 dotTimeout = time.Duration(15 * time.Second)
26
27 // SRV record resolution TTL
28 resolveEdgeAddrTTL = 1 * time.Hour
29)
30
31var friendlyDNSErrorLines = []string{
32 `Please try the following things to diagnose this issue:`,
33 ` 1. ensure that argotunnel.com is returning "origintunneld" service records.`,
34 ` Run your system's equivalent of: dig srv _origintunneld._tcp.argotunnel.com`,
35 ` 2. ensure that your DNS resolver is not returning compressed SRV records.`,
36 ` See GitHub issue https://github.com/golang/go/issues/27546`,
37 ` For example, you could use Cloudflare's 1.1.1.1 as your resolver:`,
38 ` https://developers.cloudflare.com/1.1.1.1/setting-up-1.1.1.1/`,
39}
40
41// EdgeServiceDiscoverer is an interface for looking up Cloudflare's edge network addresses
42type EdgeServiceDiscoverer interface {
43 // Addr returns an address to connect to cloudflare's edge network
44 Addr() *net.TCPAddr
45 // AvailableAddrs returns the number of unique addresses
46 AvailableAddrs() uint8
47 // Refresh rediscover Cloudflare's edge network addresses
48 Refresh() error
49}
50
51// EdgeAddrResolver discovers the addresses of Cloudflare's edge network through SRV record.
52// It implements EdgeServiceDiscoverer interface
53type EdgeAddrResolver struct {
54 sync.Mutex
55 // Addrs to connect to cloudflare's edge network
56 addrs []*net.TCPAddr
57 // index of the next element to use in addrs
58 nextAddrIndex int
59 logger *logrus.Entry
60}
61
62func NewEdgeAddrResolver(logger *logrus.Logger) (EdgeServiceDiscoverer, error) {
63 r := &EdgeAddrResolver{
64 logger: logger.WithField("subsystem", " edgeAddrResolver"),
65 }
66 if err := r.Refresh(); err != nil {
67 return nil, err
68 }
69 return r, nil
70}
71
72func (r *EdgeAddrResolver) Addr() *net.TCPAddr {
73 r.Lock()
74 defer r.Unlock()
75 addr := r.addrs[r.nextAddrIndex]
76 r.nextAddrIndex = (r.nextAddrIndex + 1) % len(r.addrs)
77 return addr
78}
79
80func (r *EdgeAddrResolver) AvailableAddrs() uint8 {
81 r.Lock()
82 defer r.Unlock()
83 return uint8(len(r.addrs))
84}
85
86func (r *EdgeAddrResolver) Refresh() error {
87 newAddrs, err := EdgeDiscovery(r.logger)
88 if err != nil {
89 return err
90 }
91 r.Lock()
92 defer r.Unlock()
93 r.addrs = newAddrs
94 r.nextAddrIndex = 0
95 return nil
96}
97
98// HA service discovery lookup
99func EdgeDiscovery(logger *logrus.Entry) ([]*net.TCPAddr, error) {
100 _, addrs, err := net.LookupSRV(srvService, srvProto, srvName)
101 if err != nil {
102 // Try to fall back to DoT from Cloudflare directly.
103 //
104 // Note: Instead of DoT, we could also have used DoH. Either of these:
105 // - directly via the JSON API (https://1.1.1.1/dns-query?ct=application/dns-json&name=_origintunneld._tcp.argotunnel.com&type=srv)
106 // - indirectly via `tunneldns.NewUpstreamHTTPS()`
107 // But both of these cases miss out on a key feature from the stdlib:
108 // "The returned records are sorted by priority and randomized by weight within a priority."
109 // (https://golang.org/pkg/net/#Resolver.LookupSRV)
110 // Does this matter? I don't know. It may someday. Let's use DoT so we don't need to worry about it.
111 // See also: Go feature request for stdlib-supported DoH: https://github.com/golang/go/issues/27552
112 r := fallbackResolver(dotServerName, dotServerAddr)
113 ctx, cancel := context.WithTimeout(context.Background(), dotTimeout)
114 defer cancel()
115 _, fallbackAddrs, fallbackErr := r.LookupSRV(ctx, srvService, srvProto, srvName)
116 if fallbackErr != nil || len(fallbackAddrs) == 0 {
117 // use the original DNS error `err` in messages, not `fallbackErr`
118 logger.Errorln("Error looking up Cloudflare edge IPs: the DNS query failed:", err)
119 for _, s := range friendlyDNSErrorLines {
120 logger.Errorln(s)
121 }
122 return nil, errors.Wrap(err, "Could not lookup srv records on _origintunneld._tcp.argotunnel.com")
123 }
124 // Accept the fallback results and keep going
125 addrs = fallbackAddrs
126 }
127 var resolvedIPsPerCNAME [][]*net.TCPAddr
128 var lookupErr error
129 for _, addr := range addrs {
130 ips, err := resolveSRVToTCP(addr)
131 if err != nil || len(ips) == 0 {
132 // don't return early, we might be able to resolve other addresses
133 lookupErr = err
134 continue
135 }
136 resolvedIPsPerCNAME = append(resolvedIPsPerCNAME, ips)
137 }
138 ips := flattenServiceIPs(resolvedIPsPerCNAME)
139 if lookupErr == nil && len(ips) == 0 {
140 return nil, fmt.Errorf("Unknown service discovery error")
141 }
142 return ips, lookupErr
143}
144
145func resolveSRVToTCP(srv *net.SRV) ([]*net.TCPAddr, error) {
146 ips, err := net.LookupIP(srv.Target)
147 if err != nil {
148 return nil, err
149 }
150 addrs := make([]*net.TCPAddr, len(ips))
151 for i, ip := range ips {
152 addrs[i] = &net.TCPAddr{IP: ip, Port: int(srv.Port)}
153 }
154 return addrs, nil
155}
156
157// FlattenServiceIPs transposes and flattens the input slices such that the
158// first element of the n inner slices are the first n elements of the result.
159func flattenServiceIPs(ipsByService [][]*net.TCPAddr) []*net.TCPAddr {
160 var result []*net.TCPAddr
161 for len(ipsByService) > 0 {
162 filtered := ipsByService[:0]
163 for _, ips := range ipsByService {
164 if len(ips) == 0 {
165 // sanity check
166 continue
167 }
168 result = append(result, ips[0])
169 if len(ips) > 1 {
170 filtered = append(filtered, ips[1:])
171 }
172 }
173 ipsByService = filtered
174 }
175 return result
176}
177
178// Inspiration: https://github.com/artyom/dot/blob/master/dot.go
179func fallbackResolver(serverName, serverAddress string) *net.Resolver {
180 return &net.Resolver{
181 PreferGo: true,
182 Dial: func(ctx context.Context, _ string, _ string) (net.Conn, error) {
183 var dialer net.Dialer
184 conn, err := dialer.DialContext(ctx, "tcp", serverAddress)
185 if err != nil {
186 return nil, err
187 }
188 tlsConfig := &tls.Config{ServerName: serverName}
189 return tls.Client(conn, tlsConfig), nil
190 },
191 }
192}
193
194// EdgeHostnameResolver discovers the addresses of Cloudflare's edge network via a list of server hostnames.
195// It implements EdgeServiceDiscoverer interface, and is used mainly for testing connectivity.
196type EdgeHostnameResolver struct {
197 sync.Mutex
198 // hostnames of edge servers
199 hostnames []string
200 // Addrs to connect to cloudflare's edge network
201 addrs []*net.TCPAddr
202 // index of the next element to use in addrs
203 nextAddrIndex int
204}
205
206func NewEdgeHostnameResolver(edgeHostnames []string) (EdgeServiceDiscoverer, error) {
207 r := &EdgeHostnameResolver{
208 hostnames: edgeHostnames,
209 }
210 if err := r.Refresh(); err != nil {
211 return nil, err
212 }
213 return r, nil
214}
215
216func (r *EdgeHostnameResolver) Addr() *net.TCPAddr {
217 r.Lock()
218 defer r.Unlock()
219 addr := r.addrs[r.nextAddrIndex]
220 r.nextAddrIndex = (r.nextAddrIndex + 1) % len(r.addrs)
221 return addr
222}
223
224func (r *EdgeHostnameResolver) AvailableAddrs() uint8 {
225 r.Lock()
226 defer r.Unlock()
227 return uint8(len(r.addrs))
228}
229
230func (r *EdgeHostnameResolver) Refresh() error {
231 newAddrs, err := ResolveAddrs(r.hostnames)
232 if err != nil {
233 return err
234 }
235 r.Lock()
236 defer r.Unlock()
237 r.addrs = newAddrs
238 r.nextAddrIndex = 0
239 return nil
240}
241
242// Resolve TCP address given a list of addresses. Address can be a hostname, however, it will return at most one
243// of the hostname's IP addresses
244func ResolveAddrs(addrs []string) ([]*net.TCPAddr, error) {
245 var tcpAddrs []*net.TCPAddr
246 for _, addr := range addrs {
247 tcpAddr, err := net.ResolveTCPAddr("tcp", addr)
248 if err != nil {
249 return nil, err
250 }
251 tcpAddrs = append(tcpAddrs, tcpAddr)
252 }
253 return tcpAddrs, nil
254}
255