From 1899b234a1d262ee42133c42626a1b47ea1ee605 Mon Sep 17 00:00:00 2001 From: Erik Dubbelboer Date: Sat, 6 Jun 2026 17:28:48 +0800 Subject: [PATCH] bug: fasthttpproxy dialers return nil DialFunc on error, causing panic (#2248) (#2279) --- fasthttpproxy/dialer.go | 9 ++++++ fasthttpproxy/dialer_test.go | 59 ++++++++++++++++++++++++++++++++++++ fasthttpproxy/http.go | 8 ++--- fasthttpproxy/proxy_env.go | 4 +-- fasthttpproxy/socks5.go | 8 ++--- 5 files changed, 78 insertions(+), 10 deletions(-) diff --git a/fasthttpproxy/dialer.go b/fasthttpproxy/dialer.go index 49753ec..6176cc2 100644 --- a/fasthttpproxy/dialer.go +++ b/fasthttpproxy/dialer.go @@ -21,6 +21,15 @@ var ( tmpURL = &url.URL{Scheme: httpsScheme, Host: "example.com"} ) +func dialFuncOrError(dialFunc fasthttp.DialFunc, err error) fasthttp.DialFunc { + if err == nil { + return dialFunc + } + return func(addr string) (net.Conn, error) { + return nil, err + } +} + // Dialer embeds both fasthttp.TCPDialer and httpproxy.Config, allowing it // to take advantage of the optimizations provided by fasthttp for dialing while also // utilizing the finer-grained configuration options offered by httpproxy. diff --git a/fasthttpproxy/dialer_test.go b/fasthttpproxy/dialer_test.go index 9b4fea5..3bbd2d1 100644 --- a/fasthttpproxy/dialer_test.go +++ b/fasthttpproxy/dialer_test.go @@ -261,6 +261,65 @@ func TestHTTPProxyDialRejectsTargetAddrContainingNewlines(t *testing.T) { } } +func TestProxyDialerConstructorsReturnErroringDialFunc(t *testing.T) { + t.Setenv("HTTP_PROXY", "socket6://127.0.0.1:8080") + t.Setenv("HTTPS_PROXY", "socket6://127.0.0.1:8080") + t.Setenv("NO_PROXY", "") + + tests := []struct { + name string + fn func() fasthttp.DialFunc + }{ + { + name: "http", + fn: func() fasthttp.DialFunc { + return FasthttpHTTPDialer("socket6://127.0.0.1:8080") + }, + }, + { + name: "http dual stack", + fn: func() fasthttp.DialFunc { + return FasthttpHTTPDialerDualStack("socket6://127.0.0.1:8080") + }, + }, + { + name: "socks", + fn: func() fasthttp.DialFunc { + return FasthttpSocksDialer("socket6://127.0.0.1:8080") + }, + }, + { + name: "socks dual stack", + fn: func() fasthttp.DialFunc { + return FasthttpSocksDialerDualStack("socket6://127.0.0.1:8080") + }, + }, + { + name: "env", + fn: FasthttpProxyHTTPDialer, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + dialFunc := tt.fn() + if dialFunc == nil { + t.Fatalf("unexpected nil dial func") + } + conn, err := dialFunc("example.com:80") + if conn != nil { + conn.Close() + } + if err == nil { + t.Fatalf("expected error") + } + if err.Error() != "proxy: unknown scheme: socket6" { + t.Fatalf("unexpected error: %v", err) + } + }) + } +} + func startProxyServer(t *testing.T, ports []string, counts []atomic.Int64) (lns []net.Listener) { for i, port := range ports { ln, err := net.Listen("tcp", ":"+port) diff --git a/fasthttpproxy/http.go b/fasthttpproxy/http.go index 7ab1174..31f67bc 100644 --- a/fasthttpproxy/http.go +++ b/fasthttpproxy/http.go @@ -30,8 +30,8 @@ func FasthttpHTTPDialer(proxy string) fasthttp.DialFunc { // } func FasthttpHTTPDialerTimeout(proxy string, timeout time.Duration) fasthttp.DialFunc { d := Dialer{Config: httpproxy.Config{HTTPProxy: proxy, HTTPSProxy: proxy}, Timeout: timeout, ConnectTimeout: timeout} - dialFunc, _ := d.GetDialFunc(false) - return dialFunc + dialFunc, err := d.GetDialFunc(false) + return dialFuncOrError(dialFunc, err) } // FasthttpHTTPDialerDualStack returns a fasthttp.DialFunc that dials using @@ -60,6 +60,6 @@ func FasthttpHTTPDialerDualStackTimeout(proxy string, timeout time.Duration) fas Config: httpproxy.Config{HTTPProxy: proxy, HTTPSProxy: proxy}, Timeout: timeout, ConnectTimeout: timeout, DialDualStack: true, } - dialFunc, _ := d.GetDialFunc(false) - return dialFunc + dialFunc, err := d.GetDialFunc(false) + return dialFuncOrError(dialFunc, err) } diff --git a/fasthttpproxy/proxy_env.go b/fasthttpproxy/proxy_env.go index 342b093..9fef673 100644 --- a/fasthttpproxy/proxy_env.go +++ b/fasthttpproxy/proxy_env.go @@ -34,6 +34,6 @@ func FasthttpProxyHTTPDialer() fasthttp.DialFunc { // } func FasthttpProxyHTTPDialerTimeout(timeout time.Duration) fasthttp.DialFunc { d := Dialer{Timeout: timeout, ConnectTimeout: timeout} - dialFunc, _ := d.GetDialFunc(true) - return dialFunc + dialFunc, err := d.GetDialFunc(true) + return dialFuncOrError(dialFunc, err) } diff --git a/fasthttpproxy/socks5.go b/fasthttpproxy/socks5.go index 12c65e2..caea838 100644 --- a/fasthttpproxy/socks5.go +++ b/fasthttpproxy/socks5.go @@ -15,8 +15,8 @@ import ( // } func FasthttpSocksDialer(proxyAddr string) fasthttp.DialFunc { d := Dialer{Config: httpproxy.Config{HTTPProxy: proxyAddr, HTTPSProxy: proxyAddr}} - dialFunc, _ := d.GetDialFunc(false) - return dialFunc + dialFunc, err := d.GetDialFunc(false) + return dialFuncOrError(dialFunc, err) } // FasthttpSocksDialerDualStack returns a fasthttp.DialFunc that dials using @@ -29,6 +29,6 @@ func FasthttpSocksDialer(proxyAddr string) fasthttp.DialFunc { // } func FasthttpSocksDialerDualStack(proxyAddr string) fasthttp.DialFunc { d := Dialer{Config: httpproxy.Config{HTTPProxy: proxyAddr, HTTPSProxy: proxyAddr}, DialDualStack: true} - dialFunc, _ := d.GetDialFunc(false) - return dialFunc + dialFunc, err := d.GetDialFunc(false) + return dialFuncOrError(dialFunc, err) }