cloudflare/cloudflared

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
2023.4.2

Branches

Tags

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

Clone

HTTPS

Download ZIP

connection/connection.go

283lines · 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 req *http.Request
161}
162
163// NewHTTPResponseReadWriterAcker returns a new instance of HTTPResponseReadWriteAcker.
164func NewHTTPResponseReadWriterAcker(w ResponseWriter, req *http.Request) *HTTPResponseReadWriteAcker {
165 return &HTTPResponseReadWriteAcker{
166 r: req.Body,
167 w: w,
168 req: req,
169 }
170}
171
172func (h *HTTPResponseReadWriteAcker) Read(p []byte) (int, error) {
173 return h.r.Read(p)
174}
175
176func (h *HTTPResponseReadWriteAcker) Write(p []byte) (int, error) {
177 return h.w.Write(p)
178}
179
180// AckConnection acks an HTTP connection by sending a switch protocols status code that enables the caller to
181// upgrade to streams.
182func (h *HTTPResponseReadWriteAcker) AckConnection(tracePropagation string) error {
183 resp := &http.Response{
184 Status: switchingProtocolText,
185 StatusCode: http.StatusSwitchingProtocols,
186 ContentLength: -1,
187 Header: http.Header{},
188 }
189
190 if secWebsocketKey := h.req.Header.Get("Sec-WebSocket-Key"); secWebsocketKey != "" {
191 resp.Header = websocket.NewResponseHeader(h.req)
192 }
193
194 if tracePropagation != "" {
195 resp.Header.Add(tracing.CanonicalCloudflaredTracingHeader, tracePropagation)
196 }
197
198 return h.w.WriteRespHeaders(resp.StatusCode, resp.Header)
199}
200
201// localProxyConnection emulates an incoming connection to cloudflared as a net.Conn.
202// Used when handling a "hijacked" connection from connection.ResponseWriter
203type localProxyConnection struct {
204 io.ReadWriteCloser
205}
206
207func (c *localProxyConnection) Read(b []byte) (int, error) {
208 return c.ReadWriteCloser.Read(b)
209}
210
211func (c *localProxyConnection) Write(b []byte) (int, error) {
212 return c.ReadWriteCloser.Write(b)
213}
214
215func (c *localProxyConnection) Close() error {
216 return c.ReadWriteCloser.Close()
217}
218
219func (c *localProxyConnection) LocalAddr() net.Addr {
220 // Unused LocalAddr
221 return &net.TCPAddr{IP: net.IPv6loopback, Port: 0, Zone: ""}
222}
223
224func (c *localProxyConnection) RemoteAddr() net.Addr {
225 // Unused RemoteAddr
226 return &net.TCPAddr{IP: net.IPv6loopback, Port: 0, Zone: ""}
227}
228
229func (c *localProxyConnection) SetDeadline(t time.Time) error {
230 // ignored since we can't set the read/write Deadlines for the tunnel back to origintunneld
231 return nil
232}
233
234func (c *localProxyConnection) SetReadDeadline(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) SetWriteDeadline(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
244// ResponseWriter is the response path for a request back through cloudflared's tunnel.
245type ResponseWriter interface {
246 WriteRespHeaders(status int, header http.Header) error
247 AddTrailer(trailerName, trailerValue string)
248 http.ResponseWriter
249 http.Hijacker
250 io.Writer
251}
252
253type ConnectedFuse interface {
254 Connected()
255 IsConnected() bool
256}
257
258// Helper method to let the caller know what content-types should require a flush on every
259// write to a ResponseWriter.
260func shouldFlush(headers http.Header) bool {
261 if contentType := headers.Get(contentTypeHeader); contentType != "" {
262 contentType = strings.ToLower(contentType)
263 for _, c := range flushableContentTypes {
264 if strings.HasPrefix(contentType, c) {
265 return true
266 }
267 }
268 }
269
270 return false
271}
272
273func uint8ToString(input uint8) string {
274 return strconv.FormatUint(uint64(input), 10)
275}
276
277func FindCfRayHeader(req *http.Request) string {
278 return req.Header.Get("Cf-Ray")
279}
280
281func IsLBProbeRequest(req *http.Request) bool {
282 return strings.HasPrefix(req.UserAgent(), lbProbeUserAgentPrefix)
283}