cloudflare/cloudflared

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
a8ae6de213f88b22047f0ca6275751eb8bc00a85

Branches

Tags

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

Clone

HTTPS

Download ZIP

carrier/websocket.go

158lines · modecode

1package carrier
2
3import (
4 "fmt"
5 "io"
6 "net"
7 "net/http"
8 "net/http/httputil"
9
10 "github.com/cloudflare/cloudflared/cmd/cloudflared/token"
11 "github.com/cloudflare/cloudflared/socks"
12 cfwebsocket "github.com/cloudflare/cloudflared/websocket"
13
14 "github.com/gorilla/websocket"
15 "github.com/rs/zerolog"
16)
17
18// Websocket is used to carry data via WS binary frames over the tunnel from client to the origin
19// This implements the functions for glider proxy (sock5) and the carrier interface
20type Websocket struct {
21 log *zerolog.Logger
22 isSocks bool
23}
24
25type wsdialer struct {
26 conn *cfwebsocket.Conn
27}
28
29func (d *wsdialer) Dial(address string) (io.ReadWriteCloser, *socks.AddrSpec, error) {
30 local, ok := d.conn.LocalAddr().(*net.TCPAddr)
31 if !ok {
32 return nil, nil, fmt.Errorf("not a tcp connection")
33 }
34
35 addr := socks.AddrSpec{IP: local.IP, Port: local.Port}
36 return d.conn, &addr, nil
37}
38
39// NewWSConnection returns a new connection object
40func NewWSConnection(log *zerolog.Logger, isSocks bool) Connection {
41 return &Websocket{
42 log: log,
43 isSocks: isSocks,
44 }
45}
46
47// ServeStream will create a Websocket client stream connection to the edge
48// it blocks and writes the raw data from conn over the tunnel
49func (ws *Websocket) ServeStream(options *StartOptions, conn io.ReadWriter) error {
50 wsConn, err := createWebsocketStream(options, ws.log)
51 if err != nil {
52 ws.log.Err(err).Str(LogFieldOriginURL, options.OriginURL).Msg("failed to connect to origin")
53 return err
54 }
55 defer wsConn.Close()
56
57 if ws.isSocks {
58 dialer := &wsdialer{conn: wsConn}
59 requestHandler := socks.NewRequestHandler(dialer)
60 socksServer := socks.NewConnectionHandler(requestHandler)
61
62 _ = socksServer.Serve(conn)
63 } else {
64 cfwebsocket.Stream(wsConn, conn)
65 }
66 return nil
67}
68
69// StartServer creates a Websocket server to listen for connections.
70// This is used on the origin (tunnel) side to take data from the muxer and send it to the origin
71func (ws *Websocket) StartServer(listener net.Listener, remote string, shutdownC <-chan struct{}) error {
72 return cfwebsocket.StartProxyServer(ws.log, listener, remote, shutdownC, cfwebsocket.DefaultStreamHandler)
73}
74
75// createWebsocketStream will create a WebSocket connection to stream data over
76// It also handles redirects from Access and will present that flow if
77// the token is not present on the request
78func createWebsocketStream(options *StartOptions, log *zerolog.Logger) (*cfwebsocket.Conn, error) {
79 req, err := http.NewRequest(http.MethodGet, options.OriginURL, nil)
80 if err != nil {
81 return nil, err
82 }
83 req.Header = options.Headers
84
85 dump, err := httputil.DumpRequest(req, false)
86 log.Debug().Msgf("Websocket request: %s", string(dump))
87
88 wsConn, resp, err := cfwebsocket.ClientConnect(req, nil)
89 defer closeRespBody(resp)
90
91 if err != nil && IsAccessResponse(resp) {
92 wsConn, err = createAccessAuthenticatedStream(options, log)
93 if err != nil {
94 return nil, err
95 }
96 } else if err != nil {
97 return nil, err
98 }
99
100 return &cfwebsocket.Conn{Conn: wsConn}, nil
101}
102
103// createAccessAuthenticatedStream will try load a token from storage and make
104// a connection with the token set on the request. If it still get redirect,
105// this probably means the token in storage is invalid (expired/revoked). If that
106// happens it deletes the token and runs the connection again, so the user can
107// login again and generate a new one.
108func createAccessAuthenticatedStream(options *StartOptions, log *zerolog.Logger) (*websocket.Conn, error) {
109 wsConn, resp, err := createAccessWebSocketStream(options, log)
110 defer closeRespBody(resp)
111 if err == nil {
112 return wsConn, nil
113 }
114
115 if !IsAccessResponse(resp) {
116 return nil, err
117 }
118
119 // Access Token is invalid for some reason. Go through regen flow
120 originReq, err := http.NewRequest(http.MethodGet, options.OriginURL, nil)
121 if err != nil {
122 return nil, err
123 }
124 if err := token.RemoveTokenIfExists(originReq.URL); err != nil {
125 return nil, err
126 }
127 wsConn, resp, err = createAccessWebSocketStream(options, log)
128 defer closeRespBody(resp)
129 if err != nil {
130 return nil, err
131 }
132
133 return wsConn, nil
134}
135
136// createAccessWebSocketStream builds an Access request and makes a connection
137func createAccessWebSocketStream(options *StartOptions, log *zerolog.Logger) (*websocket.Conn, *http.Response, error) {
138 req, err := BuildAccessRequest(options, log)
139 if err != nil {
140 return nil, nil, err
141 }
142
143 dump, err := httputil.DumpRequest(req, false)
144 log.Debug().Msgf("Access Websocket request: %s", string(dump))
145
146 conn, resp, err := cfwebsocket.ClientConnect(req, nil)
147
148 if resp != nil {
149 r, err := httputil.DumpResponse(resp, true)
150 if r != nil {
151 log.Debug().Msgf("Websocket response: %q", r)
152 } else if err != nil {
153 log.Debug().Msgf("Websocket response error: %v", err)
154 }
155 }
156
157 return conn, resp, err
158}
159