1
1
package ingress
2
2
3
3
import (
4
+ "bufio"
4
5
"context"
5
6
"crypto/tls"
6
7
"encoding/json"
@@ -9,11 +10,13 @@ import (
9
10
"net"
10
11
"net/http"
11
12
"net/url"
13
+ "os"
12
14
"strconv"
13
15
"time"
14
16
15
17
"github.com/pkg/errors"
16
18
"github.com/rs/zerolog"
19
+ "golang.org/x/net/proxy"
17
20
18
21
"github.com/cloudflare/cloudflared/hello"
19
22
"github.com/cloudflare/cloudflared/ipaccess"
@@ -97,7 +100,7 @@ func (o httpService) MarshalJSON() ([]byte, error) {
97
100
// It's used by warp routing
98
101
type rawTCPService struct {
99
102
name string
100
- dialer net .Dialer
103
+ dialer proxy .Dialer
101
104
writeTimeout time.Duration
102
105
logger * zerolog.Logger
103
106
}
@@ -114,14 +117,136 @@ func (o rawTCPService) MarshalJSON() ([]byte, error) {
114
117
return json .Marshal (o .String ())
115
118
}
116
119
120
+ // proxyAwareDialer wraps net.Dialer with proxy support for both HTTP CONNECT and SOCKS
121
+ type proxyAwareDialer struct {
122
+ baseDialer * net.Dialer
123
+ logger * zerolog.Logger
124
+ }
125
+
126
+ // newProxyAwareDialer creates a dialer that supports proxy settings from environment
127
+ func newProxyAwareDialer (timeout , keepAlive time.Duration , logger * zerolog.Logger ) proxy.Dialer {
128
+ baseDialer := & net.Dialer {
129
+ Timeout : timeout ,
130
+ KeepAlive : keepAlive ,
131
+ }
132
+
133
+ httpProxy := getEnvProxy ("HTTP_PROXY" , "http_proxy" )
134
+ httpsProxy := getEnvProxy ("HTTPS_PROXY" , "https_proxy" )
135
+
136
+ if httpProxy == "" && httpsProxy == "" {
137
+ if logger != nil {
138
+ logger .Debug ().Msg ("proxy: no proxy configured, using direct connection" )
139
+ }
140
+ return baseDialer
141
+ }
142
+
143
+ if logger != nil {
144
+ logger .Debug ().Str ("HTTP_PROXY" , httpProxy ).Str ("HTTPS_PROXY" , httpsProxy ).Msg ("proxy: detected proxy configuration" )
145
+ }
146
+ return & proxyAwareDialer {
147
+ baseDialer : baseDialer ,
148
+ logger : logger ,
149
+ }
150
+ }
151
+
152
+ func getEnvProxy (upper , lower string ) string {
153
+ if v := os .Getenv (upper ); v != "" {
154
+ return v
155
+ }
156
+ return os .Getenv (lower )
157
+ }
158
+
159
+ func (p * proxyAwareDialer ) Dial (network , addr string ) (net.Conn , error ) {
160
+ return p .DialContext (context .Background (), network , addr )
161
+ }
162
+
163
+ func (p * proxyAwareDialer ) DialContext (ctx context.Context , network , addr string ) (net.Conn , error ) {
164
+ if network != "tcp" {
165
+ return p .baseDialer .DialContext (ctx , network , addr )
166
+ }
167
+
168
+ req := & http.Request {URL : & url.URL {Scheme : "http" , Host : addr }}
169
+ proxyURL , err := http .ProxyFromEnvironment (req )
170
+ if err != nil || proxyURL == nil {
171
+ if p .logger != nil {
172
+ p .logger .Debug ().Str ("addr" , addr ).Msg ("proxy: direct connection to" )
173
+ }
174
+ return p .baseDialer .DialContext (ctx , network , addr )
175
+ }
176
+
177
+ if p .logger != nil {
178
+ p .logger .Debug ().Str ("proxy_url" , proxyURL .String ()).Str ("addr" , addr ).Msg ("proxy: using proxy" )
179
+ }
180
+
181
+ switch proxyURL .Scheme {
182
+ case "socks4" , "socks5" :
183
+ return p .dialSOCKS (ctx , proxyURL , network , addr )
184
+ case "http" , "https" :
185
+ return p .dialHTTPConnect (ctx , proxyURL , addr )
186
+ default :
187
+ return nil , fmt .Errorf ("unsupported proxy scheme: %s" , proxyURL .Scheme )
188
+ }
189
+ }
190
+
191
+ func (p * proxyAwareDialer ) dialSOCKS (ctx context.Context , proxyURL * url.URL , network , addr string ) (net.Conn , error ) {
192
+ socksDialer , err := proxy .FromURL (proxyURL , p .baseDialer )
193
+ if err != nil {
194
+ return nil , fmt .Errorf ("SOCKS proxy error: %w" , err )
195
+ }
196
+
197
+ if contextDialer , ok := socksDialer .(proxy.ContextDialer ); ok {
198
+ return contextDialer .DialContext (ctx , network , addr )
199
+ }
200
+ return socksDialer .Dial (network , addr )
201
+ }
202
+
203
+ func (p * proxyAwareDialer ) dialHTTPConnect (ctx context.Context , proxyURL * url.URL , addr string ) (net.Conn , error ) {
204
+ proxyAddr := proxyURL .Host
205
+ if proxyURL .Port () == "" {
206
+ if proxyURL .Scheme == "https" {
207
+ proxyAddr = net .JoinHostPort (proxyURL .Hostname (), "443" )
208
+ } else {
209
+ proxyAddr = net .JoinHostPort (proxyURL .Hostname (), "80" )
210
+ }
211
+ }
212
+
213
+ conn , err := p .baseDialer .DialContext (ctx , "tcp" , proxyAddr )
214
+ if err != nil {
215
+ return nil , fmt .Errorf ("proxy connection failed: %w" , err )
216
+ }
217
+
218
+ connectReq := fmt .Sprintf ("CONNECT %s HTTP/1.1\r \n Host: %s\r \n \r \n " , addr , addr )
219
+ if _ , err := conn .Write ([]byte (connectReq )); err != nil {
220
+ conn .Close ()
221
+ return nil , fmt .Errorf ("CONNECT request failed: %w" , err )
222
+ }
223
+
224
+ br := bufio .NewReader (conn )
225
+ resp , err := http .ReadResponse (br , & http.Request {Method : "CONNECT" })
226
+ if err != nil {
227
+ conn .Close ()
228
+ return nil , fmt .Errorf ("CONNECT response failed: %w" , err )
229
+ }
230
+ resp .Body .Close ()
231
+
232
+ if resp .StatusCode != 200 {
233
+ conn .Close ()
234
+ return nil , fmt .Errorf ("proxy CONNECT failed: %s" , resp .Status )
235
+ }
236
+
237
+ if p .logger != nil {
238
+ p .logger .Debug ().Str ("addr" , addr ).Msg ("proxy: HTTP CONNECT successful" )
239
+ }
240
+ return conn , nil
241
+ }
242
+
117
243
// tcpOverWSService models TCP origins serving eyeballs connecting over websocket, such as
118
- // cloudflared access commands.
119
244
type tcpOverWSService struct {
120
245
scheme string
121
246
dest string
122
247
isBastion bool
123
248
streamHandler streamHandlerFunc
124
- dialer net .Dialer
249
+ dialer proxy .Dialer
125
250
}
126
251
127
252
type socksProxyOverWSService struct {
@@ -142,12 +267,14 @@ func newTCPOverWSService(url *url.URL) *tcpOverWSService {
142
267
return & tcpOverWSService {
143
268
scheme : url .Scheme ,
144
269
dest : url .Host ,
270
+ dialer : newProxyAwareDialer (30 * time .Second , 30 * time .Second , nil ),
145
271
}
146
272
}
147
273
148
274
func newBastionService () * tcpOverWSService {
149
275
return & tcpOverWSService {
150
276
isBastion : true ,
277
+ dialer : newProxyAwareDialer (30 * time .Second , 30 * time .Second , nil ),
151
278
}
152
279
}
153
280
@@ -187,8 +314,8 @@ func (o *tcpOverWSService) start(log *zerolog.Logger, _ <-chan struct{}, cfg Ori
187
314
} else {
188
315
o .streamHandler = DefaultStreamHandler
189
316
}
190
- o . dialer . Timeout = cfg . ConnectTimeout . Duration
191
- o .dialer . KeepAlive = cfg .TCPKeepAlive .Duration
317
+ // Recreate dialer with new timeout and keepalive settings
318
+ o .dialer = newProxyAwareDialer ( cfg .ConnectTimeout . Duration , cfg . TCPKeepAlive .Duration , log )
192
319
return nil
193
320
}
194
321
@@ -291,11 +418,8 @@ type WarpRoutingService struct {
291
418
292
419
func NewWarpRoutingService (config WarpRoutingConfig , writeTimeout time.Duration ) * WarpRoutingService {
293
420
svc := & rawTCPService {
294
- name : ServiceWarpRouting ,
295
- dialer : net.Dialer {
296
- Timeout : config .ConnectTimeout .Duration ,
297
- KeepAlive : config .TCPKeepAlive .Duration ,
298
- },
421
+ name : ServiceWarpRouting ,
422
+ dialer : newProxyAwareDialer (config .ConnectTimeout .Duration , config .TCPKeepAlive .Duration , nil ),
299
423
writeTimeout : writeTimeout ,
300
424
}
301
425
0 commit comments