cloudflare/cloudflared

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
2024.11.0

Branches

Tags

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

Clone

HTTPS

Download ZIP

connection/connection.go

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