cloudflare/cloudflared

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
2020.5.0

Branches

Tags

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

Clone

HTTPS

Download ZIP

carrier/carrier.go

157lines · 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 errChan := make(chan error)
77
78 go func() {
79 for {
80 conn, err := listener.Accept()
81 if err != nil {
82 // don't block if parent goroutine quit early
83 select {
84 case errChan <- err:
85 default:
86 }
87 return
88 }
89 go serveConnection(remoteConn, conn, options)
90 }
91 }()
92
93 select {
94 case <-shutdownC:
95 return nil
96 case err := <-errChan:
97 return err
98 }
99}
100
101// serveConnection handles connections for the Serve() call
102func serveConnection(remoteConn Connection, c net.Conn, options *StartOptions) {
103 defer c.Close()
104 serveStream(remoteConn, c, options)
105}
106
107// serveStream will serve the data over the WebSocket stream
108func serveStream(remoteConn Connection, conn io.ReadWriter, options *StartOptions) error {
109 return remoteConn.ServeStream(options, conn)
110}
111
112// IsAccessResponse checks the http Response to see if the url location
113// contains the Access structure.
114func IsAccessResponse(resp *http.Response) bool {
115 if resp == nil || resp.StatusCode != http.StatusFound {
116 return false
117 }
118
119 location, err := resp.Location()
120 if err != nil || location == nil {
121 return false
122 }
123 if strings.HasPrefix(location.Path, "/cdn-cgi/access/login") {
124 return true
125 }
126
127 return false
128}
129
130// BuildAccessRequest builds an HTTP request with the Access token set
131func BuildAccessRequest(options *StartOptions) (*http.Request, error) {
132 req, err := http.NewRequest(http.MethodGet, options.OriginURL, nil)
133 if err != nil {
134 return nil, err
135 }
136
137 token, err := token.FetchToken(req.URL)
138 if err != nil {
139 return nil, err
140 }
141
142 // We need to create a new request as FetchToken will modify req (boo mutable)
143 // as it has to follow redirect on the API and such, so here we init a new one
144 originRequest, err := http.NewRequest(http.MethodGet, options.OriginURL, nil)
145 if err != nil {
146 return nil, err
147 }
148 originRequest.Header.Set("cf-access-token", token)
149
150 for k, v := range options.Headers {
151 if len(v) >= 1 {
152 originRequest.Header.Set(k, v[0])
153 }
154 }
155
156 return originRequest, nil
157}
158