cloudflare/cloudflared

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
2020.4.0

Branches

Tags

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

Clone

HTTPS

Download ZIP

carrier/carrier.go

146lines · modecode

1//Package carrier provides a WebSocket proxy to carry or proxy a connection
2//from the local client to the edge. See it as a wrapper around any protocol
3//that it packages up in a WebSocket connection to the edge.
4package carrier
5
6import (
7 "io"
8 "net"
9 "net/http"
10 "os"
11 "strings"
12
13 "github.com/cloudflare/cloudflared/cmd/cloudflared/token"
14 "github.com/pkg/errors"
15)
16
17type StartOptions struct {
18 OriginURL string
19 Headers http.Header
20}
21
22// Connection wraps up all the needed functions to forward over the tunnel
23type Connection interface {
24 // ServeStream is used to forward data from the client to the edge
25 ServeStream(*StartOptions, io.ReadWriter) error
26
27 // StartServer is used to listen for incoming connections from the edge to the origin
28 StartServer(net.Listener, string, <-chan struct{}) error
29}
30
31// StdinoutStream is empty struct for wrapping stdin/stdout
32// into a single ReadWriter
33type StdinoutStream struct {
34}
35
36// Read will read from Stdin
37func (c *StdinoutStream) Read(p []byte) (int, error) {
38 return os.Stdin.Read(p)
39
40}
41
42// Write will write to Stdout
43func (c *StdinoutStream) Write(p []byte) (int, error) {
44 return os.Stdout.Write(p)
45}
46
47// Helper to allow defering the response close with a check that the resp is not nil
48func closeRespBody(resp *http.Response) {
49 if resp != nil {
50 resp.Body.Close()
51 }
52}
53
54// StartForwarder will setup a listener on a specified address/port and then
55// forward connections to the origin by calling `Serve()`.
56func StartForwarder(conn Connection, address string, shutdownC <-chan struct{}, options *StartOptions) error {
57 listener, err := net.Listen("tcp", address)
58 if err != nil {
59 return errors.Wrap(err, "failed to start forwarding server")
60 }
61 return Serve(conn, listener, shutdownC, options)
62}
63
64// StartClient will copy the data from stdin/stdout over a WebSocket connection
65// to the edge (originURL)
66func StartClient(conn Connection, stream io.ReadWriter, options *StartOptions) error {
67 return serveStream(conn, stream, options)
68}
69
70// Serve accepts incoming connections on the specified net.Listener.
71// Each connection is handled in a new goroutine: its data is copied over a
72// WebSocket connection to the edge (originURL).
73// `Serve` always closes `listener`.
74func Serve(remoteConn Connection, listener net.Listener, shutdownC <-chan struct{}, options *StartOptions) error {
75 defer listener.Close()
76 for {
77 select {
78 case <-shutdownC:
79 return nil
80 default:
81 conn, err := listener.Accept()
82 if err != nil {
83 return err
84 }
85 go serveConnection(remoteConn, conn, options)
86 }
87 }
88}
89
90// serveConnection handles connections for the Serve() call
91func serveConnection(remoteConn Connection, c net.Conn, options *StartOptions) {
92 defer c.Close()
93 serveStream(remoteConn, c, options)
94}
95
96// serveStream will serve the data over the WebSocket stream
97func serveStream(remoteConn Connection, conn io.ReadWriter, options *StartOptions) error {
98 return remoteConn.ServeStream(options, conn)
99}
100
101// IsAccessResponse checks the http Response to see if the url location
102// contains the Access structure.
103func IsAccessResponse(resp *http.Response) bool {
104 if resp == nil || resp.StatusCode != http.StatusFound {
105 return false
106 }
107
108 location, err := resp.Location()
109 if err != nil || location == nil {
110 return false
111 }
112 if strings.HasPrefix(location.Path, "/cdn-cgi/access/login") {
113 return true
114 }
115
116 return false
117}
118
119// BuildAccessRequest builds an HTTP request with the Access token set
120func BuildAccessRequest(options *StartOptions) (*http.Request, error) {
121 req, err := http.NewRequest(http.MethodGet, options.OriginURL, nil)
122 if err != nil {
123 return nil, err
124 }
125
126 token, err := token.FetchToken(req.URL)
127 if err != nil {
128 return nil, err
129 }
130
131 // We need to create a new request as FetchToken will modify req (boo mutable)
132 // as it has to follow redirect on the API and such, so here we init a new one
133 originRequest, err := http.NewRequest(http.MethodGet, options.OriginURL, nil)
134 if err != nil {
135 return nil, err
136 }
137 originRequest.Header.Set("cf-access-token", token)
138
139 for k, v := range options.Headers {
140 if len(v) >= 1 {
141 originRequest.Header.Set(k, v[0])
142 }
143 }
144
145 return originRequest, nil
146}
147