cloudflare/cloudflared

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
2019.4.1

Branches

Tags

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

Clone

HTTPS

Download ZIP

connection/supervisor.go

145lines · modecode

1package connection
2
3import (
4 "context"
5 "net"
6 "time"
7
8 tunnelpogs "github.com/cloudflare/cloudflared/tunnelrpc/pogs"
9 "github.com/google/uuid"
10 "github.com/pkg/errors"
11 "github.com/sirupsen/logrus"
12)
13
14const (
15 // Waiting time before retrying a failed tunnel connection
16 reconnectDuration = time.Second * 10
17 // SRV record resolution TTL
18 resolveTTL = time.Hour
19 // Interval between establishing new connection
20 connectionInterval = time.Second
21)
22
23type CloudflaredConfig struct {
24 ConnectionConfig *ConnectionConfig
25 OriginCert []byte
26 Tags []tunnelpogs.Tag
27 EdgeAddrs []string
28 HAConnections uint
29 Logger *logrus.Logger
30}
31
32// Supervisor is a stateful object that manages connections with the edge
33type Supervisor struct {
34 config *CloudflaredConfig
35 state *supervisorState
36 connErrors chan error
37}
38
39type supervisorState struct {
40 // IPs to connect to cloudflare's edge network
41 edgeIPs []*net.TCPAddr
42 // index of the next element to use in edgeIPs
43 nextEdgeIPIndex int
44 // last time edgeIPs were refreshed
45 lastResolveTime time.Time
46 // ID of this cloudflared instance
47 cloudflaredID uuid.UUID
48 // connectionPool is a pool of connectionHandlers that can be used to make RPCs
49 connectionPool *connectionPool
50}
51
52func (s *supervisorState) getNextEdgeIP() *net.TCPAddr {
53 ip := s.edgeIPs[s.nextEdgeIPIndex%len(s.edgeIPs)]
54 s.nextEdgeIPIndex++
55 return ip
56}
57
58func NewSupervisor(config *CloudflaredConfig) *Supervisor {
59 return &Supervisor{
60 config: config,
61 state: &supervisorState{
62 connectionPool: &connectionPool{},
63 },
64 connErrors: make(chan error),
65 }
66}
67
68func (s *Supervisor) Run(ctx context.Context) error {
69 logger := s.config.Logger
70 if err := s.initialize(); err != nil {
71 logger.WithError(err).Error("Failed to get edge IPs")
72 return err
73 }
74 defer s.state.connectionPool.close()
75
76 var currentConnectionCount uint
77 expectedConnectionCount := s.config.HAConnections
78 if uint(len(s.state.edgeIPs)) < s.config.HAConnections {
79 logger.Warnf("You requested %d HA connections but I can give you at most %d.", s.config.HAConnections, len(s.state.edgeIPs))
80 expectedConnectionCount = uint(len(s.state.edgeIPs))
81 }
82 for {
83 select {
84 case <-ctx.Done():
85 return nil
86 case connErr := <-s.connErrors:
87 logger.WithError(connErr).Warnf("Connection dropped unexpectedly")
88 currentConnectionCount--
89 default:
90 time.Sleep(5 * time.Second)
91 }
92 if currentConnectionCount < expectedConnectionCount {
93 h, err := newH2MuxHandler(ctx, s.config.ConnectionConfig, s.state.getNextEdgeIP())
94 if err != nil {
95 logger.WithError(err).Error("Failed to create new connection handler")
96 continue
97 }
98 go func() {
99 s.connErrors <- h.serve(ctx)
100 }()
101 connResult, err := s.connect(ctx, s.config, s.state.cloudflaredID, h)
102 if err != nil {
103 logger.WithError(err).Errorf("Failed to connect to cloudflared's edge network")
104 h.shutdown()
105 continue
106 }
107 if connErr := connResult.Err; connErr != nil && !connErr.ShouldRetry {
108 logger.WithError(connErr).Errorf("Server respond with don't retry to connect")
109 h.shutdown()
110 return err
111 }
112 logger.Infof("Connected to %s", connResult.ServerInfo.LocationName)
113 s.state.connectionPool.put(h)
114 currentConnectionCount++
115 }
116 }
117}
118
119func (s *Supervisor) initialize() error {
120 edgeIPs, err := ResolveEdgeIPs(s.config.Logger, s.config.EdgeAddrs)
121 if err != nil {
122 return errors.Wrapf(err, "Failed to resolve cloudflare edge network address")
123 }
124 s.state.edgeIPs = edgeIPs
125 s.state.lastResolveTime = time.Now()
126 cloudflaredID, err := uuid.NewRandom()
127 if err != nil {
128 return errors.Wrap(err, "Failed to generate cloudflared ID")
129 }
130 s.state.cloudflaredID = cloudflaredID
131 return nil
132}
133
134func (s *Supervisor) connect(ctx context.Context,
135 config *CloudflaredConfig,
136 cloudflaredID uuid.UUID,
137 h connectionHandler,
138) (*tunnelpogs.ConnectResult, error) {
139 connectParameters := &tunnelpogs.ConnectParameters{
140 OriginCert: config.OriginCert,
141 CloudflaredID: cloudflaredID,
142 NumPreviousAttempts: 0,
143 }
144 return h.connect(ctx, connectParameters)
145}
146