Skip to content

Commit f0931e6

Browse files
committed
sockets: add TCPProxyFromEnvironment to keep pre-go1.16 behavior
TCPProxyFromEnvironment wraps http.ProxyFromEnvironment, to preserve the pre-go1.16 behavior for URLs using the 'tcp://' scheme. For other schemes, golang's standard behavior is preserved (and depends on the Go version used). Prior to go1.16, `https://` schemes would use HTTPS_PROXY, and any other scheme would use HTTP_PROXY. However, golang/net@7b1cca2 (per a request in golang/go#40909) changed this behavior to only use HTTP_PROXY for `http://` schemes, no longer using a proxy for any other scheme. Docker uses the `tcp://` scheme as a default for API connections, to indicate that the API is not "purely" HTTP. Various parts in the code also *require* this scheme to be used. While we could change the default and allow http(s) schemes to be used, doing so will take time, taking into account that there are many installs in existence that have tcp:// configured as DOCKER_HOST. This function detects if the `tcp://` scheme is used; if it is, it creates a shallow copy of req, containing just the URL, and overrides the scheme with 'http', which should be sufficient to perform proxy detection. For other (non-'tcp://') schemes, http.ProxyFromEnvironment is called without altering the request. Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
1 parent b115e42 commit f0931e6

2 files changed

Lines changed: 117 additions & 1 deletion

File tree

sockets/sockets.go

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"fmt"
88
"net"
99
"net/http"
10+
"net/url"
1011
"syscall"
1112
"time"
1213
)
@@ -41,7 +42,7 @@ func ConfigureTransport(tr *http.Transport, proto, addr string) error {
4142
case "npipe":
4243
return configureNpipeTransport(tr, addr)
4344
default:
44-
tr.Proxy = http.ProxyFromEnvironment
45+
tr.Proxy = TCPProxyFromEnvironment
4546
tr.DisableCompression = false
4647
tr.DialContext = (&net.Dialer{
4748
Timeout: defaultTimeout,
@@ -64,3 +65,35 @@ func configureUnixTransport(tr *http.Transport, addr string) error {
6465
}
6566
return nil
6667
}
68+
69+
// TCPProxyFromEnvironment wraps [http.ProxyFromEnvironment] to preserve the
70+
// pre-go1.16 behavior for URLs using the 'tcp://' scheme. For other schemes,
71+
// golang's standard behavior is preserved (and depends on the Go version used).
72+
//
73+
// Prior to go1.16, "https://" schemes would use HTTPS_PROXY, and any other
74+
// scheme would use HTTP_PROXY. However, https://github.com/golang/net/commit/7b1cca2348c07eb09fef635269c8e01611260f9f
75+
// (per a request in golang/go#40909) changed this behavior to only use
76+
// HTTP_PROXY for "http://" schemes, no longer using a proxy for any other
77+
// scheme.
78+
//
79+
// Docker uses the "tcp://" scheme as a default for API connections, to indicate
80+
// that the API is not "purely" HTTP. Various parts in the code also *require*
81+
// this scheme to be used. While we could change the default and allow http(s)
82+
// schemes to be used, doing so will take time, taking into account that there
83+
// are many installs in existence that have "tcp://" configured as DOCKER_HOST.
84+
//
85+
// This function detects if the "tcp://" scheme is used; if it is, it creates
86+
// a shallow copy of req, containing just the URL, and overrides the scheme with
87+
// "http", which should be sufficient to perform proxy detection.
88+
// For other (non-"tcp://") schemes, [http.ProxyFromEnvironment] is called without
89+
// altering the request.
90+
func TCPProxyFromEnvironment(req *http.Request) (*url.URL, error) {
91+
if req.URL.Scheme != "tcp" {
92+
return http.ProxyFromEnvironment(req)
93+
}
94+
u := req.URL
95+
if u.Scheme == "tcp" {
96+
u.Scheme = "http"
97+
}
98+
return http.ProxyFromEnvironment(&http.Request{URL: u})
99+
}

sockets/sockets_test.go

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
package sockets
2+
3+
import (
4+
"net/http"
5+
"net/url"
6+
"testing"
7+
)
8+
9+
const (
10+
httpProxy = "http://proxy.example.com"
11+
httpsProxy = "https://proxy.example.com"
12+
)
13+
14+
func TestConfigureTransportProxy(t *testing.T) {
15+
// roughly based on defaultHTTPClient in the docker client
16+
u := &url.URL{
17+
Scheme: "tcp",
18+
Host: "docker.acme.example.com",
19+
}
20+
transport := new(http.Transport)
21+
err := ConfigureTransport(transport, u.Scheme, u.Host)
22+
if err != nil {
23+
t.Fatal(err)
24+
}
25+
t.Setenv("HTTP_PROXY", httpProxy)
26+
t.Setenv("HTTPS_PROXY", httpsProxy)
27+
28+
request, err := http.NewRequest(http.MethodGet, "tcp://docker.acme.example.com:2376", nil)
29+
if err != nil {
30+
t.Fatal(err)
31+
}
32+
proxyURL, err := transport.Proxy(request)
33+
if err != nil {
34+
t.Fatal(err)
35+
}
36+
if proxyURL.String() != httpProxy {
37+
t.Fatalf("expected %s, got %s", httpProxy, proxyURL)
38+
}
39+
}
40+
41+
func TestTCPProxyFromEnvironment(t *testing.T) {
42+
t.Setenv("HTTP_PROXY", httpProxy)
43+
t.Setenv("HTTPS_PROXY", httpsProxy)
44+
45+
tests := []struct {
46+
url string
47+
expected string
48+
}{
49+
{
50+
url: "tcp://example.com:2376",
51+
expected: httpProxy,
52+
},
53+
{
54+
url: "http://example.com:2375",
55+
expected: httpProxy,
56+
},
57+
{
58+
url: "https://example.com:2376",
59+
expected: httpsProxy,
60+
},
61+
}
62+
63+
for _, tc := range tests {
64+
t.Run(tc.url, func(t *testing.T) {
65+
request, err := http.NewRequest(http.MethodGet, tc.url, nil)
66+
if err != nil {
67+
t.Fatal(err)
68+
}
69+
70+
proxyURL, err := TCPProxyFromEnvironment(request)
71+
if err != nil {
72+
t.Fatal(err)
73+
}
74+
if tc.expected == "" {
75+
if proxyURL != nil {
76+
t.Fatalf("expected no proxy, got %s", proxyURL)
77+
}
78+
} else if proxyURL.String() != tc.expected {
79+
t.Fatalf("expected %s, got %s", tc.expected, proxyURL)
80+
}
81+
})
82+
}
83+
}

0 commit comments

Comments
 (0)