cloudflare/cloudflared

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
2025.2.1

Branches

Tags

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

Clone

HTTPS

Download ZIP

connection/connection.go

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