cloudflare/cloudflared

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
2024.2.1

Branches

Tags

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

Clone

HTTPS

Download ZIP

connection/connection.go

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