cloudflare/cloudflared

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
2023.8.1

Branches

Tags

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

Clone

HTTPS

Download ZIP

connection/connection.go

289lines · modecode

1package connection
2
3import (
4 "context"
5 "encoding/base64"
6 "fmt"
7 "io"
8 "math"
9 "net"
10 "net/http"
11 "strconv"
12 "strings"
13 "time"
14
15 "github.com/google/uuid"
16 "github.com/pkg/errors"
17
18 "github.com/cloudflare/cloudflared/tracing"
19 "github.com/cloudflare/cloudflared/tunnelrpc/pogs"
20 "github.com/cloudflare/cloudflared/websocket"
21)
22
23const (
24 lbProbeUserAgentPrefix = "Mozilla/5.0 (compatible; Cloudflare-Traffic-Manager/1.0; +https://www.cloudflare.com/traffic-manager/;"
25 LogFieldConnIndex = "connIndex"
26 MaxGracePeriod = time.Minute * 3
27 MaxConcurrentStreams = math.MaxUint32
28
29 contentTypeHeader = "content-type"
30 sseContentType = "text/event-stream"
31 grpcContentType = "application/grpc"
32)
33
34var (
35 switchingProtocolText = fmt.Sprintf("%d %s", http.StatusSwitchingProtocols, http.StatusText(http.StatusSwitchingProtocols))
36 flushableContentTypes = []string{sseContentType, grpcContentType}
37)
38
39type Orchestrator interface {
40 UpdateConfig(version int32, config []byte) *pogs.UpdateConfigurationResponse
41 GetConfigJSON() ([]byte, error)
42 GetOriginProxy() (OriginProxy, error)
43 WarpRoutingEnabled() (enabled bool)
44}
45
46type NamedTunnelProperties struct {
47 Credentials Credentials
48 Client pogs.ClientInfo
49 QuickTunnelUrl string
50}
51
52// Credentials are stored in the credentials file and contain all info needed to run a tunnel.
53type Credentials struct {
54 AccountTag string
55 TunnelSecret []byte
56 TunnelID uuid.UUID
57}
58
59func (c *Credentials) Auth() pogs.TunnelAuth {
60 return pogs.TunnelAuth{
61 AccountTag: c.AccountTag,
62 TunnelSecret: c.TunnelSecret,
63 }
64}
65
66// TunnelToken are Credentials but encoded with custom fields namings.
67type TunnelToken struct {
68 AccountTag string `json:"a"`
69 TunnelSecret []byte `json:"s"`
70 TunnelID uuid.UUID `json:"t"`
71}
72
73func (t TunnelToken) Credentials() Credentials {
74 return Credentials{
75 AccountTag: t.AccountTag,
76 TunnelSecret: t.TunnelSecret,
77 TunnelID: t.TunnelID,
78 }
79}
80
81func (t TunnelToken) Encode() (string, error) {
82 val, err := json.Marshal(t)
83 if err != nil {
84 return "", errors.Wrap(err, "could not JSON encode token")
85 }
86
87 return base64.StdEncoding.EncodeToString(val), nil
88}
89
90type ClassicTunnelProperties struct {
91 Hostname string
92 OriginCert []byte
93 // feature-flag to use new edge reconnect tokens
94 UseReconnectToken bool
95}
96
97// Type indicates the connection type of the connection.
98type Type int
99
100const (
101 TypeWebsocket Type = iota
102 TypeTCP
103 TypeControlStream
104 TypeHTTP
105 TypeConfiguration
106)
107
108// ShouldFlush returns whether this kind of connection should actively flush data
109func (t Type) shouldFlush() bool {
110 switch t {
111 case TypeWebsocket, TypeTCP, TypeControlStream:
112 return true
113 default:
114 return false
115 }
116}
117
118func (t Type) String() string {
119 switch t {
120 case TypeWebsocket:
121 return "websocket"
122 case TypeTCP:
123 return "tcp"
124 case TypeControlStream:
125 return "control stream"
126 case TypeHTTP:
127 return "http"
128 default:
129 return fmt.Sprintf("Unknown Type %d", t)
130 }
131}
132
133// OriginProxy is how data flows from cloudflared to the origin services running behind it.
134type OriginProxy interface {
135 ProxyHTTP(w ResponseWriter, tr *tracing.TracedHTTPRequest, isWebsocket bool) error
136 ProxyTCP(ctx context.Context, rwa ReadWriteAcker, req *TCPRequest) error
137}
138
139// TCPRequest defines the input format needed to perform a TCP proxy.
140type TCPRequest struct {
141 Dest string
142 CFRay string
143 LBProbe bool
144 FlowID string
145 CfTraceID string
146 ConnIndex uint8
147}
148
149// ReadWriteAcker is a readwriter with the ability to Acknowledge to the downstream (edge) that the origin has
150// accepted the connection.
151type ReadWriteAcker interface {
152 io.ReadWriter
153 AckConnection(tracePropagation string) error
154}
155
156// HTTPResponseReadWriteAcker is an HTTP implementation of ReadWriteAcker.
157type HTTPResponseReadWriteAcker struct {
158 r io.Reader
159 w ResponseWriter
160 f http.Flusher
161 req *http.Request
162}
163
164// NewHTTPResponseReadWriterAcker returns a new instance of HTTPResponseReadWriteAcker.
165func NewHTTPResponseReadWriterAcker(w ResponseWriter, flusher http.Flusher, req *http.Request) *HTTPResponseReadWriteAcker {
166 return &HTTPResponseReadWriteAcker{
167 r: req.Body,
168 w: w,
169 f: flusher,
170 req: req,
171 }
172}
173
174func (h *HTTPResponseReadWriteAcker) Read(p []byte) (int, error) {
175 return h.r.Read(p)
176}
177
178func (h *HTTPResponseReadWriteAcker) Write(p []byte) (int, error) {
179 n, err := h.w.Write(p)
180 if n > 0 {
181 h.f.Flush()
182 }
183 return n, err
184}
185
186// AckConnection acks an HTTP connection by sending a switch protocols status code that enables the caller to
187// upgrade to streams.
188func (h *HTTPResponseReadWriteAcker) AckConnection(tracePropagation string) error {
189 resp := &http.Response{
190 Status: switchingProtocolText,
191 StatusCode: http.StatusSwitchingProtocols,
192 ContentLength: -1,
193 Header: http.Header{},
194 }
195
196 if secWebsocketKey := h.req.Header.Get("Sec-WebSocket-Key"); secWebsocketKey != "" {
197 resp.Header = websocket.NewResponseHeader(h.req)
198 }
199
200 if tracePropagation != "" {
201 resp.Header.Add(tracing.CanonicalCloudflaredTracingHeader, tracePropagation)
202 }
203
204 return h.w.WriteRespHeaders(resp.StatusCode, resp.Header)
205}
206
207// localProxyConnection emulates an incoming connection to cloudflared as a net.Conn.
208// Used when handling a "hijacked" connection from connection.ResponseWriter
209type localProxyConnection struct {
210 io.ReadWriteCloser
211}
212
213func (c *localProxyConnection) Read(b []byte) (int, error) {
214 return c.ReadWriteCloser.Read(b)
215}
216
217func (c *localProxyConnection) Write(b []byte) (int, error) {
218 return c.ReadWriteCloser.Write(b)
219}
220
221func (c *localProxyConnection) Close() error {
222 return c.ReadWriteCloser.Close()
223}
224
225func (c *localProxyConnection) LocalAddr() net.Addr {
226 // Unused LocalAddr
227 return &net.TCPAddr{IP: net.IPv6loopback, Port: 0, Zone: ""}
228}
229
230func (c *localProxyConnection) RemoteAddr() net.Addr {
231 // Unused RemoteAddr
232 return &net.TCPAddr{IP: net.IPv6loopback, Port: 0, Zone: ""}
233}
234
235func (c *localProxyConnection) SetDeadline(t time.Time) error {
236 // ignored since we can't set the read/write Deadlines for the tunnel back to origintunneld
237 return nil
238}
239
240func (c *localProxyConnection) SetReadDeadline(t time.Time) error {
241 // ignored since we can't set the read/write Deadlines for the tunnel back to origintunneld
242 return nil
243}
244
245func (c *localProxyConnection) SetWriteDeadline(t time.Time) error {
246 // ignored since we can't set the read/write Deadlines for the tunnel back to origintunneld
247 return nil
248}
249
250// ResponseWriter is the response path for a request back through cloudflared's tunnel.
251type ResponseWriter interface {
252 WriteRespHeaders(status int, header http.Header) error
253 AddTrailer(trailerName, trailerValue string)
254 http.ResponseWriter
255 http.Hijacker
256 io.Writer
257}
258
259type ConnectedFuse interface {
260 Connected()
261 IsConnected() bool
262}
263
264// Helper method to let the caller know what content-types should require a flush on every
265// write to a ResponseWriter.
266func shouldFlush(headers http.Header) bool {
267 if contentType := headers.Get(contentTypeHeader); contentType != "" {
268 contentType = strings.ToLower(contentType)
269 for _, c := range flushableContentTypes {
270 if strings.HasPrefix(contentType, c) {
271 return true
272 }
273 }
274 }
275
276 return false
277}
278
279func uint8ToString(input uint8) string {
280 return strconv.FormatUint(uint64(input), 10)
281}
282
283func FindCfRayHeader(req *http.Request) string {
284 return req.Header.Get("Cf-Ray")
285}
286
287func IsLBProbeRequest(req *http.Request) bool {
288 return strings.HasPrefix(req.UserAgent(), lbProbeUserAgentPrefix)
289}