@@ -16,15 +16,59 @@ import (
1616
1717 "github.com/coder/websocket"
1818 "golang.org/x/net/context"
19+ "golang.org/x/net/http/httpproxy"
1920 "golang.org/x/net/proxy"
2021)
2122
23+ type ProxyFunc func (addr string ) (* url.URL , error )
24+
2225// Dialer is an interface for a connection dialer
2326type Dialer interface {
2427 Connect () (net.Conn , error )
2528 String () string
2629}
2730
31+ // TCPProxyFromEnvironment returns the proxy URL for the given address based on
32+ // HTTP_PROXY, HTTPS_PROXY, and NO_PROXY environment variables.
33+ // If the address should be connected to directly, nil is returned.
34+ func TCPProxyFromEnvironment (addr string ) (* url.URL , error ) {
35+ // Create a fake URL with the address to check proxy rules
36+ // We use http scheme as default for TCP connections
37+ u := & url.URL {
38+ Scheme : "http" ,
39+ Host : addr ,
40+ }
41+ return httpproxy .FromEnvironment ().ProxyFunc ()(u )
42+ }
43+
44+ // normalizeProxyURL adds the appropriate scheme to a proxy URL if missing
45+ func normalizeProxyURL (proxyStr string , isSocks bool ) string {
46+ if proxyStr == "" || strings .Contains (proxyStr , "://" ) {
47+ return proxyStr
48+ }
49+
50+ if isSocks {
51+ return "socks5://" + proxyStr
52+ }
53+ return "http://" + proxyStr
54+ }
55+
56+ // parseProxyURL tries to parse proxy URL from environment variables
57+ func parseProxyURL (vars []string ) * url.URL {
58+ for _ , v := range vars {
59+ if val := os .Getenv (v ); val != "" {
60+ // Add default scheme if missing
61+ isSocks := strings .Contains (strings .ToUpper (v ), "SOCKS" )
62+ val = normalizeProxyURL (val , isSocks )
63+
64+ if u , err := url .Parse (val ); err == nil {
65+ return u
66+ }
67+ }
68+ }
69+ return nil
70+ }
71+
2872// DirectDialer connects directly to the target
2973type DirectDialer struct {
3074 addr string
@@ -150,13 +194,7 @@ func NewSOCKS5Dialer(proxyURL *url.URL, addr string, timeout time.Duration) *SOC
150194
151195func (s * SOCKS5Dialer ) Connect () (net.Conn , error ) {
152196 dialer := & net.Dialer {Timeout : s .timeout }
153- auth := & proxy.Auth {}
154- if s .proxyURL .User != nil {
155- auth .User = s .proxyURL .User .Username ()
156- if pass , ok := s .proxyURL .User .Password (); ok {
157- auth .Password = pass
158- }
159- }
197+ auth := extractSOCKS5Auth (s .proxyURL )
160198
161199 socks5 , err := proxy .SOCKS5 ("tcp" , s .proxyURL .Host , auth , dialer )
162200 if err != nil {
@@ -203,19 +241,14 @@ func (s *WebSocketDialer) Connect() (net.Conn, error) {
203241 }
204242
205243 // Configure proxies
206- if s .proxy .Scheme == "http" || s .proxy .Scheme == "https" {
244+ switch s .proxy .Scheme {
245+ case "http" , "https" :
207246 transport .Proxy = func (req * http.Request ) (* url.URL , error ) {
208247 return s .proxy , nil
209248 }
210249
211- } else if s .proxy .Scheme == "socks5" {
212- auth := & proxy.Auth {}
213- if s .proxy .User != nil {
214- auth .User = s .proxy .User .Username ()
215- if pass , ok := s .proxy .User .Password (); ok {
216- auth .Password = pass
217- }
218- }
250+ case "socks5" :
251+ auth := extractSOCKS5Auth (s .proxy )
219252 dialer , err := proxy .SOCKS5 ("tcp" , s .proxy .Host , auth , & net.Dialer {
220253 Timeout : s .timeout ,
221254 })
@@ -227,7 +260,7 @@ func (s *WebSocketDialer) Connect() (net.Conn, error) {
227260 return dialer .(proxy.ContextDialer ).DialContext (ctx , network , addr )
228261 }
229262
230- } else {
263+ default :
231264 return nil , fmt .Errorf ("unsupported proxy scheme: %s" , s .proxy .Scheme )
232265 }
233266
@@ -291,6 +324,21 @@ func generateProxyAuth(user *url.Userinfo) string {
291324 return "Basic " + base64 .StdEncoding .EncodeToString ([]byte (auth ))
292325}
293326
327+ // extractSOCKS5Auth extracts authentication from URL for SOCKS5 proxy
328+ func extractSOCKS5Auth (u * url.URL ) * proxy.Auth {
329+ if u == nil || u .User == nil {
330+ return & proxy.Auth {}
331+ }
332+
333+ auth := & proxy.Auth {
334+ User : u .User .Username (),
335+ }
336+ if pass , ok := u .User .Password (); ok {
337+ auth .Password = pass
338+ }
339+ return auth
340+ }
341+
294342// DialConfig is a configuration for creating dialers
295343type DialConfig struct {
296344 // Configuration options
@@ -306,8 +354,8 @@ func NewDialConfig() *DialConfig {
306354 enableTLS : os .Getenv ("ENABLE_TLS" ) == "true" ,
307355 enableWS : os .Getenv ("ENABLE_WS" ) == "true" ,
308356 enableWSS : os .Getenv ("ENABLE_WSS" ) == "true" ,
309- httpProxy : parseHTTPProxy (),
310- socksProxy : parseSOCKSProxy (),
357+ httpProxy : ParseHTTPProxy (),
358+ socksProxy : ParseSOCKSProxy (),
311359 }
312360}
313361
@@ -348,13 +396,9 @@ func (dc *DialConfig) WithHTTPProxyString(proxyURLStr string) *DialConfig {
348396 return dc
349397 }
350398
351- if ! strings .HasPrefix (proxyURLStr , "http://" ) && ! strings .HasPrefix (proxyURLStr , "https://" ) {
352- proxyURLStr = "http://" + proxyURLStr
353- }
354-
355- proxyURL , err := url .Parse (proxyURLStr )
356- if err == nil {
357- dc .httpProxy = proxyURL
399+ proxyURLStr = normalizeProxyURL (proxyURLStr , false )
400+ if u , err := url .Parse (proxyURLStr ); err == nil {
401+ dc .httpProxy = u
358402 }
359403 return dc
360404}
@@ -366,13 +410,9 @@ func (dc *DialConfig) WithSOCKSProxyString(proxyURLStr string) *DialConfig {
366410 return dc
367411 }
368412
369- if ! strings .HasPrefix (proxyURLStr , "socks5://" ) {
370- proxyURLStr = "socks5://" + proxyURLStr
371- }
372-
373- proxyURL , err := url .Parse (proxyURLStr )
374- if err == nil {
375- dc .socksProxy = proxyURL
413+ proxyURLStr = normalizeProxyURL (proxyURLStr , true )
414+ if u , err := url .Parse (proxyURLStr ); err == nil {
415+ dc .socksProxy = u
376416 }
377417 return dc
378418}
@@ -381,25 +421,27 @@ func (dc *DialConfig) WithSOCKSProxyString(proxyURLStr string) *DialConfig {
381421func (dc * DialConfig ) Dialers (addr string , timeout time.Duration ) []Dialer {
382422 var dialers []Dialer
383423
384- // If HTTP proxy is configured, add proxy strategies first
424+ // Helper to create WebSocket URLs
425+ makeWSURL := func (secure bool ) * url.URL {
426+ scheme := "ws"
427+ if secure {
428+ scheme = "wss"
429+ }
430+ return & url.URL {Scheme : scheme , Host : addr , Path : "/" }
431+ }
432+
433+ // If HTTP proxy is configured, add proxy strategies
385434 if dc .httpProxy != nil {
386435 dialers = append (dialers ,
387436 NewHTTPProxyDialer (dc .httpProxy , addr , timeout ))
388437
389- if dc .enableTLS {
390- dialers = append (dialers ,
391- NewHTTPProxyDialer (dc .httpProxy , addr , timeout ))
392- }
393-
394438 if dc .enableWS {
395- wsURL := & url.URL {Scheme : "ws" , Host : addr , Path : "/" }
396439 dialers = append (dialers ,
397- NewWebSocketDialer (wsURL , dc .httpProxy , false , timeout ))
440+ NewWebSocketDialer (makeWSURL ( false ) , dc .httpProxy , false , timeout ))
398441
399442 if dc .enableWSS {
400- wssURL := & url.URL {Scheme : "wss" , Host : addr , Path : "/" }
401443 dialers = append (dialers ,
402- NewWebSocketDialer (wssURL , dc .httpProxy , false , timeout ))
444+ NewWebSocketDialer (makeWSURL ( true ) , dc .httpProxy , false , timeout ))
403445 }
404446 }
405447 } else if dc .socksProxy != nil {
@@ -419,53 +461,76 @@ func (dc *DialConfig) Dialers(addr string, timeout time.Duration) []Dialer {
419461
420462 // Add WebSocket direct if enabled
421463 if dc .enableWS {
422- wsURL := & url.URL {Scheme : "ws" , Host : addr , Path : "/" }
423464 dialers = append (dialers ,
424- NewWebSocketDialer (wsURL , nil , false , timeout ))
465+ NewWebSocketDialer (makeWSURL ( false ) , nil , false , timeout ))
425466
426467 if dc .enableWSS {
427- wssURL := & url.URL {Scheme : "wss" , Host : addr , Path : "/" }
428468 dialers = append (dialers ,
429- NewWebSocketDialer (wssURL , nil , false , timeout ))
469+ NewWebSocketDialer (makeWSURL ( true ) , nil , false , timeout ))
430470 }
431471 }
432472
433473 return dialers
434474}
435475
436- func parseHTTPProxy () * url.URL {
437- proxyURLStr := os .Getenv ("HTTP_PROXY" )
438- if proxyURLStr == "" {
439- proxyURLStr = os .Getenv ("HTTPS_PROXY" )
440- }
441- if proxyURLStr == "" {
442- return nil
443- }
476+ func ParseHTTPProxy () * url.URL {
477+ // Try each proxy variable in order
478+ vars := []string {"http_proxy" , "HTTP_PROXY" , "https_proxy" , "HTTPS_PROXY" , "all_proxy" , "ALL_PROXY" }
479+ return parseProxyURL (vars )
480+ }
444481
445- if ! strings . HasPrefix ( proxyURLStr , "http://" ) && ! strings . HasPrefix ( proxyURLStr , "https://" ) {
446- proxyURLStr = "http://" + proxyURLStr
447- }
482+ func ParseSOCKSProxy () * url. URL {
483+ return parseProxyURL ([] string { "SOCKS5_PROXY" })
484+ }
448485
449- proxyURL , err := url .Parse (proxyURLStr )
450- if err != nil {
451- return nil
452- }
453- return proxyURL
486+ // TCPDialer is a smart dialer that handles proxy configuration automatically
487+ type TCPDialer struct {
488+ proxyFunc ProxyFunc
489+ timeout time.Duration
454490}
455491
456- func parseSOCKSProxy () * url.URL {
457- proxyURLStr := os .Getenv ("SOCKS5_PROXY" )
458- if proxyURLStr == "" {
459- return nil
492+ // NewTCPDialer creates a new TCP dialer with the given proxy function and timeout
493+ func NewTCPDialer (proxyFunc ProxyFunc , timeout time.Duration ) * TCPDialer {
494+ if proxyFunc == nil {
495+ // Default to environment-based proxy configuration.
496+ proxyFunc = TCPProxyFromEnvironment
460497 }
461-
462- if ! strings . HasPrefix ( proxyURLStr , "socks5://" ) {
463- proxyURLStr = "socks5://" + proxyURLStr
498+ return & TCPDialer {
499+ proxyFunc : proxyFunc ,
500+ timeout : timeout ,
464501 }
502+ }
465503
466- proxyURL , err := url .Parse (proxyURLStr )
504+ // DialContext connects to the given address, using a proxy if configured
505+ func (d * TCPDialer ) DialContext (ctx context.Context , network , addr string ) (net.Conn , error ) {
506+ // Determine if we should use a proxy
507+ proxyURL , err := d .proxyFunc (addr )
467508 if err != nil {
468- return nil
509+ return nil , fmt .Errorf ("proxy resolution failed: %w" , err )
510+ }
511+
512+ if proxyURL != nil {
513+ // Use proxy connection
514+ switch proxyURL .Scheme {
515+ case "http" , "https" :
516+ proxyDialer := NewHTTPProxyDialer (proxyURL , addr , d .timeout )
517+ return proxyDialer .Connect ()
518+ case "socks5" :
519+ socksDialer := NewSOCKS5Dialer (proxyURL , addr , d .timeout )
520+ return socksDialer .Connect ()
521+ default :
522+ return nil , fmt .Errorf ("unsupported proxy scheme: %s" , proxyURL .Scheme )
523+ }
469524 }
470- return proxyURL
525+
526+ // Direct connection
527+ dialer := & net.Dialer {
528+ Timeout : d .timeout ,
529+ }
530+ return dialer .DialContext (ctx , network , addr )
531+ }
532+
533+ // Dial connects to the given address (non-context version)
534+ func (d * TCPDialer ) Dial (network , addr string ) (net.Conn , error ) {
535+ return d .DialContext (context .Background (), network , addr )
471536}
0 commit comments