Bladeren bron

Merge remote-tracking branch 'origin/stable' into v2

tags/v2.1.13
9seconds 2 maanden geleden
bovenliggende
commit
f5244c2bfd

+ 25
- 0
essentials/conns.go Bestand weergeven

@@ -24,3 +24,28 @@ type Conn interface {
24 24
 	CloseableReader
25 25
 	CloseableWriter
26 26
 }
27
+
28
+type netConnWrapper struct {
29
+	net.Conn
30
+}
31
+
32
+func (n netConnWrapper) CloseRead() error {
33
+	if conn, ok := n.Conn.(CloseableReader); ok {
34
+		return conn.CloseRead()
35
+	}
36
+
37
+	return n.Close()
38
+}
39
+
40
+func (n netConnWrapper) CloseWrite() error {
41
+	if conn, ok := n.Conn.(CloseableWriter); ok {
42
+		return conn.CloseWrite()
43
+	}
44
+
45
+	return n.Close()
46
+}
47
+
48
+// WrapConn wraps a generic [net.Conn] into Conn.
49
+func WrapNetConn(conn net.Conn) Conn {
50
+	return netConnWrapper{conn}
51
+}

+ 27
- 14
example.config.toml Bestand weergeven

@@ -134,8 +134,33 @@ allow-fallback-on-unknown-dc = false
134 134
 # it has to access.
135 135
 #
136 136
 # By default we use Cloudflare.
137
+#
138
+# DEPRECATED option:
139
+#  If dns option is specified, it will be used instead
137 140
 doh-ip = "1.1.1.1"
138 141
 
142
+# Starting from mtg v2.1.12 we have changed a configuration for DNS. Now it
143
+# supports DNS-over-HTTPS, DNS-over-TLS, custom UDP resolver and system
144
+# resolver.
145
+#
146
+# Here is how to define DNS-over-HTTPS:
147
+#  - https://1.1.1.1
148
+#  - https://1.1.1.1/dns-query
149
+#  - https://cloudflare-dns.com/dns-query
150
+#  - https://cloudflare-dns.com
151
+#
152
+# Here is how to define DNS-over-TLS:
153
+#  - tls://1.1.1.1
154
+#  - tls://cloudflare-dns.com
155
+#
156
+# Here is how to define a custom UDP resolver (we support only IPs here)
157
+#  - 1.1.1.1
158
+#  - udp://1.1.1.1
159
+#
160
+# If you set it to empty string, default resolver will be used.
161
+# But please comment out doh-ip
162
+dns = "https://1.1.1.1"
163
+
139 164
 # mtg can work via proxies (for now, we support only socks5). Proxy
140 165
 # configuration is done via list. So, you can specify many proxies
141 166
 # there.
@@ -149,25 +174,13 @@ doh-ip = "1.1.1.1"
149 174
 #
150 175
 # Proxy configuration is done via ordinary URI schema:
151 176
 #
152
-#     socks5://user:password@host:port?open_threshold=5&half_open_timeout=1m&reset_failures_timeout=10s
177
+#     socks5://user:password@host:port
153 178
 #
154 179
 # Only socks5 proxy is used. user/password is optional. As you can
155 180
 # see, you can specify some parameters in GET query. These parameters
156 181
 # configure circuit breaker.
157
-#
158
-# open_threshold means a number of errors which should happen so we stop
159
-# use a proxy.
160
-#
161
-# half_open_timeout means a time period (in Golang duration notation)
162
-# after which we can retry with this proxy
163
-#
164
-# reset_failures_timeout means a time period when we flush out errors
165
-# when circuit breaker in closed state.
166
-#
167
-# Please see https://docs.microsoft.com/en-us/azure/architecture/patterns/circuit-breaker
168
-# on details about circuit breakers.
169 182
 proxies = [
170
-    # "socks5://user:password@host:port?open_threshold=5&half_open_timeout=1m&reset_failures_timeout=10s"
183
+    # "socks5://user:password@host:port"
171 184
 ]
172 185
 
173 186
 # network timeouts define different settings for timeouts. tcp timeout

+ 3
- 1
go.mod Bestand weergeven

@@ -21,14 +21,16 @@ require (
21 21
 	github.com/stretchr/testify v1.11.1
22 22
 	github.com/tylertreat/BoomFilters v0.0.0-20251117164519-53813c36cc1b
23 23
 	golang.org/x/crypto v0.48.0
24
-	golang.org/x/net v0.49.0 // indirect
24
+	golang.org/x/net v0.51.0
25 25
 	golang.org/x/sys v0.41.0
26 26
 	google.golang.org/protobuf v1.36.11 // indirect
27 27
 )
28 28
 
29 29
 require (
30
+	github.com/ncruces/go-dns v1.3.2
30 31
 	github.com/pelletier/go-toml/v2 v2.2.4
31 32
 	github.com/pires/go-proxyproto v0.11.0
33
+	github.com/things-go/go-socks5 v0.1.0
32 34
 	github.com/txthinking/socks5 v0.0.0-20251011041537-5c31f201a10e
33 35
 	github.com/yl2chen/cidranger v1.0.2
34 36
 )

+ 6
- 2
go.sum Bestand weergeven

@@ -51,6 +51,8 @@ github.com/miekg/dns v1.1.51 h1:0+Xg7vObnhrz/4ZCZcZh7zPXlmU0aveS2HDBd0m0qSo=
51 51
 github.com/miekg/dns v1.1.51/go.mod h1:2Z9d3CP1LQWihRZUf29mQ19yDThaI4DAYzte2CaQW5c=
52 52
 github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
53 53
 github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
54
+github.com/ncruces/go-dns v1.3.2 h1:kBLuUZBgkQ4qF4WDXZRQ4rG0Gk6sLVJQ5tESkWrxUa0=
55
+github.com/ncruces/go-dns v1.3.2/go.mod h1:tuzixNY8PY/M7yUzcvRbUaeLs3ifIdydpi5H2bfRU+s=
54 56
 github.com/panjf2000/ants/v2 v2.11.5 h1:a7LMnMEeux/ebqTux140tRiaqcFTV0q2bEHF03nl6Rg=
55 57
 github.com/panjf2000/ants/v2 v2.11.5/go.mod h1:8u92CYMUc6gyvTIw8Ru7Mt7+/ESnJahz5EVtqfrilek=
56 58
 github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
@@ -89,6 +91,8 @@ github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXl
89 91
 github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
90 92
 github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
91 93
 github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
94
+github.com/things-go/go-socks5 v0.1.0 h1:4f5dz0iMQ6cA4wseFmyLmCHmg3SWJTW92ndrKS6oERg=
95
+github.com/things-go/go-socks5 v0.1.0/go.mod h1:Riabiyu52kLsla0YmJqunt1c1JEl6iXSr4bRd7swFEA=
92 96
 github.com/txthinking/runnergroup v0.0.0-20210608031112-152c7c4432bf/go.mod h1:CLUSJbazqETbaR+i0YAhXBICV9TrKH93pziccMhmhpM=
93 97
 github.com/txthinking/runnergroup v0.0.0-20250224021307-5864ffeb65ae h1:ArVM1jICfm7g4E4dBet+KHUFMLuxmj1Nxdp/tr3ByCU=
94 98
 github.com/txthinking/runnergroup v0.0.0-20250224021307-5864ffeb65ae/go.mod h1:cldYm15/XHcGt7ndItnEWHwFZo7dinU+2QoyjfErhsI=
@@ -115,8 +119,8 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL
115 119
 golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
116 120
 golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
117 121
 golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
118
-golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
119
-golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
122
+golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
123
+golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
120 124
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
121 125
 golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
122 126
 golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=

+ 24
- 27
internal/cli/run_proxy.go Bestand weergeven

@@ -4,7 +4,6 @@ import (
4 4
 	"context"
5 5
 	"fmt"
6 6
 	"net"
7
-	"net/url"
8 7
 	"os"
9 8
 
10 9
 	"github.com/9seconds/mtg/v2/antireplay"
@@ -16,7 +15,7 @@ import (
16 15
 	"github.com/9seconds/mtg/v2/ipblocklist/files"
17 16
 	"github.com/9seconds/mtg/v2/logger"
18 17
 	"github.com/9seconds/mtg/v2/mtglib"
19
-	"github.com/9seconds/mtg/v2/network"
18
+	"github.com/9seconds/mtg/v2/network/v2"
20 19
 	"github.com/9seconds/mtg/v2/stats"
21 20
 	"github.com/pires/go-proxyproto"
22 21
 	"github.com/rs/zerolog"
@@ -40,43 +39,41 @@ func makeLogger(conf *config.Config) mtglib.Logger {
40 39
 }
41 40
 
42 41
 func makeNetwork(conf *config.Config, version string) (mtglib.Network, error) {
43
-	tcpTimeout := conf.Network.Timeout.TCP.Get(network.DefaultTimeout)
44
-	httpTimeout := conf.Network.Timeout.HTTP.Get(network.DefaultHTTPTimeout)
45
-	dohIP := conf.Network.DOHIP.Get(net.ParseIP(network.DefaultDOHHostname)).String()
46
-	userAgent := "mtg/" + version
47
-
48
-	baseDialer, err := network.NewDefaultDialer(tcpTimeout, 0)
42
+	resolver, err := network.GetDNS(conf.GetDNS())
49 43
 	if err != nil {
50
-		return nil, fmt.Errorf("cannot build a default dialer: %w", err)
51
-	}
52
-
53
-	if len(conf.Network.Proxies) == 0 {
54
-		return network.NewNetwork(baseDialer, userAgent, dohIP, httpTimeout) //nolint: wrapcheck
44
+		return nil, fmt.Errorf("cannot create DNS resolver: %w", err)
55 45
 	}
56 46
 
57
-	proxyURLs := make([]*url.URL, 0, len(conf.Network.Proxies))
58
-
59
-	for _, v := range conf.Network.Proxies {
60
-		if value := v.Get(nil); value != nil {
61
-			proxyURLs = append(proxyURLs, value)
62
-		}
63
-	}
47
+	base := network.New(
48
+		resolver,
49
+		"mtg/"+version,
50
+		conf.Network.Timeout.TCP.Get(0),
51
+		conf.Network.Timeout.HTTP.Get(0),
52
+		conf.Network.Timeout.Idle.Get(0),
53
+	)
64 54
 
65
-	if len(proxyURLs) == 1 {
66
-		socksDialer, err := network.NewSocks5Dialer(baseDialer, proxyURLs[0])
55
+	proxyDialers := make([]network.Network, len(conf.Network.Proxies))
56
+	for idx, v := range conf.Network.Proxies {
57
+		value, err := network.NewProxyNetwork(base, v.Get(nil))
67 58
 		if err != nil {
68
-			return nil, fmt.Errorf("cannot build socks5 dialer: %w", err)
59
+			return nil, fmt.Errorf("cannot use %v for proxy url: %w", v.Get(nil), err)
69 60
 		}
61
+		proxyDialers[idx] = value
62
+	}
70 63
 
71
-		return network.NewNetwork(socksDialer, userAgent, dohIP, httpTimeout) //nolint: wrapcheck
64
+	switch len(proxyDialers) {
65
+	case 0:
66
+		return base, nil
67
+	case 1:
68
+		return proxyDialers[0], nil
72 69
 	}
73 70
 
74
-	socksDialer, err := network.NewLoadBalancedSocks5Dialer(baseDialer, proxyURLs)
71
+	value, err := network.Join(proxyDialers...)
75 72
 	if err != nil {
76
-		return nil, fmt.Errorf("cannot build socks5 dialer: %w", err)
73
+		panic(err)
77 74
 	}
78 75
 
79
-	return network.NewNetwork(socksDialer, userAgent, dohIP, httpTimeout) //nolint: wrapcheck
76
+	return value, nil
80 77
 }
81 78
 
82 79
 func makeAntiReplayCache(conf *config.Config) mtglib.AntiReplayCache {

+ 12
- 0
internal/config/config.go Bestand weergeven

@@ -5,6 +5,7 @@ import (
5 5
 	"encoding/json"
6 6
 	"fmt"
7 7
 	"net"
8
+	"net/url"
8 9
 
9 10
 	"github.com/9seconds/mtg/v2/mtglib"
10 11
 )
@@ -56,6 +57,7 @@ type Config struct {
56 57
 			Idle TypeDuration `json:"idle"`
57 58
 		} `json:"timeout"`
58 59
 		DOHIP   TypeIP         `json:"dohIp"`
60
+		DNS     TypeDNSURI     `json:"dns"`
59 61
 		Proxies []TypeProxyURL `json:"proxies"`
60 62
 	} `json:"network"`
61 63
 	Stats struct {
@@ -76,6 +78,16 @@ type Config struct {
76 78
 	} `json:"stats"`
77 79
 }
78 80
 
81
+func (c *Config) GetDNS() *url.URL {
82
+	var dohURL *url.URL
83
+
84
+	if dohIP := c.Network.DOHIP.Get(nil); dohIP != nil {
85
+		dohURL, _ = url.Parse("https://" + dohIP.String())
86
+	}
87
+
88
+	return c.Network.DNS.Get(dohURL)
89
+}
90
+
79 91
 func (c *Config) GetDomainFrontingPort(defaultValue uint) uint {
80 92
 	if port := c.DomainFronting.Port.Get(0); port != 0 {
81 93
 		return port

+ 1
- 0
internal/config/parse.go Bestand weergeven

@@ -52,6 +52,7 @@ type tomlConfig struct {
52 52
 			Idle string `toml:"idle" json:"idle,omitempty"`
53 53
 		} `toml:"timeout" json:"timeout,omitempty"`
54 54
 		DOHIP   string   `toml:"doh-ip" json:"dohIp,omitempty"`
55
+		DNS     string   `toml:"dns" json:"dns,omitempty"`
55 56
 		Proxies []string `toml:"proxies" json:"proxies,omitempty"`
56 57
 	} `toml:"network" json:"network,omitempty"`
57 58
 	Stats struct {

+ 69
- 0
internal/config/type_dns_uri.go Bestand weergeven

@@ -0,0 +1,69 @@
1
+package config
2
+
3
+import (
4
+	"fmt"
5
+	"net"
6
+	"net/url"
7
+)
8
+
9
+type TypeDNSURI struct {
10
+	Value *url.URL
11
+}
12
+
13
+func (t *TypeDNSURI) Set(value string) error {
14
+	parsed, err := url.Parse(value)
15
+	if err != nil {
16
+		return fmt.Errorf("value is not URI: %w", err)
17
+	}
18
+
19
+	if parsed.Host == "" {
20
+		parsed.Host = parsed.Path
21
+		parsed.Path = ""
22
+		parsed.Scheme = "udp"
23
+	}
24
+
25
+	switch parsed.Scheme {
26
+	case "https", "tls":
27
+	case "udp":
28
+		if ip := net.ParseIP(parsed.Hostname()); ip == nil {
29
+			return fmt.Errorf("simple DNS must IP address: %s", parsed.Hostname())
30
+		}
31
+	default:
32
+		return fmt.Errorf("unsupported DNS type %s", parsed.Scheme)
33
+	}
34
+
35
+	if parsed.Scheme != "https" && parsed.Path != "" {
36
+		return fmt.Errorf("path is supported only for DoH: %s", parsed)
37
+	}
38
+
39
+	if parsed.User != nil {
40
+		return fmt.Errorf("used info is not supported: %s", parsed.User.String())
41
+	}
42
+
43
+	t.Value = parsed
44
+
45
+	return nil
46
+}
47
+
48
+func (t *TypeDNSURI) Get(defaultValue *url.URL) *url.URL {
49
+	if t.Value != nil {
50
+		return t.Value
51
+	}
52
+
53
+	return defaultValue
54
+}
55
+
56
+func (t *TypeDNSURI) UnmarshalText(data []byte) error {
57
+	return t.Set(string(data))
58
+}
59
+
60
+func (t TypeDNSURI) MarshalText() ([]byte, error) {
61
+	return []byte(t.String()), nil
62
+}
63
+
64
+func (t TypeDNSURI) String() string {
65
+	if t.Value == nil {
66
+		return ""
67
+	}
68
+	return t.Value.String()
69
+}

+ 117
- 0
internal/config/type_dns_uri_test.go Bestand weergeven

@@ -0,0 +1,117 @@
1
+package config_test
2
+
3
+import (
4
+	"encoding/json"
5
+	"testing"
6
+
7
+	"github.com/9seconds/mtg/v2/internal/config"
8
+	"github.com/stretchr/testify/assert"
9
+	"github.com/stretchr/testify/suite"
10
+)
11
+
12
+type typeDNSURITestStruct struct {
13
+	Value config.TypeDNSURI `json:"value"`
14
+}
15
+
16
+type TypeDNSURITestSuite struct {
17
+	suite.Suite
18
+}
19
+
20
+func (suite *TypeDNSURITestSuite) TestUnmarshalFail() {
21
+	testData := []string{
22
+		"xx",
23
+		"ppar",
24
+		"",
25
+		"dns://hahaha",
26
+		"udp://xcxxcv",
27
+		"udp://1.1.1.1/xcv",
28
+		"1.1.1.1/xxx",
29
+		"tls://dns/xx",
30
+		"tls://1.1.1.1/xx",
31
+		"https://user:password@1.1.1.1",
32
+		"tls://user:password@1.1.1.1",
33
+		"udp://user:password@1.1.1.1",
34
+	}
35
+
36
+	for _, v := range testData {
37
+		data, err := json.Marshal(map[string]string{
38
+			"value": v,
39
+		})
40
+		suite.NoError(err)
41
+
42
+		suite.T().Run(v, func(t *testing.T) {
43
+			assert.Error(t, json.Unmarshal(data, &typeDNSURITestStruct{}))
44
+		})
45
+	}
46
+}
47
+
48
+func (suite *TypeDNSURITestSuite) TestUnmarshalOk() {
49
+	testData := []string{
50
+		"1.1.1.1",
51
+		"tls://1.1.1.1",
52
+		"tls://dns.google",
53
+		"https://1.1.1.1",
54
+		"https://1.1.1.1/dns-query",
55
+		"https://dns.google",
56
+		"https://dns.google/dns-query",
57
+		"udp://1.1.1.1",
58
+	}
59
+
60
+	for _, v := range testData {
61
+		data, err := json.Marshal(map[string]string{
62
+			"value": v,
63
+		})
64
+		suite.NoError(err)
65
+
66
+		suite.T().Run(v, func(t *testing.T) {
67
+			testStruct := &typeDNSURITestStruct{}
68
+			assert.NoError(t, json.Unmarshal(data, testStruct))
69
+			if v == "1.1.1.1" {
70
+				v = "udp://" + v
71
+			}
72
+			assert.Equal(t, v, testStruct.Value.String())
73
+		})
74
+	}
75
+}
76
+
77
+func (suite *TypeDNSURITestSuite) TestMarshalOk() {
78
+	testData := []string{
79
+		"tls://1.1.1.1",
80
+		"tls://dns.google",
81
+		"https://1.1.1.1",
82
+		"https://1.1.1.1/dns-query",
83
+	}
84
+
85
+	for _, v := range testData {
86
+		suite.T().Run(v, func(t *testing.T) {
87
+			testStruct := &typePreferIPTestStruct{
88
+				Value: config.TypePreferIP{
89
+					Value: v,
90
+				},
91
+			}
92
+
93
+			encodedJSON, err := json.Marshal(testStruct)
94
+			assert.NoError(t, err)
95
+
96
+			expectedJSON, err := json.Marshal(map[string]string{
97
+				"value": v,
98
+			})
99
+			assert.NoError(t, err)
100
+
101
+			assert.JSONEq(t, string(expectedJSON), string(encodedJSON))
102
+		})
103
+	}
104
+}
105
+
106
+func (suite *TypeDNSURITestSuite) TestGet() {
107
+	value := config.TypeDNSURI{}
108
+	suite.Nil(value.Get(nil))
109
+
110
+	suite.NoError(value.Set("tls://1.1.1.1"))
111
+	suite.NotNil(value.Get(nil))
112
+}
113
+
114
+func TestDNSURI(t *testing.T) {
115
+	t.Parallel()
116
+	suite.Run(t, &TypeDNSURITestSuite{})
117
+}

+ 8
- 4
internal/config/type_proxy_url.go Bestand weergeven

@@ -15,20 +15,24 @@ type TypeProxyURL struct {
15 15
 func (t *TypeProxyURL) Set(value string) error {
16 16
 	parsedURL, err := url.Parse(value)
17 17
 	if err != nil {
18
-		return fmt.Errorf("value is not corect URL (%s): %w", value, err)
18
+		return fmt.Errorf("value is not correct URL (%s): %w", value, err)
19 19
 	}
20 20
 
21 21
 	if parsedURL.Host == "" {
22 22
 		return fmt.Errorf("url has to have a schema: %s", value)
23 23
 	}
24 24
 
25
-	if parsedURL.Scheme != "socks5" {
25
+	switch parsedURL.Scheme {
26
+	case "socks5", "socks5h":
27
+	default:
26 28
 		return fmt.Errorf("unsupported schema: %s", parsedURL.Scheme)
27 29
 	}
28 30
 
29 31
 	if _, _, err := net.SplitHostPort(parsedURL.Host); err != nil {
30
-		parsedURL.Host = net.JoinHostPort(parsedURL.Host,
31
-			typeProxyURLDefaultSOCKS5Port)
32
+		parsedURL.Host = net.JoinHostPort(
33
+			parsedURL.Host,
34
+			typeProxyURLDefaultSOCKS5Port,
35
+		)
32 36
 	}
33 37
 
34 38
 	t.Value = parsedURL

+ 49
- 0
network/v2/base_http_test.go Bestand weergeven

@@ -0,0 +1,49 @@
1
+package network_test
2
+
3
+import (
4
+	"io"
5
+	"net/http"
6
+	"net/http/httptest"
7
+	"testing"
8
+
9
+	"github.com/9seconds/mtg/v2/network/v2"
10
+	"github.com/stretchr/testify/suite"
11
+)
12
+
13
+type BaseHTTPTestSuite struct {
14
+	suite.Suite
15
+
16
+	http   *httptest.Server
17
+	client *http.Client
18
+}
19
+
20
+func (suite *BaseHTTPTestSuite) SetupSuite() {
21
+	suite.http = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
22
+		w.WriteHeader(http.StatusOK)
23
+		w.Write([]byte(r.Header.Get("User-Agent"))) //nolint: errcheck
24
+	}))
25
+}
26
+
27
+func (suite *BaseHTTPTestSuite) SetupTest() {
28
+	suite.client = network.New(nil, "mtg/1", 0, 0, 0).MakeHTTPClient(nil)
29
+}
30
+
31
+func (suite *BaseHTTPTestSuite) TestGet() {
32
+	resp, err := suite.client.Get(suite.http.URL)
33
+	suite.NoError(err)
34
+
35
+	defer resp.Body.Close() //nolint: errcheck
36
+
37
+	data, err := io.ReadAll(resp.Body)
38
+	suite.NoError(err)
39
+	suite.Equal("mtg/1", string(data))
40
+}
41
+
42
+func (suite *BaseHTTPTestSuite) TearDownSuite() {
43
+	suite.http.Close()
44
+}
45
+
46
+func TestBaseHTTP(t *testing.T) {
47
+	t.Parallel()
48
+	suite.Run(t, &BaseHTTPTestSuite{})
49
+}

+ 83
- 0
network/v2/base_network_test.go Bestand weergeven

@@ -0,0 +1,83 @@
1
+package network_test
2
+
3
+import (
4
+	"context"
5
+	"testing"
6
+
7
+	"github.com/9seconds/mtg/v2/network/v2"
8
+	"github.com/stretchr/testify/assert"
9
+	"github.com/stretchr/testify/suite"
10
+)
11
+
12
+type BaseNetworkTestSuite struct {
13
+	EchoServerTestSuite
14
+
15
+	net network.Network
16
+}
17
+
18
+func (suite *BaseNetworkTestSuite) SetupSuite() {
19
+	suite.EchoServerTestSuite.SetupSuite()
20
+
21
+	suite.net = network.New(nil, "agent", 0, 0, 0)
22
+}
23
+
24
+func (suite *BaseNetworkTestSuite) TestDialUnknownNetwork() {
25
+	testData := []string{
26
+		"udp",
27
+		"udp4",
28
+		"udp6",
29
+		"unix",
30
+	}
31
+
32
+	for _, name := range testData {
33
+		suite.T().Run(name, func(t *testing.T) {
34
+			_, err := suite.net.Dial(name, suite.EchoServerAddr())
35
+			assert.Error(t, err)
36
+		})
37
+	}
38
+}
39
+
40
+func (suite *BaseNetworkTestSuite) TestDial() {
41
+	conn, err := suite.net.Dial("tcp4", suite.EchoServerAddr())
42
+	suite.NoError(err)
43
+
44
+	buf := []byte{1, 2, 3, 4, 5}
45
+	n, err := conn.Write(buf)
46
+	suite.Equal(5, n)
47
+	suite.NoError(err)
48
+
49
+	another := make([]byte, len(buf))
50
+	n, err = conn.Read(another)
51
+	suite.NoError(err)
52
+	suite.Equal(len(another), n)
53
+	suite.Equal(buf, another)
54
+}
55
+
56
+func (suite *BaseNetworkTestSuite) TestDialContextOk() {
57
+	conn, err := suite.net.DialContext(context.Background(), "tcp4", suite.EchoServerAddr())
58
+	suite.NoError(err)
59
+
60
+	buf := []byte{1, 2, 3, 4, 5}
61
+	n, err := conn.Write(buf)
62
+	suite.Equal(5, n)
63
+	suite.NoError(err)
64
+
65
+	another := make([]byte, len(buf))
66
+	n, err = conn.Read(another)
67
+	suite.NoError(err)
68
+	suite.Equal(len(another), n)
69
+	suite.Equal(buf, another)
70
+}
71
+
72
+func (suite *BaseNetworkTestSuite) TestDialContextClosed() {
73
+	ctx, cancel := context.WithCancel(context.Background())
74
+	cancel()
75
+
76
+	_, err := suite.net.DialContext(ctx, "tcp4", suite.EchoServerAddr())
77
+	suite.ErrorIs(err, ctx.Err())
78
+}
79
+
80
+func TestNetworkBase(t *testing.T) {
81
+	t.Parallel()
82
+	suite.Run(t, &BaseNetworkTestSuite{})
83
+}

+ 61
- 0
network/v2/dns.go Bestand weergeven

@@ -0,0 +1,61 @@
1
+package network
2
+
3
+import (
4
+	"context"
5
+	"fmt"
6
+	"net"
7
+	"net/url"
8
+	"time"
9
+
10
+	"github.com/ncruces/go-dns"
11
+)
12
+
13
+var dnsCacheOptions = []dns.CacheOption{
14
+	dns.MaxCacheEntries(dns.DefaultMaxCacheEntries),
15
+	dns.MaxCacheTTL(time.Hour),
16
+	dns.NegativeCache(false),
17
+}
18
+
19
+func GetDNS(u *url.URL) (*net.Resolver, error) {
20
+	if u == nil {
21
+		return dns.NewCachingResolver(nil, dnsCacheOptions...), nil
22
+	}
23
+
24
+	if u.Scheme == "" {
25
+		u.Scheme = "udp"
26
+	}
27
+	if u.Scheme == "udp" && u.Host == "" {
28
+		u.Host = u.Path
29
+		u.Path = ""
30
+	}
31
+
32
+	switch u.Scheme {
33
+	case "tls":
34
+		return dns.NewDoTResolver(u.Host, dns.DoTCache(dnsCacheOptions...))
35
+	case "https":
36
+		if u.Path == "" {
37
+			u.Path = "/dns-query"
38
+		}
39
+
40
+		return dns.NewDoHResolver(u.String(), dns.DoHCache(dnsCacheOptions...))
41
+	case "udp", "":
42
+	default:
43
+		return nil, fmt.Errorf("unsupported DNS %v", u)
44
+	}
45
+
46
+	port := u.Port()
47
+	if port == "" {
48
+		port = "53"
49
+	}
50
+
51
+	hostport := net.JoinHostPort(u.Hostname(), port)
52
+	dialer := &net.Dialer{}
53
+	resolver := &net.Resolver{
54
+		PreferGo: true,
55
+		Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
56
+			return dialer.DialContext(ctx, "udp", hostport)
57
+		},
58
+	}
59
+
60
+	return dns.NewCachingResolver(resolver, dnsCacheOptions...), nil
61
+}

+ 68
- 0
network/v2/dns_test.go Bestand weergeven

@@ -0,0 +1,68 @@
1
+package network_test
2
+
3
+import (
4
+	"context"
5
+	"net"
6
+	"net/url"
7
+	"testing"
8
+	"time"
9
+
10
+	"github.com/9seconds/mtg/v2/network/v2"
11
+	"github.com/stretchr/testify/require"
12
+	"github.com/stretchr/testify/suite"
13
+)
14
+
15
+type DNSTestSuite struct {
16
+	suite.Suite
17
+}
18
+
19
+func (suite *DNSTestSuite) TestDefault() {
20
+	resolver, err := network.GetDNS(nil)
21
+	suite.NoError(err)
22
+	suite.doTest(resolver)
23
+}
24
+
25
+func (suite *DNSTestSuite) TestDoH() {
26
+	for _, addr := range []string{"1.1.1.1", "cloudflare-dns.com"} {
27
+		suite.Run(addr, func() {
28
+			u, err := url.Parse("https://" + addr)
29
+			require.NoError(suite.T(), err)
30
+
31
+			resolver, err := network.GetDNS(u)
32
+			suite.NoError(err)
33
+			suite.doTest(resolver)
34
+		})
35
+	}
36
+}
37
+
38
+func (suite *DNSTestSuite) TestDoT() {
39
+	u, err := url.Parse("tls://dns.google")
40
+	require.NoError(suite.T(), err)
41
+
42
+	resolver, err := network.GetDNS(u)
43
+	suite.NoError(err)
44
+	suite.doTest(resolver)
45
+}
46
+
47
+func (suite *DNSTestSuite) TestUDP() {
48
+	u, err := url.Parse("8.8.8.8")
49
+	require.NoError(suite.T(), err)
50
+
51
+	resolver, err := network.GetDNS(u)
52
+	suite.NoError(err)
53
+	suite.doTest(resolver)
54
+}
55
+
56
+func (suite *DNSTestSuite) doTest(resolver *net.Resolver) {
57
+	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
58
+	defer cancel()
59
+
60
+	ips, err := resolver.LookupIP(ctx, "ip4", "dns.google")
61
+	suite.NoError(err)
62
+	suite.Greater(len(ips), 0)
63
+}
64
+
65
+func TestGetDNS(t *testing.T) {
66
+	t.Parallel()
67
+	suite.Run(t, &DNSTestSuite{})
68
+}

+ 106
- 0
network/v2/echo_server_test.go Bestand weergeven

@@ -0,0 +1,106 @@
1
+package network_test
2
+
3
+import (
4
+	"context"
5
+	"io"
6
+	"net"
7
+	"sync"
8
+
9
+	"github.com/stretchr/testify/require"
10
+	"github.com/stretchr/testify/suite"
11
+)
12
+
13
+type EchoServer struct {
14
+	wg        sync.WaitGroup
15
+	ctx       context.Context
16
+	ctxCancel context.CancelFunc
17
+	listener  net.Listener
18
+}
19
+
20
+func (e *EchoServer) Run() {
21
+	e.wg.Go(func() {
22
+		<-e.ctx.Done()
23
+		e.listener.Close() //nolint: errcheck
24
+	})
25
+
26
+	e.wg.Go(func() {
27
+		for {
28
+			conn, err := e.listener.Accept()
29
+			if err != nil {
30
+				return
31
+			}
32
+
33
+			e.wg.Go(func() {
34
+				<-e.ctx.Done()
35
+				conn.Close() //nolint: errcheck
36
+			})
37
+			e.wg.Go(func() {
38
+				e.process(conn)
39
+			})
40
+		}
41
+	})
42
+}
43
+
44
+func (e *EchoServer) Stop() {
45
+	e.ctxCancel()
46
+	e.wg.Wait()
47
+}
48
+
49
+func (e *EchoServer) Addr() string {
50
+	return e.listener.Addr().String()
51
+}
52
+
53
+func (e *EchoServer) process(conn io.ReadWriter) {
54
+	buf := [4096]byte{}
55
+
56
+	for {
57
+		select {
58
+		case <-e.ctx.Done():
59
+			return
60
+		default:
61
+		}
62
+
63
+		n, err := conn.Read(buf[:])
64
+		if err != nil {
65
+			return
66
+		}
67
+
68
+		select {
69
+		case <-e.ctx.Done():
70
+			return
71
+		default:
72
+		}
73
+
74
+		if _, err = conn.Write(buf[:n]); err != nil {
75
+			return
76
+		}
77
+	}
78
+}
79
+
80
+type EchoServerTestSuite struct {
81
+	suite.Suite
82
+
83
+	echoServer *EchoServer
84
+}
85
+
86
+func (suite *EchoServerTestSuite) SetupSuite() {
87
+	ctx, cancel := context.WithCancel(context.Background())
88
+
89
+	listener, err := net.Listen("tcp", "127.0.0.1:0")
90
+	require.NoError(suite.T(), err)
91
+
92
+	suite.echoServer = &EchoServer{
93
+		ctx:       ctx,
94
+		ctxCancel: cancel,
95
+		listener:  listener,
96
+	}
97
+	suite.echoServer.Run()
98
+}
99
+
100
+func (suite *EchoServerTestSuite) TearDownSuite() {
101
+	suite.echoServer.Stop()
102
+}
103
+
104
+func (suite *EchoServerTestSuite) EchoServerAddr() string {
105
+	return suite.echoServer.Addr()
106
+}

+ 14
- 0
network/v2/http.go Bestand weergeven

@@ -0,0 +1,14 @@
1
+package network
2
+
3
+import "net/http"
4
+
5
+type networkHTTPTransport struct {
6
+	userAgent string
7
+	next      http.RoundTripper
8
+}
9
+
10
+func (n networkHTTPTransport) RoundTrip(req *http.Request) (*http.Response, error) {
11
+	req.Header.Set("User-Agent", n.userAgent)
12
+
13
+	return n.next.RoundTrip(req) //nolint: wrapcheck
14
+}

+ 45
- 0
network/v2/init.go Bestand weergeven

@@ -0,0 +1,45 @@
1
+// Network contains a default implementation of the network.
2
+//
3
+// Please see [mtglib.Network] interface to get some basic idea behind this
4
+// abstraction.
5
+//
6
+// This implementation is more simple that v1 because life shows that all
7
+// this complexity, especially around circuit breakers and DoH is not really
8
+// required. There is no chance that if DNS address is spoofed, that real
9
+// IP would work as expected.
10
+package network
11
+
12
+import (
13
+	"errors"
14
+	"net"
15
+	"time"
16
+
17
+	"github.com/9seconds/mtg/v2/mtglib"
18
+)
19
+
20
+const (
21
+	// DefaultTimeout is a default timeout for establishing TCP connection.
22
+	DefaultTimeout = 10 * time.Second
23
+
24
+	// DefaultHTTPTimeout defines a default timeout for making HTTP request.
25
+	DefaultHTTPTimeout = 10 * time.Second
26
+
27
+	// DefaultIdleTimeout defines a timeout for idle HTTP connections
28
+	DefaultIdleTimeout = time.Minute
29
+
30
+	// DefaultTCPKeepAlivePeriod defines a time period between 2 consecuitive
31
+	// probes.
32
+	DefaultTCPKeepAlivePeriod = 10 * time.Second
33
+
34
+	// tcpLingerTimeout defines a number of seconds to wait for sending
35
+	// unacknowledged data.
36
+	tcpLingerTimeout = 1
37
+)
38
+
39
+var ErrCannotDial = errors.New("cannot dial to any address")
40
+
41
+type Network interface {
42
+	mtglib.Network
43
+
44
+	NativeDialer() *net.Dialer
45
+}

+ 70
- 0
network/v2/multi_network.go Bestand weergeven

@@ -0,0 +1,70 @@
1
+package network
2
+
3
+import (
4
+	"context"
5
+	"errors"
6
+	"math/rand"
7
+	"net"
8
+	"net/http"
9
+
10
+	"github.com/9seconds/mtg/v2/essentials"
11
+)
12
+
13
+type multiNetwork struct {
14
+	networks []Network
15
+}
16
+
17
+func (m multiNetwork) Dial(network, address string) (essentials.Conn, error) {
18
+	return m.DialContext(context.Background(), network, address)
19
+}
20
+
21
+func (m multiNetwork) DialContext(ctx context.Context, network, address string) (essentials.Conn, error) {
22
+	networks := m.networks
23
+
24
+	if len(networks) > 1 {
25
+		networks = make([]Network, len(m.networks))
26
+		copy(networks, m.networks)
27
+
28
+		rand.Shuffle(len(m.networks), func(i, j int) {
29
+			networks[i], networks[j] = networks[j], networks[i]
30
+		})
31
+	}
32
+
33
+	errs := make([]error, 1, len(networks)+1)
34
+	errs[0] = ErrCannotDial
35
+
36
+	for _, ntw := range networks {
37
+		conn, err := ntw.DialContext(ctx, network, address)
38
+		if err == nil {
39
+			return conn, nil
40
+		}
41
+
42
+		errs = append(errs, err)
43
+	}
44
+
45
+	return nil, errors.Join(errs...)
46
+}
47
+
48
+func (m multiNetwork) NativeDialer() *net.Dialer {
49
+	return m.networks[0].NativeDialer()
50
+}
51
+
52
+func (m multiNetwork) MakeHTTPClient(
53
+	dialFunc func(context.Context, string, string) (essentials.Conn, error),
54
+) *http.Client {
55
+	if dialFunc == nil {
56
+		dialFunc = m.DialContext
57
+	}
58
+
59
+	return m.networks[0].MakeHTTPClient(dialFunc)
60
+}
61
+
62
+func Join(networks ...Network) (Network, error) {
63
+	if len(networks) == 0 {
64
+		return nil, errors.New("cannot join no networks")
65
+	}
66
+
67
+	return multiNetwork{
68
+		networks: networks,
69
+	}, nil
70
+}

+ 88
- 0
network/v2/network.go Bestand weergeven

@@ -0,0 +1,88 @@
1
+package network
2
+
3
+import (
4
+	"context"
5
+	"fmt"
6
+	"net"
7
+	"net/http"
8
+	"time"
9
+
10
+	"github.com/9seconds/mtg/v2/essentials"
11
+)
12
+
13
+type network struct {
14
+	net.Dialer
15
+
16
+	httpTimeout time.Duration
17
+	idleTimeout time.Duration
18
+	userAgent   string
19
+}
20
+
21
+func (n *network) Dial(network, address string) (essentials.Conn, error) {
22
+	return n.DialContext(context.Background(), network, address)
23
+}
24
+
25
+func (n *network) DialContext(ctx context.Context, network, address string) (essentials.Conn, error) {
26
+	switch network {
27
+	case "tcp", "tcp4", "tcp6":
28
+	default:
29
+		return nil, fmt.Errorf("unsupported network %s", network)
30
+	}
31
+
32
+	conn, err := n.Dialer.DialContext(ctx, network, address)
33
+	if err != nil {
34
+		return nil, err
35
+	}
36
+
37
+	tcpConn := conn.(*net.TCPConn)
38
+
39
+	return tcpConn, setCommonSocketOptions(tcpConn)
40
+}
41
+
42
+func (n *network) MakeHTTPClient(
43
+	dialFunc func(context.Context, string, string) (essentials.Conn, error),
44
+) *http.Client {
45
+	if dialFunc == nil {
46
+		dialFunc = n.DialContext
47
+	}
48
+
49
+	return &http.Client{
50
+		Timeout: n.httpTimeout,
51
+		Transport: networkHTTPTransport{
52
+			userAgent: n.userAgent,
53
+			next: &http.Transport{
54
+				IdleConnTimeout: n.idleTimeout,
55
+				DialContext: func(ctx context.Context, network, address string) (net.Conn, error) {
56
+					return dialFunc(ctx, network, address)
57
+				},
58
+			},
59
+		},
60
+	}
61
+}
62
+
63
+func (n *network) NativeDialer() *net.Dialer {
64
+	return &n.Dialer
65
+}
66
+
67
+func New(
68
+	dnsResolver *net.Resolver,
69
+	userAgent string,
70
+	tcpTimeout,
71
+	httpTimeout,
72
+	idleTimeout time.Duration,
73
+) Network {
74
+	if dnsResolver == nil {
75
+		dnsResolver = net.DefaultResolver
76
+	}
77
+
78
+	return &network{
79
+		Dialer: net.Dialer{
80
+			Timeout:       tcpTimeout,
81
+			Resolver:      dnsResolver,
82
+			FallbackDelay: -1,
83
+		},
84
+		userAgent:   userAgent,
85
+		idleTimeout: idleTimeout,
86
+		httpTimeout: httpTimeout,
87
+	}
88
+}

+ 36
- 0
network/v2/proxy_network.go Bestand weergeven

@@ -0,0 +1,36 @@
1
+package network
2
+
3
+import (
4
+	"context"
5
+	"fmt"
6
+	"net/url"
7
+
8
+	"github.com/9seconds/mtg/v2/essentials"
9
+	"golang.org/x/net/proxy"
10
+)
11
+
12
+type proxyNetwork struct {
13
+	Network
14
+	client proxy.ContextDialer
15
+}
16
+
17
+func (p proxyNetwork) DialContext(ctx context.Context, network, address string) (essentials.Conn, error) {
18
+	conn, err := p.client.DialContext(ctx, network, address)
19
+	if err != nil {
20
+		return nil, err
21
+	}
22
+
23
+	return essentials.WrapNetConn(conn), nil
24
+}
25
+
26
+func NewProxyNetwork(base Network, proxyURL *url.URL) (*proxyNetwork, error) {
27
+	socks, err := proxy.FromURL(proxyURL, base.NativeDialer())
28
+	if err != nil {
29
+		return nil, fmt.Errorf("cannot build proxy dialer: %w", err)
30
+	}
31
+
32
+	return &proxyNetwork{
33
+		Network: base,
34
+		client:  socks.(proxy.ContextDialer),
35
+	}, nil
36
+}

+ 27
- 0
network/v2/sockopts.go Bestand weergeven

@@ -0,0 +1,27 @@
1
+package network
2
+
3
+import (
4
+	"fmt"
5
+	"net"
6
+)
7
+
8
+func setCommonSocketOptions(conn *net.TCPConn) error {
9
+	if err := conn.SetKeepAlivePeriod(DefaultTCPKeepAlivePeriod); err != nil {
10
+		return fmt.Errorf("cannot set time period of TCP keepalive probes: %w", err)
11
+	}
12
+
13
+	if err := conn.SetLinger(tcpLingerTimeout); err != nil {
14
+		return fmt.Errorf("cannot set TCP linger timeout: %w", err)
15
+	}
16
+
17
+	rawConn, err := conn.SyscallConn()
18
+	if err != nil {
19
+		return fmt.Errorf("cannot get underlying raw connection: %w", err)
20
+	}
21
+
22
+	if err := setSocketReuseAddrPort(rawConn); err != nil {
23
+		return fmt.Errorf("cannot setup SO_REUSEADDR/PORT: %w", err)
24
+	}
25
+
26
+	return nil
27
+}

+ 31
- 0
network/v2/sockopts_unix.go Bestand weergeven

@@ -0,0 +1,31 @@
1
+//go:build !windows
2
+// +build !windows
3
+
4
+package network
5
+
6
+import (
7
+	"fmt"
8
+	"syscall"
9
+
10
+	"golang.org/x/sys/unix"
11
+)
12
+
13
+func setSocketReuseAddrPort(conn syscall.RawConn) error {
14
+	var err error
15
+
16
+	conn.Control(func(fd uintptr) { //nolint: errcheck
17
+		err = unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_REUSEADDR, 1)
18
+		if err != nil {
19
+			err = fmt.Errorf("cannot set SO_REUSEADDR: %w", err)
20
+
21
+			return
22
+		}
23
+
24
+		err = unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_REUSEPORT, 1)
25
+		if err != nil {
26
+			err = fmt.Errorf("cannot set SO_REUSEPORT: %w", err)
27
+		}
28
+	})
29
+
30
+	return err
31
+}

+ 10
- 0
network/v2/sockopts_windows.go Bestand weergeven

@@ -0,0 +1,10 @@
1
+//go:build windows
2
+// +build windows
3
+
4
+package network
5
+
6
+import "syscall"
7
+
8
+func setSocketReuseAddrPort(conn syscall.RawConn) error {
9
+	return nil
10
+}

+ 127
- 0
network/v2/socks_proxy_test.go Bestand weergeven

@@ -0,0 +1,127 @@
1
+package network_test
2
+
3
+import (
4
+	"net"
5
+	"net/url"
6
+	"sync"
7
+	"testing"
8
+
9
+	"github.com/9seconds/mtg/v2/network/v2"
10
+	"github.com/stretchr/testify/assert"
11
+	"github.com/stretchr/testify/require"
12
+	"github.com/stretchr/testify/suite"
13
+	"github.com/things-go/go-socks5"
14
+)
15
+
16
+type SocksProxyTestSuite struct {
17
+	EchoServerTestSuite
18
+
19
+	wg          sync.WaitGroup
20
+	baseNetwork network.Network
21
+
22
+	noAuthURL *url.URL
23
+	authURL   *url.URL
24
+
25
+	noAuthListener net.Listener
26
+	authListener   net.Listener
27
+
28
+	noAuthServer *socks5.Server
29
+	authServer   *socks5.Server
30
+}
31
+
32
+func (suite *SocksProxyTestSuite) SetupSuite() {
33
+	suite.EchoServerTestSuite.SetupSuite()
34
+
35
+	listener, err := net.Listen("tcp4", "127.0.0.1:0")
36
+	require.NoError(suite.T(), err)
37
+	suite.noAuthListener = listener
38
+
39
+	listener, err = net.Listen("tcp4", "127.0.0.1:0")
40
+	require.NoError(suite.T(), err)
41
+	suite.authListener = listener
42
+
43
+	suite.noAuthServer = socks5.NewServer()
44
+	suite.wg.Go(func() {
45
+		suite.noAuthServer.Serve(suite.noAuthListener) //nolint: errcheck
46
+	})
47
+
48
+	suite.authServer = socks5.NewServer(
49
+		socks5.WithAuthMethods([]socks5.Authenticator{
50
+			socks5.UserPassAuthenticator{
51
+				Credentials: socks5.StaticCredentials{
52
+					"user": "pass",
53
+				},
54
+			},
55
+		}))
56
+	suite.wg.Go(func() {
57
+		suite.authServer.Serve(suite.authListener) //nolint: errcheck
58
+	})
59
+
60
+	parsed, err := url.Parse("socks5://" + suite.noAuthListener.Addr().String())
61
+	require.NoError(suite.T(), err)
62
+	suite.noAuthURL = parsed
63
+
64
+	parsed, err = url.Parse("socks5://user:pass@" + suite.authListener.Addr().String())
65
+	require.NoError(suite.T(), err)
66
+	suite.authURL = parsed
67
+
68
+	suite.baseNetwork = network.New(nil, "mtg", 0, 0, 0)
69
+}
70
+
71
+func (suite *SocksProxyTestSuite) TestIncorrectSchema() {
72
+	parsed, err := url.Parse("http://hello")
73
+	suite.NoError(err)
74
+
75
+	_, err = network.NewProxyNetwork(suite.baseNetwork, parsed)
76
+	suite.Error(err)
77
+}
78
+
79
+func (suite *SocksProxyTestSuite) TestRead() {
80
+	testData := map[string][]*url.URL{
81
+		"noAuth": {suite.noAuthURL},
82
+		"auth":   {suite.authURL},
83
+		"both":   {suite.noAuthURL, suite.authURL},
84
+	}
85
+
86
+	for name, proxies := range testData {
87
+		suite.T().Run(name, func(t *testing.T) {
88
+			proxyNetworks := []network.Network{}
89
+
90
+			for _, u := range proxies {
91
+				value, err := network.NewProxyNetwork(suite.baseNetwork, u)
92
+				assert.NoError(t, err)
93
+				proxyNetworks = append(proxyNetworks, value)
94
+			}
95
+
96
+			netw, err := network.Join(proxyNetworks...)
97
+			assert.NoError(t, err)
98
+
99
+			conn, err := netw.Dial("tcp4", suite.EchoServerAddr())
100
+			assert.NoError(t, err)
101
+
102
+			data := []byte{1, 2, 3}
103
+			n, err := conn.Write(data)
104
+			assert.NoError(t, err)
105
+			assert.Equal(t, len(data), n)
106
+
107
+			toRead := []byte{1, 2, 3, 4, 5}
108
+			n, err = conn.Read(toRead)
109
+			assert.NoError(t, err)
110
+			assert.Equal(t, len(data), n)
111
+			assert.Equal(t, data, toRead[:n])
112
+			assert.NotEqual(t, data, toRead)
113
+		})
114
+	}
115
+}
116
+
117
+func (suite *SocksProxyTestSuite) TearDownSuite() {
118
+	suite.noAuthListener.Close() //nolint: errcheck
119
+	suite.authListener.Close()   //nolint: errcheck
120
+	suite.wg.Wait()
121
+	suite.EchoServerTestSuite.TearDownSuite()
122
+}
123
+
124
+func TestSocksProxy(t *testing.T) {
125
+	t.Parallel()
126
+	suite.Run(t, &SocksProxyTestSuite{})
127
+}

Laden…
Annuleren
Opslaan