소스 검색

Do not use custom DNS resolver to dial proxy upstreams

Fixes #439.

When `[network] dns = "tls://..."` (or "https://...") is set, the
resulting *net.Resolver gets attached to the base network's NativeDialer
and was previously also handed to golang.org/x/net/proxy.FromURL via
NewProxyNetwork. As a result, the SOCKS5 client used the user's DoT/DoH
resolver to look up the SOCKS server's own hostname (e.g. "xray" inside
a docker compose stack). Public DNS-over-TLS resolvers don't know about
docker-compose service names, k8s service DNS, /etc/hosts entries, or
corporate split-horizon DNS, so the upstream lookup returned NXDOMAIN
and the proxy chain broke with a misleading "lookup xray on
127.0.0.11:53: no such host" error.

The custom DNS resolver exists to bypass DPI poisoning when resolving
public censored names like Telegram DCs or the SNI/fronting host. Proxy
server addresses are almost always internal and should be resolved via
the system resolver instead. This change introduces proxyServerDialer,
which copies the timeout and fallback-delay from the base dialer but
leaves Resolver==nil, and uses it for the SOCKS upstream.

The new internal test asserts the structural property directly: the
returned dialer must not inherit the base's custom resolver.
pull/483/head
dolonet 3 주 전
부모
커밋
488ba2b60a
2개의 변경된 파일66개의 추가작업 그리고 1개의 파일을 삭제
  1. 17
    1
      network/v2/proxy_network.go
  2. 49
    0
      network/v2/proxy_network_internal_test.go

+ 17
- 1
network/v2/proxy_network.go 파일 보기

@@ -3,6 +3,7 @@ package network
3 3
 import (
4 4
 	"context"
5 5
 	"fmt"
6
+	"net"
6 7
 	"net/http"
7 8
 	"net/url"
8 9
 
@@ -39,8 +40,23 @@ func (p proxyNetwork) MakeHTTPClient(
39 40
 	return p.Network.MakeHTTPClient(dialFunc)
40 41
 }
41 42
 
43
+// proxyServerDialer is the dialer used to connect to a SOCKS upstream. It
44
+// copies timeout and fallback-delay from the base dialer but drops the custom
45
+// Resolver: the user-supplied DoT/DoH resolver is for bypassing DPI on public
46
+// names (Telegram DCs, fronting host), whereas SOCKS upstream addresses are
47
+// usually internal (docker compose, k8s, /etc/hosts) and must be resolved by
48
+// the system resolver. See https://github.com/9seconds/mtg/issues/439.
49
+func proxyServerDialer(base mtglib.Network) *net.Dialer {
50
+	nd := base.NativeDialer()
51
+
52
+	return &net.Dialer{
53
+		Timeout:       nd.Timeout,
54
+		FallbackDelay: nd.FallbackDelay,
55
+	}
56
+}
57
+
42 58
 func NewProxyNetwork(base mtglib.Network, proxyURL *url.URL) (*proxyNetwork, error) {
43
-	socks, err := proxy.FromURL(proxyURL, base.NativeDialer())
59
+	socks, err := proxy.FromURL(proxyURL, proxyServerDialer(base))
44 60
 	if err != nil {
45 61
 		return nil, fmt.Errorf("cannot build proxy dialer: %w", err)
46 62
 	}

+ 49
- 0
network/v2/proxy_network_internal_test.go 파일 보기

@@ -0,0 +1,49 @@
1
+package network
2
+
3
+import (
4
+	"context"
5
+	"errors"
6
+	"net"
7
+	"testing"
8
+	"time"
9
+)
10
+
11
+// Regression test for https://github.com/9seconds/mtg/issues/439: the dialer
12
+// used to reach a SOCKS upstream must not inherit the user-supplied DoT/DoH
13
+// resolver, otherwise internal names (docker compose, k8s, /etc/hosts) fail
14
+// to resolve through a public resolver that does not know them.
15
+func TestProxyServerDialerDropsCustomResolver(t *testing.T) {
16
+	customResolver := &net.Resolver{
17
+		PreferGo: true,
18
+		Dial: func(_ context.Context, _, _ string) (net.Conn, error) {
19
+			return nil, errors.New("custom resolver must not be queried for SOCKS upstream")
20
+		},
21
+	}
22
+
23
+	base := New(
24
+		customResolver,
25
+		"mtg-test",
26
+		7*time.Second,
27
+		0,
28
+		0,
29
+		DefaultKeepAliveConfig,
30
+	)
31
+
32
+	if base.NativeDialer().Resolver != customResolver {
33
+		t.Fatalf("base.NativeDialer().Resolver = %v, want custom resolver", base.NativeDialer().Resolver)
34
+	}
35
+
36
+	d := proxyServerDialer(base)
37
+
38
+	if d.Resolver != nil {
39
+		t.Errorf("proxyServerDialer().Resolver = %v, want nil (must use OS resolver)", d.Resolver)
40
+	}
41
+
42
+	if d.Timeout != base.NativeDialer().Timeout {
43
+		t.Errorf("proxyServerDialer().Timeout = %v, want %v", d.Timeout, base.NativeDialer().Timeout)
44
+	}
45
+
46
+	if d.FallbackDelay != base.NativeDialer().FallbackDelay {
47
+		t.Errorf("proxyServerDialer().FallbackDelay = %v, want %v", d.FallbackDelay, base.NativeDialer().FallbackDelay)
48
+	}
49
+}

Loading…
취소
저장