Skip to content

Commit 3475981

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. 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 <[email protected]>
1 parent 57e36d5 commit 3475981

File tree

2 files changed

+138
-1
lines changed

2 files changed

+138
-1
lines changed

sockets/sockets.go

+33-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ package sockets
44
import (
55
"errors"
66
"net/http"
7+
"net/url"
78
)
89

910
// ErrProtocolNotAvailable is returned when a given transport protocol is not provided by the operating system.
@@ -20,7 +21,38 @@ func ConfigureTransport(tr *http.Transport, proto, addr string) error {
2021
case "npipe":
2122
return configureNpipeTransport(tr, proto, addr)
2223
default:
23-
tr.Proxy = http.ProxyFromEnvironment
24+
tr.Proxy = TCPProxyFromEnvironment
2425
}
2526
return nil
2627
}
28+
29+
// TCPProxyFromEnvironment wraps http.ProxyFromEnvironment, to preserve the
30+
// pre-go1.16 behavior for URLs using the 'tcp://' scheme.
31+
//
32+
// Prior to go1.16, `https://` schemes would use HTTPS_PROXY, and any other
33+
// scheme would use HTTP_PROXY. However, https://github.com/golang/net/commit/7b1cca2348c07eb09fef635269c8e01611260f9f
34+
// (per a request in golang/go#40909) changed this behavior to only use
35+
// HTTP_PROXY for `http://` schemes, no longer using a proxy for any other
36+
// scheme.
37+
//
38+
// Docker uses the `tcp://` scheme as a default for API connections, to indicate
39+
// that the API is not "purely" HTTP. Various parts in the code also *require*
40+
// this scheme to be used. While we could change the default and allow http(s)
41+
// schemes to be used, doing so will take time, taking into account that there
42+
// are many installs in existence that have tcp:// configured as DOCKER_HOST.
43+
//
44+
// This function detects if the `tcp://` scheme is used; if it is, it creates
45+
// a shallow copy of req, containing just the URL, and overrides the scheme with
46+
// 'http', which should be sufficient to perform proxy detection.
47+
// For other (non-'tcp://') schemes, http.ProxyFromEnvironment is called without
48+
// altering the request.
49+
func TCPProxyFromEnvironment(req *http.Request) (*url.URL, error) {
50+
if req.URL.Scheme != "tcp" {
51+
return http.ProxyFromEnvironment(req)
52+
}
53+
u := req.URL
54+
if u.Scheme == "tcp" {
55+
u.Scheme = "http"
56+
}
57+
return http.ProxyFromEnvironment(&http.Request{URL: u})
58+
}

sockets/sockets_test.go

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

0 commit comments

Comments
 (0)