Skip to content

Commit 38e4d30

Browse files
committed
net/tshttpproxy: don't proxy through ourselves
When running a SOCKS or HTTP proxy, configure the tshttpproxy package to drop those addresses from any HTTP_PROXY or HTTPS_PROXY environment variables. Fixes tailscale#7407 Signed-off-by: Andrew Dunham <[email protected]> Change-Id: I6cd7cad7a609c639780484bad521c7514841764b
1 parent 62a1e9a commit 38e4d30

File tree

5 files changed

+207
-4
lines changed

5 files changed

+207
-4
lines changed

cmd/derper/depaware.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa
112112
L golang.org/x/net/bpf from github.com/mdlayher/netlink+
113113
golang.org/x/net/dns/dnsmessage from net+
114114
golang.org/x/net/http/httpguts from net/http
115-
golang.org/x/net/http/httpproxy from net/http
115+
golang.org/x/net/http/httpproxy from net/http+
116116
golang.org/x/net/http2/hpack from net/http
117117
golang.org/x/net/idna from golang.org/x/crypto/acme/autocert+
118118
golang.org/x/net/proxy from tailscale.com/net/netns

cmd/tailscaled/depaware.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -355,7 +355,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
355355
golang.org/x/net/bpf from github.com/mdlayher/genetlink+
356356
golang.org/x/net/dns/dnsmessage from net+
357357
golang.org/x/net/http/httpguts from golang.org/x/net/http2+
358-
golang.org/x/net/http/httpproxy from net/http
358+
golang.org/x/net/http/httpproxy from net/http+
359359
golang.org/x/net/http2 from golang.org/x/net/http2/h2c+
360360
golang.org/x/net/http2/h2c from tailscale.com/ipn/ipnlocal
361361
golang.org/x/net/http2/hpack from golang.org/x/net/http2+

cmd/tailscaled/tailscaled.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ import (
4343
"tailscale.com/net/proxymux"
4444
"tailscale.com/net/socks5"
4545
"tailscale.com/net/tsdial"
46+
"tailscale.com/net/tshttpproxy"
4647
"tailscale.com/net/tstun"
4748
"tailscale.com/paths"
4849
"tailscale.com/safesocket"
@@ -494,11 +495,13 @@ func getLocalBackend(ctx context.Context, logf logger.Logf, logID logid.PublicID
494495
}
495496
}
496497
if socksListener != nil || httpProxyListener != nil {
498+
var addrs []string
497499
if httpProxyListener != nil {
498500
hs := &http.Server{Handler: httpProxyHandler(dialer.UserDial)}
499501
go func() {
500502
log.Fatalf("HTTP proxy exited: %v", hs.Serve(httpProxyListener))
501503
}()
504+
addrs = append(addrs, httpProxyListener.Addr().String())
502505
}
503506
if socksListener != nil {
504507
ss := &socks5.Server{
@@ -508,7 +511,9 @@ func getLocalBackend(ctx context.Context, logf logger.Logf, logID logid.PublicID
508511
go func() {
509512
log.Fatalf("SOCKS5 server exited: %v", ss.Serve(socksListener))
510513
}()
514+
addrs = append(addrs, socksListener.Addr().String())
511515
}
516+
tshttpproxy.SetSelfProxy(addrs...)
512517
}
513518

514519
e = wgengine.NewWatchdog(e)

net/tshttpproxy/tshttpproxy.go

Lines changed: 76 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,16 @@ import (
99
"context"
1010
"fmt"
1111
"log"
12+
"net"
1213
"net/http"
1314
"net/url"
1415
"os"
16+
"runtime"
17+
"strings"
1518
"sync"
1619
"time"
20+
21+
"golang.org/x/net/http/httpproxy"
1722
)
1823

1924
// InvalidateCache invalidates the package-level cache for ProxyFromEnvironment.
@@ -27,9 +32,24 @@ func InvalidateCache() {
2732

2833
var (
2934
mu sync.Mutex
30-
noProxyUntil time.Time // if non-zero, time at which ProxyFromEnvironment should check again
35+
noProxyUntil time.Time // if non-zero, time at which ProxyFromEnvironment should check again
36+
config *httpproxy.Config // used to create proxyFunc
37+
proxyFunc func(*url.URL) (*url.URL, error)
3138
)
3239

40+
func getProxyFunc() func(*url.URL) (*url.URL, error) {
41+
// Create config/proxyFunc if it's not created
42+
mu.Lock()
43+
defer mu.Unlock()
44+
if config == nil {
45+
config = httpproxy.FromEnvironment()
46+
}
47+
if proxyFunc == nil {
48+
proxyFunc = config.ProxyFunc()
49+
}
50+
return proxyFunc
51+
}
52+
3353
// setNoProxyUntil stops calls to sysProxyEnv (if any) for the provided duration.
3454
func setNoProxyUntil(d time.Duration) {
3555
mu.Lock()
@@ -39,6 +59,59 @@ func setNoProxyUntil(d time.Duration) {
3959

4060
var _ = setNoProxyUntil // quiet staticcheck; Windows uses the above, more might later
4161

62+
// SetSelfProxy configures this package to avoid proxying through any of the
63+
// provided addresses–e.g. if they refer to proxies being run by this process.
64+
func SetSelfProxy(addrs ...string) {
65+
mu.Lock()
66+
defer mu.Unlock()
67+
68+
// Ensure we have a valid config
69+
if config == nil {
70+
config = httpproxy.FromEnvironment()
71+
}
72+
73+
normalizeHostPort := func(s string) string {
74+
host, portStr, err := net.SplitHostPort(s)
75+
if err != nil {
76+
return s
77+
}
78+
79+
// Normalize the localhost IP into "localhost", to avoid IPv4/IPv6 confusion.
80+
if host == "127.0.0.1" || host == "::1" {
81+
return "localhost:" + portStr
82+
}
83+
84+
// On Linux, all 127.0.0.1/8 IPs are also localhost.
85+
if runtime.GOOS == "linux" && strings.HasPrefix(host, "127.0.0.") {
86+
return "localhost:" + portStr
87+
}
88+
89+
return s
90+
}
91+
92+
normHTTP := normalizeHostPort(config.HTTPProxy)
93+
normHTTPS := normalizeHostPort(config.HTTPSProxy)
94+
95+
// If any of our proxy variables point to one of the configured
96+
// addresses, ignore them.
97+
for _, addr := range addrs {
98+
normAddr := normalizeHostPort(addr)
99+
if normHTTP != "" && normHTTP == normAddr {
100+
log.Printf("tshttpproxy: skipping HTTP_PROXY pointing to self: %q", addr)
101+
config.HTTPProxy = ""
102+
normHTTP = ""
103+
}
104+
if normHTTPS != "" && normHTTPS == normAddr {
105+
log.Printf("tshttpproxy: skipping HTTPS_PROXY pointing to self: %q", addr)
106+
config.HTTPSProxy = ""
107+
normHTTPS = ""
108+
}
109+
}
110+
111+
// Invalidate to cause it to get re-created
112+
proxyFunc = nil
113+
}
114+
42115
// sysProxyFromEnv, if non-nil, specifies a platform-specific ProxyFromEnvironment
43116
// func to use if http.ProxyFromEnvironment doesn't return a proxy.
44117
// For example, WPAD PAC files on Windows.
@@ -48,7 +121,8 @@ var sysProxyFromEnv func(*http.Request) (*url.URL, error)
48121
// but additionally does OS-specific proxy lookups if the environment variables
49122
// alone don't specify a proxy.
50123
func ProxyFromEnvironment(req *http.Request) (*url.URL, error) {
51-
u, err := http.ProxyFromEnvironment(req)
124+
localProxyFunc := getProxyFunc()
125+
u, err := localProxyFunc(req.URL)
52126
if u != nil && err == nil {
53127
return u, nil
54128
}

net/tshttpproxy/tshttpproxy_test.go

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,3 +81,127 @@ func TestProxyFromEnvironment_setNoProxyUntil(t *testing.T) {
8181
}
8282

8383
}
84+
85+
func TestSetSelfProxy(t *testing.T) {
86+
// Ensure we clean everything up at the end of our test
87+
t.Cleanup(func() {
88+
config = nil
89+
proxyFunc = nil
90+
})
91+
92+
testCases := []struct {
93+
name string
94+
env map[string]string
95+
self []string
96+
wantHTTP string
97+
wantHTTPS string
98+
}{
99+
{
100+
name: "no self proxy",
101+
env: map[string]string{
102+
"HTTP_PROXY": "127.0.0.1:1234",
103+
"HTTPS_PROXY": "127.0.0.1:1234",
104+
},
105+
self: nil,
106+
wantHTTP: "127.0.0.1:1234",
107+
wantHTTPS: "127.0.0.1:1234",
108+
},
109+
{
110+
name: "skip proxies",
111+
env: map[string]string{
112+
"HTTP_PROXY": "127.0.0.1:1234",
113+
"HTTPS_PROXY": "127.0.0.1:5678",
114+
},
115+
self: []string{"127.0.0.1:1234", "127.0.0.1:5678"},
116+
wantHTTP: "", // skipped
117+
wantHTTPS: "", // skipped
118+
},
119+
{
120+
name: "localhost normalization of env var",
121+
env: map[string]string{
122+
"HTTP_PROXY": "localhost:1234",
123+
"HTTPS_PROXY": "[::1]:5678",
124+
},
125+
self: []string{"127.0.0.1:1234", "127.0.0.1:5678"},
126+
wantHTTP: "", // skipped
127+
wantHTTPS: "", // skipped
128+
},
129+
{
130+
name: "localhost normalization of addr",
131+
env: map[string]string{
132+
"HTTP_PROXY": "127.0.0.1:1234",
133+
"HTTPS_PROXY": "127.0.0.1:1234",
134+
},
135+
self: []string{"[::1]:1234"},
136+
wantHTTP: "", // skipped
137+
wantHTTPS: "", // skipped
138+
},
139+
{
140+
name: "no ports",
141+
env: map[string]string{
142+
"HTTP_PROXY": "myproxy",
143+
"HTTPS_PROXY": "myproxy",
144+
},
145+
self: []string{"127.0.0.1:1234"},
146+
wantHTTP: "myproxy",
147+
wantHTTPS: "myproxy",
148+
},
149+
}
150+
for _, tt := range testCases {
151+
t.Run(tt.name, func(t *testing.T) {
152+
for k, v := range tt.env {
153+
oldEnv, found := os.LookupEnv(k)
154+
if found {
155+
t.Cleanup(func() {
156+
os.Setenv(k, oldEnv)
157+
})
158+
}
159+
os.Setenv(k, v)
160+
}
161+
162+
// Reset computed variables
163+
config = nil
164+
proxyFunc = func(*url.URL) (*url.URL, error) {
165+
panic("should not be called")
166+
}
167+
168+
SetSelfProxy(tt.self...)
169+
170+
if got := config.HTTPProxy; got != tt.wantHTTP {
171+
t.Errorf("got HTTPProxy=%q; want %q", got, tt.wantHTTP)
172+
}
173+
if got := config.HTTPSProxy; got != tt.wantHTTPS {
174+
t.Errorf("got HTTPSProxy=%q; want %q", got, tt.wantHTTPS)
175+
}
176+
if proxyFunc != nil {
177+
t.Errorf("wanted nil proxyFunc")
178+
}
179+
180+
// Verify that we do actually proxy through the
181+
// expected proxy, if we have one configured.
182+
pf := getProxyFunc()
183+
if tt.wantHTTP != "" {
184+
want := "http://" + tt.wantHTTP
185+
186+
uu, _ := url.Parse("http://tailscale.com")
187+
dest, err := pf(uu)
188+
if err != nil {
189+
t.Error(err)
190+
} else if dest.String() != want {
191+
t.Errorf("got dest=%q; want %q", dest, want)
192+
}
193+
}
194+
if tt.wantHTTPS != "" {
195+
want := "http://" + tt.wantHTTPS
196+
197+
uu, _ := url.Parse("https://tailscale.com")
198+
dest, err := pf(uu)
199+
if err != nil {
200+
t.Error(err)
201+
} else if dest.String() != want {
202+
t.Errorf("got dest=%q; want %q", dest, want)
203+
}
204+
}
205+
})
206+
}
207+
}

0 commit comments

Comments
 (0)