cloudflare/cloudflared

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
2020.6.2

Branches

Tags

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

Clone

HTTPS

Download ZIP

carrier/carrier.go

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