浏览代码

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

tags/v2.1.11^2
9seconds 2 个月前
父节点
当前提交
e6fa5906c9
共有 52 个文件被更改,包括 1473 次插入1032 次删除
  1. 38
    0
      .github/workflows/govulncheck.yml
  2. 10
    13
      .mise.toml
  3. 42
    16
      example.config.toml
  4. 1
    1
      go.mod
  5. 5
    12
      internal/cli/run_proxy.go
  6. 7
    0
      internal/cli/simple_run.go
  7. 39
    14
      internal/config/config.go
  8. 17
    14
      internal/config/parse.go
  9. 4
    0
      mise.lock
  10. 38
    10
      mtglib/conns.go
  11. 96
    0
      mtglib/conns_internal_test.go
  12. 10
    3
      mtglib/internal/dc/addr.go
  13. 0
    16
      mtglib/internal/dc/addr_test.go
  14. 54
    49
      mtglib/internal/dc/init.go
  15. 43
    0
      mtglib/internal/dc/init_test.go
  16. 93
    0
      mtglib/internal/dc/public_config_updater.go
  17. 113
    0
      mtglib/internal/dc/public_config_updater_test.go
  18. 6
    35
      mtglib/internal/dc/telegram.go
  19. 47
    0
      mtglib/internal/dc/updater.go
  20. 55
    0
      mtglib/internal/dc/updater_test.go
  21. 3
    5
      mtglib/internal/dc/view.go
  22. 7
    9
      mtglib/internal/dc/view_test.go
  23. 9
    10
      mtglib/internal/faketls/conn.go
  24. 0
    21
      mtglib/internal/faketls/pools.go
  25. 3
    4
      mtglib/internal/faketls/welcome.go
  26. 0
    54
      mtglib/internal/obfuscated2/client_handshake.go
  27. 0
    32
      mtglib/internal/obfuscated2/client_handshake_fuzz_internal_test.go
  28. 0
    89
      mtglib/internal/obfuscated2/client_handshake_test.go
  29. 0
    37
      mtglib/internal/obfuscated2/conn.go
  30. 0
    71
      mtglib/internal/obfuscated2/handshake_frame.go
  31. 0
    73
      mtglib/internal/obfuscated2/handshake_frame_internal_test.go
  32. 0
    137
      mtglib/internal/obfuscated2/init_test.go
  33. 0
    39
      mtglib/internal/obfuscated2/pools.go
  34. 0
    67
      mtglib/internal/obfuscated2/server_handshake.go
  35. 0
    58
      mtglib/internal/obfuscated2/server_handshake_fuzz_test.go
  36. 0
    65
      mtglib/internal/obfuscated2/server_handshake_test.go
  37. 0
    15
      mtglib/internal/obfuscated2/utils.go
  38. 34
    0
      mtglib/internal/obfuscation/conn.go
  39. 102
    0
      mtglib/internal/obfuscation/conn_test.go
  40. 111
    0
      mtglib/internal/obfuscation/handshake_frame.go
  41. 15
    5
      mtglib/internal/obfuscation/handshake_frame_fuzz_test.go
  42. 66
    0
      mtglib/internal/obfuscation/handshake_frame_test.go
  43. 79
    0
      mtglib/internal/obfuscation/init_test.go
  44. 87
    0
      mtglib/internal/obfuscation/obfuscator.go
  45. 63
    0
      mtglib/internal/obfuscation/obfuscator_fuzz_test.go
  46. 94
    0
      mtglib/internal/obfuscation/obfuscator_test.go
  47. 0
    0
      mtglib/internal/obfuscation/testdata/client-handshake-snapshot-4529d55776e2d427.json
  48. 0
    0
      mtglib/internal/obfuscation/testdata/client-handshake-snapshot-585c944d672f60a2.json
  49. 0
    19
      mtglib/internal/relay/pools.go
  50. 3
    4
      mtglib/internal/relay/relay.go
  51. 61
    34
      mtglib/proxy.go
  52. 18
    1
      mtglib/proxy_opts.go

+ 38
- 0
.github/workflows/govulncheck.yml 查看文件

@@ -0,0 +1,38 @@
1
+---
2
+
3
+name: Vulnerability checks
4
+
5
+permissions:
6
+  actions: read
7
+  checks: read
8
+  contents: read
9
+  deployments: read
10
+  issues: read
11
+  discussions: read
12
+  pull-requests: read
13
+  repository-projects: read
14
+  security-events: read
15
+  statuses: read
16
+
17
+on:
18
+  push:
19
+  pull_request:
20
+  schedule: # daily at 10:22 UTC
21
+    - cron: '22 10 * * *'
22
+  workflow_dispatch:
23
+
24
+jobs:
25
+  vuln:
26
+    name: Test vulnerabilities
27
+    runs-on: ubuntu-latest
28
+    steps:
29
+    - name: Checkout
30
+      uses: actions/checkout@v6
31
+      with:
32
+        submodules: recursive
33
+
34
+    - uses: jdx/mise-action@v3
35
+      name: Install mise
36
+
37
+    - name: Run tests
38
+      run: mise tasks run vuln

+ 10
- 13
.mise.toml 查看文件

@@ -1,6 +1,7 @@
1 1
 [tools]
2 2
 "go:golang.org/x/pkgsite/cmd/pkgsite" = "latest"
3 3
 "go:golang.org/x/tools/gopls" = "latest"
4
+"go:golang.org/x/vuln/cmd/govulncheck" = "latest"
4 5
 "go:mvdan.cc/gofumpt" = "latest"
5 6
 go = "latest"
6 7
 golangci-lint = "latest"
@@ -19,13 +20,17 @@ run = "go build"
19 20
 description = "Update dependencies"
20 21
 run = [
21 22
     "go get -u",
22
-    "go mod tidy -go=1.25"
23
+    "go mod tidy -go=1.26"
23 24
 ]
24 25
 
25 26
 [tasks.lint]
26 27
 description = "Run linter"
27 28
 run = "golangci-lint run"
28 29
 
30
+[tasks.vuln]
31
+description = "Test for vulnerabilities"
32
+run = "govulncheck ./..."
33
+
29 34
 [tasks.test]
30 35
 description = "Run tests"
31 36
 run = "go test -v ./..."
@@ -47,19 +52,11 @@ run = "go test -v {{ vars.fuzzflags }} -fuzz=FuzzClientHello ./mtglib/internal/f
47 52
 
48 53
 [tasks."test:fuzz:client-handshake"]
49 54
 description = "Run fuzzy test for ClientHandshake"
50
-run = "go test -v {{ vars.fuzzflags }} -fuzz=FuzzClientHandshake ./mtglib/internal/obfuscated2"
51
-
52
-[tasks."test:fuzz:server-generate-handshake-frame"]
53
-description = "Run fuzzy test for ServerGenerateHandshakeFrame"
54
-run = "go test -v {{ vars.fuzzflags }} -fuzz=FuzzServerGenerateHandshakeFrame ./mtglib/internal/obfuscated2"
55
-
56
-[tasks."test:fuzz:server-receive"]
57
-description = "Run fuzzy test for ServerReceive"
58
-run = "go test -v {{ vars.fuzzflags }} -fuzz=FuzzServerReceive ./mtglib/internal/obfuscated2"
55
+run = "go test -v {{ vars.fuzzflags }} -fuzz=FuzzClientServerHandshake ./mtglib/internal/obfuscation"
59 56
 
60
-[tasks."test:fuzz:server-send"]
61
-description = "Run fuzzy test for ServerSend"
62
-run = "go test -v {{ vars.fuzzflags }} -fuzz=FuzzServerSend ./mtglib/internal/obfuscated2"
57
+[tasks."test:fuzz:server-handshake-frame"]
58
+description = "Run fuzzy test for GenerateHandshakeFrame"
59
+run = "go test -v {{ vars.fuzzflags }} -fuzz=FuzzGenerateHandshakeFrame ./mtglib/internal/obfuscation"
63 60
 
64 61
 [tasks.static]
65 62
 description = "Build static binary"

+ 42
- 16
example.config.toml 查看文件

@@ -36,13 +36,6 @@ bind-to = "0.0.0.0:3128"
36 36
 # All other incoming connections are going to be dropped.
37 37
 concurrency = 8192
38 38
 
39
-# A size of user-space buffer for TCP to use. Since we do 2 connections,
40
-# then we have tcp-buffer * (4 + 2) per each connection: read/write for
41
-# each connection + 2 copy buffers to pump the data between sockets.
42
-#
43
-# Deprecated: this setting is no longer makes any effect.
44
-# tcp-buffer = "4kb"
45
-
46 39
 # Sometimes you want to enforce mtg to use some types of
47 40
 # IP connectivity to Telegram. We have 4 modes:
48 41
 #   - prefer-ipv6:
@@ -57,7 +50,28 @@ prefer-ip = "prefer-ipv6"
57 50
 
58 51
 # FakeTLS uses domain fronting protection. So it needs to know a port to
59 52
 # access.
60
-domain-fronting-port = 443
53
+#
54
+# Deprecated: use [domain-fronting] configuration block. If relevant option
55
+# is defined there, this one would be ignored.
56
+# domain-fronting-port = 443
57
+
58
+# By default, mtg resolves the fronting hostname (from the secret) via DNS
59
+# to establish a TCP connection. If DNS resolution of that hostname is blocked,
60
+# you can specify an IP address to connect to directly. The hostname is still
61
+# used for SNI in the TLS handshake.
62
+#
63
+# default value is not set (DNS resolution is used).
64
+#
65
+# Deprecated: use [domain-fronting] configuration block. If relevant option
66
+# is defined there, this one would be ignored.
67
+# domain-fronting-ip = "10.0.0.10"
68
+
69
+# This makes a communication between both fronting website and mtg to use
70
+# proxy protocol.
71
+#
72
+# Deprecated: use [domain-fronting] configuration block. If relevant option
73
+# is defined there, this one would be ignored.
74
+# domain-fronting-proxy-protocol = false
61 75
 
62 76
 # FakeTLS can compare timestamps to prevent probes. Each message has
63 77
 # encrypted timestamp. So, mtg can compare this timestamp and decide if
@@ -80,14 +94,26 @@ tolerate-time-skewness = "5s"
80 94
 # Otherwise, chose a new DC.
81 95
 allow-fallback-on-unknown-dc = false
82 96
 
83
-# Telegram uses different DCs for different purposes. Unfortunately, most of
84
-# DCs are not public, and dependent on a location of the current user, so
85
-# mtg cannot know upfront about all of them, and how to access them. It has
86
-# a default list of DCs, including some CDN IPs, but it is possible that some
87
-# of them are not working for you. In this case, you can override them here.
88
-[[dc-overrides]]
89
-dc = 101
90
-ips = ["127.0.0.1:443"]
97
+# This section is relevant to communication with fronting domain. Usually
98
+# you do not need to setup anything here but there are plenty of cases, especially
99
+# if you put mtg behind load balancer, when some specific configuration is
100
+# required.
101
+[domain-fronting]
102
+# By default, mtg resolves the fronting hostname (from the secret) via DNS
103
+# to establish a TCP connection. If DNS resolution of that hostname is blocked,
104
+# you can specify an IP address to connect to directly. The hostname is still
105
+# used for SNI in the TLS handshake.
106
+#
107
+# default value is not set (DNS resolution is used).
108
+# ip = "10.10.10.11"
109
+
110
+# FakeTLS uses domain fronting protection. So it needs to know a port to
111
+# access. Default value is 443
112
+# port = 443
113
+
114
+# This makes a communication between both fronting website and mtg to use
115
+# proxy protocol.
116
+# proxy-protocol = false
91 117
 
92 118
 # network defines different network-related settings
93 119
 [network]

+ 1
- 1
go.mod 查看文件

@@ -1,6 +1,6 @@
1 1
 module github.com/9seconds/mtg/v2
2 2
 
3
-go 1.25
3
+go 1.26
4 4
 
5 5
 require (
6 6
 	github.com/OneOfOne/xxhash v1.2.8

+ 5
- 12
internal/cli/run_proxy.go 查看文件

@@ -242,14 +242,6 @@ func runProxy(conf *config.Config, version string) error { //nolint: funlen
242 242
 		return fmt.Errorf("cannot build ip allowlist: %w", err)
243 243
 	}
244 244
 
245
-	dcOverrides := map[int][]string{}
246
-	for _, override := range conf.DCOverrides {
247
-		dcid := override.DC.Get()
248
-		for _, addr := range override.IPs {
249
-			dcOverrides[dcid] = append(dcOverrides[dcid], addr.Get(""))
250
-		}
251
-	}
252
-
253 245
 	opts := mtglib.ProxyOpts{
254 246
 		Logger:          logger,
255 247
 		Network:         ntw,
@@ -258,13 +250,14 @@ func runProxy(conf *config.Config, version string) error { //nolint: funlen
258 250
 		IPAllowlist:     allowlist,
259 251
 		EventStream:     eventStream,
260 252
 
261
-		Secret:             conf.Secret,
262
-		DomainFrontingPort: conf.DomainFrontingPort.Get(mtglib.DefaultDomainFrontingPort),
263
-		PreferIP:           conf.PreferIP.Get(mtglib.DefaultPreferIP),
253
+		Secret:                      conf.Secret,
254
+		DomainFrontingPort:          conf.GetDomainFrontingPort(mtglib.DefaultDomainFrontingPort),
255
+		DomainFrontingIP:            conf.GetDomainFrontingIP(nil),
256
+		DomainFrontingProxyProtocol: conf.GetDomainFrontingProxyProtocol(false),
257
+		PreferIP:                    conf.PreferIP.Get(mtglib.DefaultPreferIP),
264 258
 
265 259
 		AllowFallbackOnUnknownDC: conf.AllowFallbackOnUnknownDC.Get(false),
266 260
 		TolerateTimeSkewness:     conf.TolerateTimeSkewness.Value,
267
-		DCOverrides:              dcOverrides,
268 261
 	}
269 262
 
270 263
 	proxy, err := mtglib.NewProxy(opts)

+ 7
- 0
internal/cli/simple_run.go 查看文件

@@ -18,6 +18,7 @@ type SimpleRun struct {
18 18
 	TCPBuffer           string        `kong:"name='tcp-buffer',short='b',default='4KB',help='Deprecated and ignored'"`                                                 //nolint: lll
19 19
 	PreferIP            string        `kong:"name='prefer-ip',short='i',default='prefer-ipv6',help='IP preference. By default we prefer IPv6 with fallback to IPv4.'"` //nolint: lll
20 20
 	DomainFrontingPort  uint64        `kong:"name='domain-fronting-port',short='p',default='443',help='A port to access for domain fronting.'"`                        //nolint: lll
21
+	DomainFrontingIP    string        `kong:"name='domain-fronting-ip',help='An IP address to use for domain fronting instead of resolving the hostname via DNS.'"`    //nolint: lll
21 22
 	DOHIP               net.IP        `kong:"name='doh-ip',short='n',default='1.1.1.1',help='IP address of DNS-over-HTTP to use.'"`                                    //nolint: lll
22 23
 	Timeout             time.Duration `kong:"name='timeout',short='t',default='10s',help='Network timeout to use'"`                                                    //nolint: lll
23 24
 	Socks5Proxies       []string      `kong:"name='socks5-proxy',short='s',help='Socks5 proxies to use for network access.'"`                                          //nolint: lll
@@ -47,6 +48,12 @@ func (s *SimpleRun) Run(cli *CLI, version string) error { //nolint: cyclop,funle
47 48
 		return fmt.Errorf("incorrect domain-fronting-port: %w", err)
48 49
 	}
49 50
 
51
+	if s.DomainFrontingIP != "" {
52
+		if err := conf.DomainFrontingIP.Set(s.DomainFrontingIP); err != nil {
53
+			return fmt.Errorf("incorrect domain-fronting-ip: %w", err)
54
+		}
55
+	}
56
+
50 57
 	if err := conf.Network.DOHIP.Set(s.DOHIP.String()); err != nil {
51 58
 		return fmt.Errorf("incorrect doh-ip: %w", err)
52 59
 	}

+ 39
- 14
internal/config/config.go 查看文件

@@ -4,6 +4,7 @@ import (
4 4
 	"bytes"
5 5
 	"encoding/json"
6 6
 	"fmt"
7
+	"net"
7 8
 
8 9
 	"github.com/9seconds/mtg/v2/mtglib"
9 10
 )
@@ -21,16 +22,23 @@ type ListConfig struct {
21 22
 }
22 23
 
23 24
 type Config struct {
24
-	Debug                    TypeBool        `json:"debug"`
25
-	AllowFallbackOnUnknownDC TypeBool        `json:"allowFallbackOnUnknownDc"`
26
-	Secret                   mtglib.Secret   `json:"secret"`
27
-	BindTo                   TypeHostPort    `json:"bindTo"`
28
-	ProxyProtocolListener    TypeBool        `json:"proxyProtocolListener"`
29
-	PreferIP                 TypePreferIP    `json:"preferIp"`
30
-	DomainFrontingPort       TypePort        `json:"domainFrontingPort"`
31
-	TolerateTimeSkewness     TypeDuration    `json:"tolerateTimeSkewness"`
32
-	Concurrency              TypeConcurrency `json:"concurrency"`
33
-	Defense                  struct {
25
+	Debug                       TypeBool        `json:"debug"`
26
+	AllowFallbackOnUnknownDC    TypeBool        `json:"allowFallbackOnUnknownDc"`
27
+	Secret                      mtglib.Secret   `json:"secret"`
28
+	BindTo                      TypeHostPort    `json:"bindTo"`
29
+	ProxyProtocolListener       TypeBool        `json:"proxyProtocolListener"`
30
+	PreferIP                    TypePreferIP    `json:"preferIp"`
31
+	DomainFrontingPort          TypePort        `json:"domainFrontingPort"`
32
+	DomainFrontingIP            TypeIP          `json:"domainFrontingIp"`
33
+	DomainFrontingProxyProtocol TypeBool        `json:"domainFrontingProxyProtocol"`
34
+	TolerateTimeSkewness        TypeDuration    `json:"tolerateTimeSkewness"`
35
+	Concurrency                 TypeConcurrency `json:"concurrency"`
36
+	DomainFronting              struct {
37
+		IP            TypeIP   `json:"ip"`
38
+		Port          TypePort `json:"port"`
39
+		ProxyProtocol TypeBool `json:"proxyProtocol"`
40
+	} `json:"domainFronting"`
41
+	Defense struct {
34 42
 		AntiReplay struct {
35 43
 			Optional
36 44
 
@@ -65,10 +73,27 @@ type Config struct {
65 73
 			MetricPrefix TypeMetricPrefix `json:"metricPrefix"`
66 74
 		} `json:"prometheus"`
67 75
 	} `json:"stats"`
68
-	DCOverrides []struct {
69
-		DC  TypeDC         `json:"dc"`
70
-		IPs []TypeHostPort `json:"ips"`
71
-	} `json:"dcOverrides"`
76
+}
77
+
78
+func (c *Config) GetDomainFrontingPort(defaultValue uint) uint {
79
+	if port := c.DomainFronting.Port.Get(0); port != 0 {
80
+		return port
81
+	}
82
+	return c.DomainFrontingPort.Get(defaultValue)
83
+}
84
+
85
+func (c *Config) GetDomainFrontingIP(defaultValue net.IP) string {
86
+	if ip := c.DomainFronting.IP.Get(nil); ip != nil {
87
+		return ip.String()
88
+	}
89
+	if ip := c.DomainFrontingIP.Get(defaultValue); ip != nil {
90
+		return ip.String()
91
+	}
92
+	return ""
93
+}
94
+
95
+func (c *Config) GetDomainFrontingProxyProtocol(defaultValue bool) bool {
96
+	return c.DomainFronting.ProxyProtocol.Get(false) || c.DomainFrontingProxyProtocol.Get(defaultValue)
72 97
 }
73 98
 
74 99
 func (c *Config) Validate() error {

+ 17
- 14
internal/config/parse.go 查看文件

@@ -9,16 +9,23 @@ import (
9 9
 )
10 10
 
11 11
 type tomlConfig struct {
12
-	Debug                    bool   `toml:"debug" json:"debug,omitempty"`
13
-	AllowFallbackOnUnknownDC bool   `toml:"allow-fallback-on-unknown-dc" json:"allowFallbackOnUnknownDc,omitempty"`
14
-	Secret                   string `toml:"secret" json:"secret"`
15
-	BindTo                   string `toml:"bind-to" json:"bindTo"`
16
-	ProxyProtocolListener    bool   `toml:"proxy-protocol-listener" json:"proxyProtocolListener"`
17
-	PreferIP                 string `toml:"prefer-ip" json:"preferIp,omitempty"`
18
-	DomainFrontingPort       uint   `toml:"domain-fronting-port" json:"domainFrontingPort,omitempty"`
19
-	TolerateTimeSkewness     string `toml:"tolerate-time-skewness" json:"tolerateTimeSkewness,omitempty"`
20
-	Concurrency              uint   `toml:"concurrency" json:"concurrency,omitempty"`
21
-	Defense                  struct {
12
+	Debug                       bool   `toml:"debug" json:"debug,omitempty"`
13
+	AllowFallbackOnUnknownDC    bool   `toml:"allow-fallback-on-unknown-dc" json:"allowFallbackOnUnknownDc,omitempty"`
14
+	Secret                      string `toml:"secret" json:"secret"`
15
+	BindTo                      string `toml:"bind-to" json:"bindTo"`
16
+	ProxyProtocolListener       bool   `toml:"proxy-protocol-listener" json:"proxyProtocolListener"`
17
+	PreferIP                    string `toml:"prefer-ip" json:"preferIp,omitempty"`
18
+	DomainFrontingPort          uint   `toml:"domain-fronting-port" json:"domainFrontingPort,omitempty"`
19
+	DomainFrontingIP            string `toml:"domain-fronting-ip" json:"domainFrontingIp,omitempty"`
20
+	DomainFrontingProxyProtocol bool   `toml:"domain-fronting-proxy-protocol" json:"domainFrontingProxyProtocol,omitempty"`
21
+	TolerateTimeSkewness        string `toml:"tolerate-time-skewness" json:"tolerateTimeSkewness,omitempty"`
22
+	Concurrency                 uint   `toml:"concurrency" json:"concurrency,omitempty"`
23
+	DomainFronting              struct {
24
+		IP            string `toml:"ip" json:"ip,omitempty"`
25
+		Port          uint   `toml:"port" json:"port,omitempty"`
26
+		ProxyProtocol bool   `toml:"proxy-protocol" json:"proxyProtocol,omitempty"`
27
+	} `toml:"domain-fronting" json:"domainFronting,omitempty"`
28
+	Defense struct {
22 29
 		AntiReplay struct {
23 30
 			Enabled   bool    `toml:"enabled" json:"enabled,omitempty"`
24 31
 			MaxSize   string  `toml:"max-size" json:"maxSize,omitempty"`
@@ -60,10 +67,6 @@ type tomlConfig struct {
60 67
 			MetricPrefix string `toml:"metric-prefix" json:"metricPrefix,omitempty"`
61 68
 		} `toml:"prometheus" json:"prometheus,omitempty"`
62 69
 	} `toml:"stats" json:"stats,omitempty"`
63
-	DCOverrides []struct {
64
-		DC  uint     `toml:"dc" json:"dc"`
65
-		IPs []string `toml:"ips" json:"ips"`
66
-	} `toml:"dc-overrides" json:"dcOverrides,omitempty"`
67 70
 }
68 71
 
69 72
 func Parse(rawData []byte) (*Config, error) {

+ 4
- 0
mise.lock 查看文件

@@ -15,6 +15,10 @@ backend = "go:golang.org/x/pkgsite/cmd/pkgsite"
15 15
 version = "0.21.1"
16 16
 backend = "go:golang.org/x/tools/gopls"
17 17
 
18
+[[tools."go:golang.org/x/vuln/cmd/govulncheck"]]
19
+version = "1.1.4"
20
+backend = "go:golang.org/x/vuln/cmd/govulncheck"
21
+
18 22
 [[tools."go:mvdan.cc/gofumpt"]]
19 23
 version = "0.9.2"
20 24
 backend = "go:mvdan.cc/gofumpt"

+ 38
- 10
mtglib/conns.go 查看文件

@@ -3,10 +3,12 @@ package mtglib
3 3
 import (
4 4
 	"bytes"
5 5
 	"context"
6
+	"fmt"
6 7
 	"io"
7
-	"sync"
8
+	"net"
8 9
 
9 10
 	"github.com/9seconds/mtg/v2/essentials"
11
+	"github.com/pires/go-proxyproto"
10 12
 )
11 13
 
12 14
 type connTraffic struct {
@@ -40,22 +42,15 @@ func (c connTraffic) Write(b []byte) (int, error) {
40 42
 type connRewind struct {
41 43
 	essentials.Conn
42 44
 
43
-	active io.Reader
44 45
 	buf    bytes.Buffer
45
-	mutex  sync.RWMutex
46
+	active io.Reader
46 47
 }
47 48
 
48 49
 func (c *connRewind) Read(p []byte) (int, error) {
49
-	c.mutex.RLock()
50
-	defer c.mutex.RUnlock()
51
-
52
-	return c.active.Read(p) //nolint: wrapcheck
50
+	return c.active.Read(p)
53 51
 }
54 52
 
55 53
 func (c *connRewind) Rewind() {
56
-	c.mutex.Lock()
57
-	defer c.mutex.Unlock()
58
-
59 54
 	c.active = io.MultiReader(&c.buf, c.Conn)
60 55
 }
61 56
 
@@ -67,3 +62,36 @@ func newConnRewind(conn essentials.Conn) *connRewind {
67 62
 
68 63
 	return rv
69 64
 }
65
+
66
+type connProxyProtocol struct {
67
+	essentials.Conn
68
+
69
+	sourceAddr     net.Addr
70
+	headersWritten bool
71
+}
72
+
73
+func (c *connProxyProtocol) Write(p []byte) (int, error) {
74
+	if !c.headersWritten {
75
+		headers := proxyproto.HeaderProxyFromAddrs(2, c.sourceAddr, c.RemoteAddr())
76
+
77
+		toSend, err := headers.Format()
78
+		if err != nil {
79
+			panic(err)
80
+		}
81
+
82
+		if _, err := c.Conn.Write(toSend); err != nil {
83
+			return 0, fmt.Errorf("cannot send proxy protocol header: %w", err)
84
+		}
85
+
86
+		c.headersWritten = true
87
+	}
88
+
89
+	return c.Conn.Write(p)
90
+}
91
+
92
+func newConnProxyProtocol(source, target essentials.Conn) *connProxyProtocol {
93
+	return &connProxyProtocol{
94
+		Conn:       target,
95
+		sourceAddr: source.RemoteAddr(),
96
+	}
97
+}

+ 96
- 0
mtglib/conns_internal_test.go 查看文件

@@ -1,14 +1,17 @@
1 1
 package mtglib
2 2
 
3 3
 import (
4
+	"bufio"
4 5
 	"bytes"
5 6
 	"context"
6 7
 	"errors"
7 8
 	"io"
9
+	"net"
8 10
 	"testing"
9 11
 	"time"
10 12
 
11 13
 	"github.com/9seconds/mtg/v2/internal/testlib"
14
+	"github.com/pires/go-proxyproto"
12 15
 	"github.com/stretchr/testify/mock"
13 16
 	"github.com/stretchr/testify/suite"
14 17
 )
@@ -200,6 +203,94 @@ func (suite *ConnRewindTestSuite) TestRead() {
200 203
 	suite.Equal([]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, data)
201 204
 }
202 205
 
206
+type ConnProxyProtocolTestSuite struct {
207
+	suite.Suite
208
+
209
+	sourceConnMock *testlib.EssentialsConnMock
210
+	targetConnMock *testlib.EssentialsConnMock
211
+	conn           *connProxyProtocol
212
+}
213
+
214
+func (suite *ConnProxyProtocolTestSuite) SetupTest() {
215
+	suite.sourceConnMock = &testlib.EssentialsConnMock{}
216
+	suite.targetConnMock = &testlib.EssentialsConnMock{}
217
+
218
+	localAddr := &net.TCPAddr{
219
+		IP: net.ParseIP("127.0.0.1").To4(),
220
+	}
221
+	remoteAddr := &net.TCPAddr{
222
+		IP: net.ParseIP("127.0.0.2").To4(),
223
+	}
224
+
225
+	suite.sourceConnMock.
226
+		On("RemoteAddr").
227
+		Return(localAddr)
228
+	suite.targetConnMock.
229
+		On("RemoteAddr").
230
+		Maybe().
231
+		Return(remoteAddr)
232
+
233
+	suite.conn = newConnProxyProtocol(suite.sourceConnMock, suite.targetConnMock)
234
+}
235
+
236
+func (suite *ConnProxyProtocolTestSuite) TestRead() {
237
+	value := []byte{1, 2, 3, 4, 5}
238
+	toRead := make([]byte, len(value))
239
+
240
+	suite.targetConnMock.
241
+		On("Read", mock.AnythingOfType("[]uint8")).
242
+		Once().
243
+		Return(len(toRead), nil).
244
+		Run(func(args mock.Arguments) {
245
+			arr := args.Get(0).([]byte)
246
+			copy(arr, value)
247
+		})
248
+
249
+	n, err := suite.conn.Read(toRead)
250
+	suite.Equal(len(value), n)
251
+	suite.NoError(err)
252
+	suite.Equal(value, toRead)
253
+}
254
+
255
+func (suite *ConnProxyProtocolTestSuite) TestWrite() {
256
+	value := []byte{1, 2, 3, 4, 5}
257
+	buf := &bytes.Buffer{}
258
+	bufReader := bufio.NewReader(buf)
259
+
260
+	suite.targetConnMock.
261
+		On("Write", mock.AnythingOfType("[]uint8")).
262
+		Return(28, nil).
263
+		Run(func(args mock.Arguments) {
264
+			arr := args.Get(0).([]byte)
265
+			buf.Write(arr)
266
+		})
267
+
268
+	_, err := suite.conn.Write(value)
269
+	suite.NoError(err)
270
+
271
+	header, err := proxyproto.Read(bufReader)
272
+	suite.NoError(err)
273
+
274
+	sourceAddr, destAddr, ok := header.TCPAddrs()
275
+	suite.True(ok)
276
+	suite.Equal(suite.sourceConnMock.RemoteAddr(), sourceAddr)
277
+	suite.Equal(suite.targetConnMock.RemoteAddr(), destAddr)
278
+
279
+	read, _ := io.ReadAll(bufReader)
280
+	suite.Equal(value, read)
281
+
282
+	_, err = suite.conn.Write(value)
283
+	suite.NoError(err)
284
+
285
+	read, _ = io.ReadAll(bufReader)
286
+	suite.Equal(value, read)
287
+}
288
+
289
+func (suite *ConnProxyProtocolTestSuite) TearDownTest() {
290
+	suite.sourceConnMock.AssertExpectations(suite.T())
291
+	suite.targetConnMock.AssertExpectations(suite.T())
292
+}
293
+
203 294
 func TestConnTraffic(t *testing.T) {
204 295
 	t.Parallel()
205 296
 	suite.Run(t, &ConnTrafficTestSuite{})
@@ -209,3 +300,8 @@ func TestConnRewind(t *testing.T) {
209 300
 	t.Parallel()
210 301
 	suite.Run(t, &ConnRewindTestSuite{})
211 302
 }
303
+
304
+func TestConnProxyProtocol(t *testing.T) {
305
+	t.Parallel()
306
+	suite.Run(t, &ConnProxyProtocolTestSuite{})
307
+}

+ 10
- 3
mtglib/internal/dc/addr.go 查看文件

@@ -1,10 +1,17 @@
1 1
 package dc
2 2
 
3
+import (
4
+	"fmt"
5
+
6
+	"github.com/9seconds/mtg/v2/mtglib/internal/obfuscation"
7
+)
8
+
3 9
 type Addr struct {
4
-	Network string
5
-	Address string
10
+	Network    string
11
+	Address    string
12
+	Obfuscator obfuscation.Obfuscator
6 13
 }
7 14
 
8 15
 func (d Addr) String() string {
9
-	return d.Address
16
+	return fmt.Sprintf("addr=%s, secret=%v", d.Address, d.Obfuscator.Secret)
10 17
 }

+ 0
- 16
mtglib/internal/dc/addr_test.go 查看文件

@@ -1,16 +0,0 @@
1
-package dc_test
2
-
3
-import (
4
-	"testing"
5
-
6
-	"github.com/9seconds/mtg/v2/mtglib/internal/dc"
7
-	"github.com/stretchr/testify/assert"
8
-)
9
-
10
-func TestAddr(t *testing.T) {
11
-	t.Parallel()
12
-
13
-	addr := dc.Addr{Network: "tcp4", Address: "127.0.0.1:443"}
14
-
15
-	assert.Equal(t, "127.0.0.1:443", addr.String())
16
-}

+ 54
- 49
mtglib/internal/dc/init.go 查看文件

@@ -1,5 +1,10 @@
1 1
 package dc
2 2
 
3
+import (
4
+	"context"
5
+	"time"
6
+)
7
+
3 8
 type preferIP uint8
4 9
 
5 10
 const (
@@ -10,7 +15,18 @@ const (
10 15
 )
11 16
 
12 17
 const (
18
+	// Default DC to connect to if not sure.
13 19
 	DefaultDC = 2
20
+
21
+	// How often should we request updates from
22
+	// https://core.telegram.org/getProxyConfig
23
+	PublicConfigUpdateEach  = time.Hour
24
+	PublicConfigUpdateURLv4 = "https://core.telegram.org/getProxyConfig"
25
+	PublicConfigUpdateURLv6 = "https://core.telegram.org/getProxyConfigV6"
26
+
27
+	// How often should we extract hosts from Telegram using help.getConfig
28
+	// method.
29
+	OwnConfigUpdateEach = time.Hour
14 30
 )
15 31
 
16 32
 type Logger interface {
@@ -18,56 +34,45 @@ type Logger interface {
18 34
 	WarningError(msg string, err error)
19 35
 }
20 36
 
21
-var (
22
-	// https://github.com/telegramdesktop/tdesktop/blob/master/Telegram/SourceFiles/mtproto/mtproto_dc_options.cpp#L30
23
-	defaultDCAddrSet = dcAddrSet{
24
-		v4: map[int][]Addr{
25
-			1: {
26
-				{Network: "tcp4", Address: "149.154.175.50:443"},
27
-			},
28
-			2: {
29
-				{Network: "tcp4", Address: "149.154.167.51:443"},
30
-				{Network: "tcp4", Address: "95.161.76.100:443"},
31
-			},
32
-			3: {
33
-				{Network: "tcp4", Address: "149.154.175.100:443"},
34
-			},
35
-			4: {
36
-				{Network: "tcp4", Address: "149.154.167.91:443"},
37
-			},
38
-			5: {
39
-				{Network: "tcp4", Address: "149.154.171.5:443"},
40
-			},
37
+type Updater interface {
38
+	Run(ctx context.Context)
39
+}
40
+
41
+// https://github.com/telegramdesktop/tdesktop/blob/master/Telegram/SourceFiles/mtproto/mtproto_dc_options.cpp#L30
42
+var defaultDCAddrSet = dcAddrSet{
43
+	v4: map[int][]Addr{
44
+		1: {
45
+			{Network: "tcp4", Address: "149.154.175.50:443"},
41 46
 		},
42
-		v6: map[int][]Addr{
43
-			1: {
44
-				{Network: "tcp6", Address: "[2001:b28:f23d:f001::a]:443"},
45
-			},
46
-			2: {
47
-				{Network: "tcp6", Address: "[2001:67c:04e8:f002::a]:443"},
48
-			},
49
-			3: {
50
-				{Network: "tcp6", Address: "[2001:b28:f23d:f003::a]:443"},
51
-			},
52
-			4: {
53
-				{Network: "tcp6", Address: "[2001:67c:04e8:f004::a]:443"},
54
-			},
55
-			5: {
56
-				{Network: "tcp6", Address: "[2001:b28:f23f:f005::a]:443"},
57
-			},
47
+		2: {
48
+			{Network: "tcp4", Address: "149.154.167.51:443"},
49
+			{Network: "tcp4", Address: "95.161.76.100:443"},
58 50
 		},
59
-	}
60
-
61
-	defaultDCOverridesAddrSet = dcAddrSet{
62
-		v4: map[int][]Addr{
63
-			203: {
64
-				{Network: "tcp4", Address: "91.105.192.100:443"},
65
-			},
51
+		3: {
52
+			{Network: "tcp4", Address: "149.154.175.100:443"},
66 53
 		},
67
-		v6: map[int][]Addr{
68
-			203: {
69
-				{Network: "tcp6", Address: "[2a0a:f280:0203:000a:5000:0000:0000:0100]:443"},
70
-			},
54
+		4: {
55
+			{Network: "tcp4", Address: "149.154.167.91:443"},
71 56
 		},
72
-	}
73
-)
57
+		5: {
58
+			{Network: "tcp4", Address: "149.154.171.5:443"},
59
+		},
60
+	},
61
+	v6: map[int][]Addr{
62
+		1: {
63
+			{Network: "tcp6", Address: "[2001:b28:f23d:f001::a]:443"},
64
+		},
65
+		2: {
66
+			{Network: "tcp6", Address: "[2001:67c:04e8:f002::a]:443"},
67
+		},
68
+		3: {
69
+			{Network: "tcp6", Address: "[2001:b28:f23d:f003::a]:443"},
70
+		},
71
+		4: {
72
+			{Network: "tcp6", Address: "[2001:67c:04e8:f004::a]:443"},
73
+		},
74
+		5: {
75
+			{Network: "tcp6", Address: "[2001:b28:f23f:f005::a]:443"},
76
+		},
77
+	},
78
+}

+ 43
- 0
mtglib/internal/dc/init_test.go 查看文件

@@ -0,0 +1,43 @@
1
+package dc
2
+
3
+import (
4
+	"context"
5
+
6
+	"github.com/stretchr/testify/mock"
7
+	"github.com/stretchr/testify/suite"
8
+)
9
+
10
+type LoggerMock struct {
11
+	mock.Mock
12
+}
13
+
14
+func (m *LoggerMock) Info(msg string) {
15
+	m.Called(msg)
16
+}
17
+
18
+func (m *LoggerMock) WarningError(msg string, err error) {
19
+	m.Called(msg, err)
20
+}
21
+
22
+type UpdaterTestSuiteBase struct {
23
+	suite.Suite
24
+
25
+	ctx        context.Context
26
+	ctxCancel  context.CancelFunc
27
+	loggerMock *LoggerMock
28
+}
29
+
30
+func (s *UpdaterTestSuiteBase) SetupTest() {
31
+	ctx, cancel := context.WithCancel(context.Background())
32
+
33
+	s.loggerMock = &LoggerMock{}
34
+	s.loggerMock.On("Info", mock.AnythingOfType("string"))
35
+	s.loggerMock.On("WarningError", mock.AnythingOfType("string"), mock.Anything)
36
+
37
+	s.ctx = ctx
38
+	s.ctxCancel = cancel
39
+}
40
+
41
+func (s *UpdaterTestSuiteBase) TearDownTest() {
42
+	s.ctxCancel()
43
+}

+ 93
- 0
mtglib/internal/dc/public_config_updater.go 查看文件

@@ -0,0 +1,93 @@
1
+package dc
2
+
3
+import (
4
+	"bufio"
5
+	"context"
6
+	"fmt"
7
+	"io"
8
+	"net/http"
9
+	"regexp"
10
+	"strconv"
11
+)
12
+
13
+var publicConfigRe = regexp.MustCompile(`^\s*proxy_for\s+(\d+)\s+(\S+?)?;\s*$`)
14
+
15
+type PublicConfigUpdater struct {
16
+	updater
17
+
18
+	http *http.Client
19
+	tg   *Telegram
20
+}
21
+
22
+func (p *PublicConfigUpdater) Run(ctx context.Context, url, network string) {
23
+	p.run(ctx, func() error {
24
+		req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
25
+		if err != nil {
26
+			panic(err)
27
+		}
28
+
29
+		resp, err := p.http.Do(req)
30
+		if err != nil {
31
+			if resp != nil {
32
+				io.Copy(io.Discard, resp.Body) //nolint: errcheck
33
+				resp.Body.Close()              //nolint: errcheck
34
+			}
35
+			return fmt.Errorf("cannot fetch url %s: %w", url, err)
36
+		}
37
+
38
+		if resp.StatusCode >= http.StatusBadRequest {
39
+			return fmt.Errorf("unexpected status code from %s: %d", url, resp.StatusCode)
40
+		}
41
+
42
+		scanner := bufio.NewScanner(resp.Body)
43
+		addrs := map[int][]Addr{}
44
+
45
+		for scanner.Scan() {
46
+			matches := publicConfigRe.FindStringSubmatch(scanner.Text())
47
+			if len(matches) != 3 {
48
+				continue
49
+			}
50
+
51
+			dc, err := strconv.Atoi(matches[1])
52
+			if err != nil {
53
+				continue
54
+			}
55
+
56
+			switch dc {
57
+			// this is a list of DC we currently support. Other are ignored.
58
+			case 203: // CDN DC
59
+				p.logger.Info(fmt.Sprintf("found %s address for DC %d", matches[2], dc))
60
+				addrs[dc] = append(addrs[dc], Addr{
61
+					Network: network,
62
+					Address: matches[2],
63
+				})
64
+			}
65
+		}
66
+
67
+		if err := scanner.Err(); err != nil {
68
+			return fmt.Errorf("cannot read response body from %s: %w", url, err)
69
+		}
70
+
71
+		p.tg.lock.Lock()
72
+		defer p.tg.lock.Unlock()
73
+
74
+		if network == "tcp4" {
75
+			p.tg.view.publicConfigs.v4 = addrs
76
+		} else {
77
+			p.tg.view.publicConfigs.v6 = addrs
78
+		}
79
+
80
+		return nil
81
+	})
82
+}
83
+
84
+func NewPublicConfigUpdater(tg *Telegram, logger Logger, client *http.Client) *PublicConfigUpdater {
85
+	return &PublicConfigUpdater{
86
+		updater: updater{
87
+			logger: logger,
88
+			period: PublicConfigUpdateEach,
89
+		},
90
+		http: client,
91
+		tg:   tg,
92
+	}
93
+}

+ 113
- 0
mtglib/internal/dc/public_config_updater_test.go 查看文件

@@ -0,0 +1,113 @@
1
+package dc
2
+
3
+import (
4
+	"net/http"
5
+	"net/http/httptest"
6
+	"sync"
7
+	"testing"
8
+	"time"
9
+
10
+	"github.com/stretchr/testify/require"
11
+	"github.com/stretchr/testify/suite"
12
+)
13
+
14
+type PublicConfigUpdaterTestSuite struct {
15
+	UpdaterTestSuiteBase
16
+
17
+	u               *PublicConfigUpdater
18
+	lock            sync.Mutex
19
+	srv             *httptest.Server
20
+	responseHandler func(w http.ResponseWriter)
21
+}
22
+
23
+func (s *PublicConfigUpdaterTestSuite) SetupSuite() {
24
+	s.srv = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
25
+		s.lock.Lock()
26
+		s.responseHandler(w)
27
+		s.lock.Unlock()
28
+	}))
29
+}
30
+
31
+func (s *PublicConfigUpdaterTestSuite) TearDownSuite() {
32
+	s.srv.Close()
33
+}
34
+
35
+func (s *PublicConfigUpdaterTestSuite) SetupTest() {
36
+	s.UpdaterTestSuiteBase.SetupTest()
37
+
38
+	tg, err := New("prefer-ipv4")
39
+	require.NoError(s.T(), err)
40
+
41
+	s.u = NewPublicConfigUpdater(tg, s.loggerMock, s.srv.Client())
42
+}
43
+
44
+func (s *PublicConfigUpdaterTestSuite) Test502StatusCode() {
45
+	s.responseHandler = func(w http.ResponseWriter) {
46
+		w.WriteHeader(http.StatusBadGateway)
47
+	}
48
+	s.u.Run(s.ctx, s.srv.URL, "tcp4")
49
+
50
+	time.Sleep(100 * time.Millisecond)
51
+	s.ctxCancel()
52
+	s.u.Wait()
53
+
54
+	s.Len(s.u.tg.view.publicConfigs.v4, 0)
55
+}
56
+
57
+func (s *PublicConfigUpdaterTestSuite) TestEmptyFile() {
58
+	s.responseHandler = func(w http.ResponseWriter) {
59
+		w.WriteHeader(http.StatusOK)
60
+	}
61
+	s.u.Run(s.ctx, s.srv.URL, "tcp4")
62
+
63
+	time.Sleep(100 * time.Millisecond)
64
+	s.ctxCancel()
65
+	s.u.Wait()
66
+
67
+	s.Len(s.u.tg.view.publicConfigs.v4, 0)
68
+}
69
+
70
+func (s *PublicConfigUpdaterTestSuite) TestGarbage() {
71
+	result := `
72
+proxy_for -1 -1;
73
+proxy_for 100 100.10.0.0:3333;
74
+lala 0 0
75
+`
76
+
77
+	s.responseHandler = func(w http.ResponseWriter) {
78
+		w.WriteHeader(http.StatusOK)
79
+		w.Write([]byte(result)) //nolint: errcheck
80
+	}
81
+	s.u.Run(s.ctx, s.srv.URL, "tcp4")
82
+
83
+	time.Sleep(100 * time.Millisecond)
84
+	s.ctxCancel()
85
+	s.u.Wait()
86
+
87
+	s.Len(s.u.tg.view.publicConfigs.v4, 0)
88
+}
89
+
90
+func (s *PublicConfigUpdaterTestSuite) TestOk() {
91
+	result := `
92
+proxy_for 203 100.10.0.0:3333;
93
+proxy_for -100 101.10.0.0:3333;
94
+`
95
+
96
+	s.responseHandler = func(w http.ResponseWriter) {
97
+		w.WriteHeader(http.StatusOK)
98
+		w.Write([]byte(result)) //nolint: errcheck
99
+	}
100
+	s.u.Run(s.ctx, s.srv.URL, "tcp4")
101
+
102
+	time.Sleep(100 * time.Millisecond)
103
+	s.ctxCancel()
104
+	s.u.Wait()
105
+
106
+	s.Len(s.u.tg.view.publicConfigs.v4, 1)
107
+	s.Len(s.u.tg.view.publicConfigs.v4[203], 1)
108
+	s.Equal("100.10.0.0:3333", s.u.tg.view.publicConfigs.v4[203][0].Address)
109
+}
110
+
111
+func TestPublicConfigUpdater(t *testing.T) {
112
+	suite.Run(t, &PublicConfigUpdaterTestSuite{})
113
+}

+ 6
- 35
mtglib/internal/dc/telegram.go 查看文件

@@ -2,16 +2,20 @@ package dc
2 2
 
3 3
 import (
4 4
 	"fmt"
5
-	"net"
6 5
 	"strings"
6
+	"sync"
7 7
 )
8 8
 
9 9
 type Telegram struct {
10
+	lock     sync.RWMutex
10 11
 	view     dcView
11 12
 	preferIP preferIP
12 13
 }
13 14
 
14 15
 func (t *Telegram) GetAddresses(dc int) []Addr {
16
+	t.lock.RLock()
17
+	defer t.lock.RUnlock()
18
+
15 19
 	switch t.preferIP {
16 20
 	case preferIPOnlyIPv4:
17 21
 		return t.view.getV4(dc)
@@ -24,7 +28,7 @@ func (t *Telegram) GetAddresses(dc int) []Addr {
24 28
 	return append(t.view.getV6(dc), t.view.getV4(dc)...)
25 29
 }
26 30
 
27
-func New(ipPreference string, userOverrides map[int][]string) (*Telegram, error) {
31
+func New(ipPreference string) (*Telegram, error) {
28 32
 	var pref preferIP
29 33
 
30 34
 	switch strings.ToLower(ipPreference) {
@@ -40,40 +44,7 @@ func New(ipPreference string, userOverrides map[int][]string) (*Telegram, error)
40 44
 		return nil, fmt.Errorf("unknown ip preference %s", ipPreference)
41 45
 	}
42 46
 
43
-	overrides := dcAddrSet{
44
-		v4: map[int][]Addr{},
45
-		v6: map[int][]Addr{},
46
-	}
47
-	for dc, addrs := range userOverrides {
48
-		for _, addr := range addrs {
49
-			host, _, err := net.SplitHostPort(addr)
50
-			if err != nil {
51
-				return nil, fmt.Errorf("incorrect host %s: %w", addr, err)
52
-			}
53
-
54
-			parsed := net.ParseIP(host)
55
-			if parsed == nil {
56
-				return nil, fmt.Errorf("incorrect host %s", addr)
57
-			}
58
-
59
-			if parsed.To4() != nil {
60
-				overrides.v4[dc] = append(overrides.v4[dc], Addr{
61
-					Network: "tcp4",
62
-					Address: addr,
63
-				})
64
-			} else {
65
-				overrides.v6[dc] = append(overrides.v6[dc], Addr{
66
-					Network: "tcp6",
67
-					Address: addr,
68
-				})
69
-			}
70
-		}
71
-	}
72
-
73 47
 	return &Telegram{
74
-		view: dcView{
75
-			overrides: overrides,
76
-		},
77 48
 		preferIP: pref,
78 49
 	}, nil
79 50
 }

+ 47
- 0
mtglib/internal/dc/updater.go 查看文件

@@ -0,0 +1,47 @@
1
+package dc
2
+
3
+import (
4
+	"context"
5
+	"sync"
6
+	"time"
7
+)
8
+
9
+type updater struct {
10
+	wg     sync.WaitGroup
11
+	logger Logger
12
+	period time.Duration
13
+}
14
+
15
+func (u *updater) Wait() {
16
+	u.wg.Wait()
17
+}
18
+
19
+func (u *updater) run(ctx context.Context, callback func() error) {
20
+	u.wg.Go(func() {
21
+		ticker := time.NewTicker(u.period)
22
+
23
+		defer func() {
24
+			ticker.Stop()
25
+
26
+			select {
27
+			case <-ticker.C:
28
+			default:
29
+			}
30
+		}()
31
+
32
+		for {
33
+			u.logger.Info("start update")
34
+			if err := callback(); err != nil {
35
+				u.logger.WarningError("cannot update: %w", err)
36
+			}
37
+			u.logger.Info("updated")
38
+
39
+			select {
40
+			case <-ctx.Done():
41
+				u.logger.Info("stop updating")
42
+				return
43
+			case <-ticker.C:
44
+			}
45
+		}
46
+	})
47
+}

+ 55
- 0
mtglib/internal/dc/updater_test.go 查看文件

@@ -0,0 +1,55 @@
1
+package dc
2
+
3
+import (
4
+	"sync"
5
+	"testing"
6
+	"time"
7
+
8
+	"github.com/stretchr/testify/suite"
9
+)
10
+
11
+type UpdaterTestSuite struct {
12
+	UpdaterTestSuiteBase
13
+
14
+	u updater
15
+}
16
+
17
+func (s *UpdaterTestSuite) SetupTest() {
18
+	s.UpdaterTestSuiteBase.SetupTest()
19
+	s.u = updater{
20
+		logger: s.loggerMock,
21
+		period: 100 * time.Millisecond,
22
+	}
23
+}
24
+
25
+func (s *UpdaterTestSuite) TestPeriodicUpdates() {
26
+	ticker := time.NewTicker(10 * time.Millisecond)
27
+	defer ticker.Stop()
28
+
29
+	lock := &sync.Mutex{}
30
+	collected := []time.Time{}
31
+
32
+	go s.u.run(s.ctx, func() error {
33
+		select {
34
+		case <-s.ctx.Done():
35
+		case value := <-ticker.C:
36
+			lock.Lock()
37
+			collected = append(collected, value)
38
+			lock.Unlock()
39
+		}
40
+
41
+		return nil
42
+	})
43
+
44
+	s.Eventually(func() bool {
45
+		lock.Lock()
46
+		defer lock.Unlock()
47
+
48
+		return len(collected) == 3
49
+	}, time.Second, 10*time.Millisecond)
50
+}
51
+
52
+func TestUpdater(t *testing.T) {
53
+	t.Parallel()
54
+	suite.Run(t, &UpdaterTestSuite{})
55
+}

+ 3
- 5
mtglib/internal/dc/view.go 查看文件

@@ -1,20 +1,18 @@
1 1
 package dc
2 2
 
3 3
 type dcView struct {
4
-	overrides dcAddrSet
4
+	publicConfigs dcAddrSet
5 5
 }
6 6
 
7 7
 func (d dcView) getV4(dc int) []Addr {
8
-	addrs := d.overrides.getV4(dc)
9
-	addrs = append(addrs, defaultDCOverridesAddrSet.getV4(dc)...)
8
+	addrs := d.publicConfigs.getV4(dc)
10 9
 	addrs = append(addrs, defaultDCAddrSet.getV4(dc)...)
11 10
 
12 11
 	return addrs
13 12
 }
14 13
 
15 14
 func (d dcView) getV6(dc int) []Addr {
16
-	addrs := d.overrides.getV6(dc)
17
-	addrs = append(addrs, defaultDCOverridesAddrSet.getV6(dc)...)
15
+	addrs := d.publicConfigs.getV6(dc)
18 16
 	addrs = append(addrs, defaultDCAddrSet.getV6(dc)...)
19 17
 
20 18
 	return addrs

+ 7
- 9
mtglib/internal/dc/view_test.go 查看文件

@@ -16,7 +16,7 @@ type ViewTestSuite struct {
16 16
 
17 17
 func (suite *ViewTestSuite) SetupSuite() {
18 18
 	suite.view = dcView{
19
-		overrides: dcAddrSet{
19
+		publicConfigs: dcAddrSet{
20 20
 			v4: map[int][]Addr{
21 21
 				111: {
22 22
 					{Network: "tcp4", Address: "127.0.0.1:443"},
@@ -37,15 +37,14 @@ func (suite *ViewTestSuite) SetupSuite() {
37 37
 func (suite *ViewTestSuite) TestGetV4() {
38 38
 	testData := map[int][]Addr{
39 39
 		111: {
40
-			{"tcp4", "127.0.0.1:443"},
40
+			{Network: "tcp4", Address: "127.0.0.1:443"},
41 41
 		},
42 42
 		203: {
43
-			{"tcp4", "127.0.0.2:443"},
44
-			{"tcp4", "91.105.192.100:443"},
43
+			{Network: "tcp4", Address: "127.0.0.2:443"},
45 44
 		},
46 45
 		2: {
47
-			{"tcp4", "149.154.167.51:443"},
48
-			{"tcp4", "95.161.76.100:443"},
46
+			{Network: "tcp4", Address: "149.154.167.51:443"},
47
+			{Network: "tcp4", Address: "95.161.76.100:443"},
49 48
 		},
50 49
 	}
51 50
 
@@ -60,11 +59,10 @@ func (suite *ViewTestSuite) TestGetV6() {
60 59
 	testData := map[int][]Addr{
61 60
 		111: {},
62 61
 		203: {
63
-			{"tcp6", "xxx"},
64
-			{"tcp6", "[2a0a:f280:0203:000a:5000:0000:0000:0100]:443"},
62
+			{Network: "tcp6", Address: "xxx"},
65 63
 		},
66 64
 		1: {
67
-			{"tcp6", "[2001:b28:f23d:f001::a]:443"},
65
+			{Network: "tcp6", Address: "[2001:b28:f23d:f001::a]:443"},
68 66
 		},
69 67
 	}
70 68
 

+ 9
- 10
mtglib/internal/faketls/conn.go 查看文件

@@ -47,10 +47,7 @@ func (c *Conn) Write(p []byte) (int, error) {
47 47
 	rec.Type = record.TypeApplicationData
48 48
 	rec.Version = record.Version12
49 49
 
50
-	sendBuffer := acquireBytesBuffer()
51
-	defer releaseBytesBuffer(sendBuffer)
52
-
53
-	lenP := len(p)
50
+	written := 0
54 51
 
55 52
 	for len(p) > 0 {
56 53
 		chunkSize := rand.IntN(record.TLSMaxRecordSize)
@@ -60,14 +57,16 @@ func (c *Conn) Write(p []byte) (int, error) {
60 57
 
61 58
 		rec.Payload.Reset()
62 59
 		rec.Payload.Write(p[:chunkSize])
63
-		rec.Dump(sendBuffer) //nolint: errcheck
64 60
 
65
-		p = p[chunkSize:]
66
-	}
61
+		err := rec.Dump(c.Conn)
62
+		written += chunkSize
67 63
 
68
-	if _, err := c.Conn.Write(sendBuffer.Bytes()); err != nil {
69
-		return 0, err //nolint: wrapcheck
64
+		if err != nil {
65
+			return written, err
66
+		}
67
+
68
+		p = p[chunkSize:]
70 69
 	}
71 70
 
72
-	return lenP, nil
71
+	return written, nil
73 72
 }

+ 0
- 21
mtglib/internal/faketls/pools.go 查看文件

@@ -1,21 +0,0 @@
1
-package faketls
2
-
3
-import (
4
-	"bytes"
5
-	"sync"
6
-)
7
-
8
-var bytesBufferPool = sync.Pool{
9
-	New: func() any {
10
-		return &bytes.Buffer{}
11
-	},
12
-}
13
-
14
-func acquireBytesBuffer() *bytes.Buffer {
15
-	return bytesBufferPool.Get().(*bytes.Buffer) //nolint: forcetypeassert
16
-}
17
-
18
-func releaseBytesBuffer(b *bytes.Buffer) {
19
-	b.Reset()
20
-	bytesBufferPool.Put(b)
21
-}

+ 3
- 4
mtglib/internal/faketls/welcome.go 查看文件

@@ -1,6 +1,7 @@
1 1
 package faketls
2 2
 
3 3
 import (
4
+	"bytes"
4 5
 	"crypto/hmac"
5 6
 	"crypto/rand"
6 7
 	"crypto/sha256"
@@ -13,8 +14,7 @@ import (
13 14
 )
14 15
 
15 16
 func SendWelcomePacket(writer io.Writer, secret []byte, clientHello ClientHello) error {
16
-	buf := acquireBytesBuffer()
17
-	defer releaseBytesBuffer(buf)
17
+	buf := &bytes.Buffer{}
18 18
 
19 19
 	rec := record.AcquireRecord()
20 20
 	defer record.ReleaseRecord(rec)
@@ -58,8 +58,7 @@ func SendWelcomePacket(writer io.Writer, secret []byte, clientHello ClientHello)
58 58
 }
59 59
 
60 60
 func generateServerHello(writer io.Writer, clientHello ClientHello) {
61
-	bodyBuf := acquireBytesBuffer()
62
-	defer releaseBytesBuffer(bodyBuf)
61
+	bodyBuf := &bytes.Buffer{}
63 62
 
64 63
 	sliceBuf := [2]byte{}
65 64
 	digest := [RandomLen]byte{}

+ 0
- 54
mtglib/internal/obfuscated2/client_handshake.go 查看文件

@@ -1,54 +0,0 @@
1
-package obfuscated2
2
-
3
-import (
4
-	"crypto/cipher"
5
-	"crypto/subtle"
6
-	"encoding/hex"
7
-	"fmt"
8
-	"io"
9
-)
10
-
11
-type clientHandhakeFrame struct {
12
-	handshakeFrame
13
-}
14
-
15
-func (c *clientHandhakeFrame) decryptor(secret []byte) cipher.Stream {
16
-	hasher := acquireSha256Hasher()
17
-	defer releaseSha256Hasher(hasher)
18
-
19
-	hasher.Write(c.key())
20
-	hasher.Write(secret)
21
-
22
-	return makeAesCtr(hasher.Sum(nil), c.iv())
23
-}
24
-
25
-func (c *clientHandhakeFrame) encryptor(secret []byte) cipher.Stream {
26
-	invertedHandshake := c.invert()
27
-
28
-	hasher := acquireSha256Hasher()
29
-	defer releaseSha256Hasher(hasher)
30
-
31
-	hasher.Write(invertedHandshake.key())
32
-	hasher.Write(secret)
33
-
34
-	return makeAesCtr(hasher.Sum(nil), invertedHandshake.iv())
35
-}
36
-
37
-func ClientHandshake(secret []byte, reader io.Reader) (int, cipher.Stream, cipher.Stream, error) {
38
-	handshake := clientHandhakeFrame{}
39
-
40
-	if _, err := io.ReadFull(reader, handshake.data[:]); err != nil {
41
-		return 0, nil, nil, fmt.Errorf("cannot read frame: %w", err)
42
-	}
43
-
44
-	decryptor := handshake.decryptor(secret)
45
-	encryptor := handshake.encryptor(secret)
46
-
47
-	decryptor.XORKeyStream(handshake.data[:], handshake.data[:])
48
-
49
-	if val := handshake.connectionType(); subtle.ConstantTimeCompare(handshakeConnectionType, val) != 1 {
50
-		return 0, nil, nil, fmt.Errorf("unsupported connection type: %s", hex.EncodeToString(val))
51
-	}
52
-
53
-	return handshake.dc(), encryptor, decryptor, nil
54
-}

+ 0
- 32
mtglib/internal/obfuscated2/client_handshake_fuzz_internal_test.go 查看文件

@@ -1,32 +0,0 @@
1
-package obfuscated2
2
-
3
-import (
4
-	"bytes"
5
-	"testing"
6
-
7
-	"github.com/stretchr/testify/require"
8
-)
9
-
10
-var FuzzClientHandshakeSecret = []byte{1, 2, 3}
11
-
12
-func FuzzClientHandshake(f *testing.F) {
13
-	f.Add([]byte{1, 2, 3})
14
-
15
-	f.Fuzz(func(t *testing.T, frame []byte) {
16
-		data := bytes.NewReader(frame)
17
-
18
-		if _, _, _, err := ClientHandshake(FuzzClientHandshakeSecret, data); err != nil {
19
-			return
20
-		}
21
-
22
-		handshake := clientHandhakeFrame{}
23
-		require.Len(t, frame, handshakeFrameLen)
24
-
25
-		copy(handshake.data[:], frame)
26
-
27
-		decryptor := handshake.decryptor(FuzzClientHandshakeSecret)
28
-		decryptor.XORKeyStream(handshake.data[:], handshake.data[:])
29
-
30
-		require.Equal(t, handshakeConnectionType, handshake.connectionType())
31
-	})
32
-}

+ 0
- 89
mtglib/internal/obfuscated2/client_handshake_test.go 查看文件

@@ -1,89 +0,0 @@
1
-package obfuscated2_test
2
-
3
-import (
4
-	"bytes"
5
-	"testing"
6
-
7
-	"github.com/9seconds/mtg/v2/internal/testlib"
8
-	"github.com/9seconds/mtg/v2/mtglib/internal/obfuscated2"
9
-	"github.com/stretchr/testify/assert"
10
-	"github.com/stretchr/testify/mock"
11
-	"github.com/stretchr/testify/suite"
12
-)
13
-
14
-type ClientHandshakeTestSuite struct {
15
-	suite.Suite
16
-	SnapshotTestSuite
17
-}
18
-
19
-func (suite *ClientHandshakeTestSuite) SetupSuite() {
20
-	suite.NoError(suite.IngestSnapshots(".", "client-handshake-snapshot-"))
21
-}
22
-
23
-func (suite *ClientHandshakeTestSuite) TestCannotRead() {
24
-	buf := bytes.NewBuffer([]byte{1, 2, 3})
25
-	_, _, _, err := obfuscated2.ClientHandshake([]byte{1, 2, 3}, buf) //nolint: dogsled
26
-
27
-	suite.Error(err)
28
-}
29
-
30
-func (suite *ClientHandshakeTestSuite) TestOk() {
31
-	for nameV, snapshotV := range suite.snapshots {
32
-		snapshot := snapshotV
33
-
34
-		suite.T().Run(nameV, func(t *testing.T) {
35
-			buf := bytes.NewBuffer(snapshot.Frame.data)
36
-
37
-			dc, encryptor, decryptor, err := obfuscated2.ClientHandshake(
38
-				snapshot.Secret.data, buf)
39
-			assert.NoError(t, err)
40
-			assert.EqualValues(t, snapshot.DC, dc)
41
-
42
-			writeData := make([]byte, len(snapshot.Encrypted.Text.data))
43
-			readData := make([]byte, len(snapshot.Decrypted.Text.data))
44
-
45
-			connMock := &testlib.EssentialsConnMock{}
46
-			connMock.On("Read", mock.Anything).
47
-				Once().
48
-				Return(len(snapshot.Decrypted.Text.data), nil).
49
-				Run(func(args mock.Arguments) {
50
-					arr, ok := args.Get(0).([]byte)
51
-
52
-					suite.True(ok)
53
-					copy(arr, snapshot.Decrypted.Cipher.data)
54
-				})
55
-			connMock.On("Write", mock.Anything).
56
-				Once().
57
-				Return(len(snapshot.Encrypted.Text.data), nil).
58
-				Run(func(args mock.Arguments) {
59
-					arr, ok := args.Get(0).([]byte)
60
-
61
-					suite.True(ok)
62
-					copy(writeData, arr)
63
-				})
64
-
65
-			conn := obfuscated2.Conn{
66
-				Conn:      connMock,
67
-				Encryptor: encryptor,
68
-				Decryptor: decryptor,
69
-			}
70
-
71
-			n, err := conn.Read(readData)
72
-			assert.Equal(t, len(readData), n)
73
-			assert.NoError(t, err)
74
-			assert.Equal(t, snapshot.Decrypted.Text.data, readData)
75
-
76
-			n, err = conn.Write(snapshot.Encrypted.Text.data)
77
-			assert.Equal(t, len(writeData), n)
78
-			assert.NoError(t, err)
79
-			assert.Equal(t, snapshot.Encrypted.Cipher.data, writeData)
80
-
81
-			connMock.AssertExpectations(t)
82
-		})
83
-	}
84
-}
85
-
86
-func TestClientHandshake(t *testing.T) {
87
-	t.Parallel()
88
-	suite.Run(t, &ClientHandshakeTestSuite{})
89
-}

+ 0
- 37
mtglib/internal/obfuscated2/conn.go 查看文件

@@ -1,37 +0,0 @@
1
-package obfuscated2
2
-
3
-import (
4
-	"crypto/cipher"
5
-
6
-	"github.com/9seconds/mtg/v2/essentials"
7
-)
8
-
9
-type Conn struct {
10
-	essentials.Conn
11
-
12
-	Encryptor cipher.Stream
13
-	Decryptor cipher.Stream
14
-}
15
-
16
-func (c Conn) Read(p []byte) (int, error) {
17
-	n, err := c.Conn.Read(p)
18
-	if err != nil {
19
-		return n, err //nolint: wrapcheck
20
-	}
21
-
22
-	c.Decryptor.XORKeyStream(p, p[:n])
23
-
24
-	return n, nil
25
-}
26
-
27
-func (c Conn) Write(p []byte) (int, error) {
28
-	buf := acquireBytesBuffer()
29
-	defer releaseBytesBuffer(buf)
30
-
31
-	buf.Write(p)
32
-
33
-	payload := buf.Bytes()
34
-	c.Encryptor.XORKeyStream(payload, payload)
35
-
36
-	return c.Conn.Write(payload) //nolint: wrapcheck
37
-}

+ 0
- 71
mtglib/internal/obfuscated2/handshake_frame.go 查看文件

@@ -1,71 +0,0 @@
1
-package obfuscated2
2
-
3
-const (
4
-	// DefaultDC defines a number of the default DC to use. This value used
5
-	// only if a value from obfuscated2 handshake frame is 0 (default).
6
-	DefaultDC = 2
7
-
8
-	handshakeFrameLen = 64
9
-
10
-	handshakeFrameLenKey            = 32
11
-	handshakeFrameLenIV             = 16
12
-	handshakeFrameLenConnectionType = 4
13
-
14
-	handshakeFrameOffsetStart          = 8
15
-	handshakeFrameOffsetKey            = handshakeFrameOffsetStart
16
-	handshakeFrameOffsetIV             = handshakeFrameOffsetKey + handshakeFrameLenKey
17
-	handshakeFrameOffsetConnectionType = handshakeFrameOffsetIV + handshakeFrameLenIV
18
-	handshakeFrameOffsetDC             = handshakeFrameOffsetConnectionType + handshakeFrameLenConnectionType
19
-)
20
-
21
-// Connection-Type: Secure. We support only fake tls.
22
-var handshakeConnectionType = []byte{0xdd, 0xdd, 0xdd, 0xdd}
23
-
24
-// A structure of obfuscated2 handshake frame is following:
25
-//
26
-//	[frameOffsetFirst:frameOffsetKey:frameOffsetIV:frameOffsetMagic:frameOffsetDC:frameOffsetEnd].
27
-//
28
-//	- 8 bytes of noise
29
-//	- 32 bytes of AES Key
30
-//	- 16 bytes of AES IV
31
-//	- 4 bytes of 'connection type' - this has some setting like a connection type
32
-//	- 2 bytes of 'DC'. DC is little endian int16
33
-//	- 2 bytes of noise
34
-type handshakeFrame struct {
35
-	data [handshakeFrameLen]byte
36
-}
37
-
38
-func (h *handshakeFrame) dc() int {
39
-	idx := int16(h.data[handshakeFrameOffsetDC]) | int16(h.data[handshakeFrameOffsetDC+1])<<8 //nolint: lll // little endian for int16 is here
40
-
41
-	switch {
42
-	case idx > 0:
43
-		return int(idx)
44
-	case idx < 0:
45
-		return -int(idx)
46
-	default:
47
-		return DefaultDC
48
-	}
49
-}
50
-
51
-func (h *handshakeFrame) key() []byte {
52
-	return h.data[handshakeFrameOffsetKey:handshakeFrameOffsetIV]
53
-}
54
-
55
-func (h *handshakeFrame) iv() []byte {
56
-	return h.data[handshakeFrameOffsetIV:handshakeFrameOffsetConnectionType]
57
-}
58
-
59
-func (h *handshakeFrame) connectionType() []byte {
60
-	return h.data[handshakeFrameOffsetConnectionType:handshakeFrameOffsetDC]
61
-}
62
-
63
-func (h *handshakeFrame) invert() handshakeFrame {
64
-	copyFrame := *h
65
-
66
-	for i := range handshakeFrameLenKey + handshakeFrameLenIV {
67
-		copyFrame.data[handshakeFrameOffsetKey+i] = h.data[handshakeFrameOffsetConnectionType-1-i]
68
-	}
69
-
70
-	return copyFrame
71
-}

+ 0
- 73
mtglib/internal/obfuscated2/handshake_frame_internal_test.go 查看文件

@@ -1,73 +0,0 @@
1
-package obfuscated2
2
-
3
-import (
4
-	"crypto/rand"
5
-	"encoding/base64"
6
-	"strconv"
7
-	"testing"
8
-
9
-	"github.com/stretchr/testify/assert"
10
-	"github.com/stretchr/testify/suite"
11
-)
12
-
13
-type HandshakeFrameTestSuite struct {
14
-	suite.Suite
15
-}
16
-
17
-func (suite *HandshakeFrameTestSuite) Decode(value string) []byte {
18
-	v, err := base64.RawStdEncoding.DecodeString(value)
19
-	suite.NoError(err)
20
-
21
-	return v
22
-}
23
-
24
-func (suite *HandshakeFrameTestSuite) Encode(value []byte) string {
25
-	return base64.RawStdEncoding.EncodeToString(value)
26
-}
27
-
28
-func (suite *HandshakeFrameTestSuite) TestOk() {
29
-	hf := handshakeFrame{}
30
-	testFrame := suite.Decode(
31
-		"L9TmCzzxl9bPKODBpZeVM/qqNUxQ/axxBup1S2ymbIfUd6f7YSyzzM9EmTFv2/XzGqJGEHuj2zofmUGBLghu5g")
32
-	copy(hf.data[:], testFrame)
33
-
34
-	suite.Equal("zyjgwaWXlTP6qjVMUP2scQbqdUtspmyH1Hen+2Ess8w", suite.Encode(hf.key()))
35
-	suite.Equal("z0SZMW/b9fMaokYQe6PbOg", suite.Encode(hf.iv()))
36
-	suite.Equal("H5lBgQ", suite.Encode(hf.connectionType()))
37
-	suite.EqualValues(2094, hf.dc())
38
-
39
-	inverted := hf.invert()
40
-	suite.Equal("OtujexBGohrz9dtvMZlEz8yzLGH7p3fUh2ymbEt16gY", suite.Encode(inverted.key()))
41
-	suite.Equal("caz9UEw1qvozlZelweAozw", suite.Encode(inverted.iv()))
42
-	suite.Equal("H5lBgQ", suite.Encode(inverted.connectionType()))
43
-	suite.EqualValues(2094, inverted.dc())
44
-}
45
-
46
-func (suite *HandshakeFrameTestSuite) TestDC() {
47
-	testData := map[int16]int{
48
-		1:  1,
49
-		-1: 1,
50
-		0:  DefaultDC,
51
-	}
52
-
53
-	for k, v := range testData {
54
-		incoming := k
55
-		expected := v
56
-
57
-		suite.T().Run(strconv.Itoa(int(incoming)), func(t *testing.T) {
58
-			frame := handshakeFrame{}
59
-
60
-			rand.Read(frame.data[:]) //nolint: errcheck
61
-
62
-			frame.data[handshakeFrameOffsetDC] = byte(incoming)
63
-			frame.data[handshakeFrameOffsetDC+1] = byte(incoming >> 8)
64
-
65
-			assert.Equal(t, expected, frame.dc())
66
-		})
67
-	}
68
-}
69
-
70
-func TestHandshakeFrame(t *testing.T) {
71
-	t.Parallel()
72
-	suite.Run(t, &HandshakeFrameTestSuite{})
73
-}

+ 0
- 137
mtglib/internal/obfuscated2/init_test.go 查看文件

@@ -1,137 +0,0 @@
1
-package obfuscated2_test
2
-
3
-import (
4
-	"bytes"
5
-	"crypto/aes"
6
-	"crypto/cipher"
7
-	"encoding/base64"
8
-	"encoding/json"
9
-	"fmt"
10
-	"os"
11
-	"path/filepath"
12
-	"strings"
13
-	"testing"
14
-
15
-	"github.com/9seconds/mtg/v2/internal/testlib"
16
-	"github.com/9seconds/mtg/v2/mtglib/internal/obfuscated2"
17
-	"github.com/stretchr/testify/require"
18
-)
19
-
20
-type snapshotBytes struct {
21
-	data []byte
22
-}
23
-
24
-func (s snapshotBytes) MarshalText() ([]byte, error) {
25
-	if len(s.data) == 0 {
26
-		return nil, nil
27
-	}
28
-
29
-	return []byte(base64.RawStdEncoding.EncodeToString(s.data)), nil
30
-}
31
-
32
-func (s *snapshotBytes) UnmarshalText(data []byte) error {
33
-	val, err := base64.RawStdEncoding.DecodeString(string(data))
34
-	if err != nil {
35
-		return fmt.Errorf("cannot unmarshal %v: %w", len(val), err)
36
-	}
37
-
38
-	s.data = val
39
-
40
-	return nil
41
-}
42
-
43
-type Obfuscated2Snapshot struct {
44
-	Secret    snapshotBytes `json:"secret"`
45
-	Frame     snapshotBytes `json:"frame"`
46
-	DC        int16         `json:"dc"`
47
-	Encrypted struct {
48
-		Text   snapshotBytes `json:"text"`
49
-		Cipher snapshotBytes `json:"cipher"`
50
-	} `json:"encrypted"`
51
-	Decrypted struct {
52
-		Text   snapshotBytes `json:"text"`
53
-		Cipher snapshotBytes `json:"cipher"`
54
-	} `json:"decrypted"`
55
-}
56
-
57
-type SnapshotTestSuite struct {
58
-	snapshots map[string]*Obfuscated2Snapshot
59
-}
60
-
61
-type ServerHandshakeTestData struct {
62
-	connMock *testlib.EssentialsConnMock
63
-
64
-	proxyConn obfuscated2.Conn
65
-	encryptor cipher.Stream
66
-	decryptor cipher.Stream
67
-}
68
-
69
-func (suite *SnapshotTestSuite) IngestSnapshots(dirname, namePrefix string) error {
70
-	suite.snapshots = map[string]*Obfuscated2Snapshot{}
71
-
72
-	files, err := os.ReadDir(filepath.Join("testdata", dirname))
73
-	if err != nil {
74
-		return fmt.Errorf("cannot ingest snapshots: %w", err)
75
-	}
76
-
77
-	for _, v := range files {
78
-		if !strings.HasPrefix(v.Name(), namePrefix) {
79
-			continue
80
-		}
81
-
82
-		filename := filepath.Join("testdata", dirname, v.Name())
83
-
84
-		contents, err := os.ReadFile(filename)
85
-		if err != nil {
86
-			return fmt.Errorf("cannot read %s: %w", filename, err)
87
-		}
88
-
89
-		value := &Obfuscated2Snapshot{}
90
-
91
-		if err := json.Unmarshal(contents, value); err != nil {
92
-			return fmt.Errorf("cannot unmarshal %s: %w", filename, err)
93
-		}
94
-
95
-		suite.snapshots[v.Name()] = value
96
-	}
97
-
98
-	return nil
99
-}
100
-
101
-func NewServerHandshakeTestData(t *testing.T) ServerHandshakeTestData {
102
-	buf := &bytes.Buffer{}
103
-	connMock := &testlib.EssentialsConnMock{}
104
-
105
-	handshakeEnc, handshakeDec, err := obfuscated2.ServerHandshake(buf)
106
-	require.NoError(t, err)
107
-
108
-	serverEncrypted := buf.Bytes()
109
-	decBlock, _ := aes.NewCipher(serverEncrypted[8 : 8+32])
110
-	decryptor := cipher.NewCTR(decBlock, serverEncrypted[8+32:8+32+16])
111
-
112
-	serverDecrypted := make([]byte, len(serverEncrypted))
113
-	decryptor.XORKeyStream(serverDecrypted, serverEncrypted)
114
-
115
-	require.Equal(t, "3d3d3Q",
116
-		base64.RawStdEncoding.EncodeToString(serverDecrypted[8+32+16:8+32+16+4]))
117
-
118
-	serverEncryptedReverted := make([]byte, len(serverEncrypted))
119
-
120
-	for i := range 32 + 16 {
121
-		serverEncryptedReverted[8+i] = serverEncrypted[8+32+16-1-i]
122
-	}
123
-
124
-	encBlock, _ := aes.NewCipher(serverEncryptedReverted[8 : 8+32])
125
-	encryptor := cipher.NewCTR(encBlock, serverEncryptedReverted[8+32:8+32+16])
126
-
127
-	return ServerHandshakeTestData{
128
-		connMock: connMock,
129
-		proxyConn: obfuscated2.Conn{
130
-			Conn:      connMock,
131
-			Encryptor: handshakeEnc,
132
-			Decryptor: handshakeDec,
133
-		},
134
-		encryptor: encryptor,
135
-		decryptor: decryptor,
136
-	}
137
-}

+ 0
- 39
mtglib/internal/obfuscated2/pools.go 查看文件

@@ -1,39 +0,0 @@
1
-package obfuscated2
2
-
3
-import (
4
-	"bytes"
5
-	"crypto/sha256"
6
-	"hash"
7
-	"sync"
8
-)
9
-
10
-var (
11
-	sha256HasherPool = sync.Pool{
12
-		New: func() any {
13
-			return sha256.New()
14
-		},
15
-	}
16
-	bytesBufferPool = sync.Pool{
17
-		New: func() any {
18
-			return &bytes.Buffer{}
19
-		},
20
-	}
21
-)
22
-
23
-func acquireSha256Hasher() hash.Hash {
24
-	return sha256HasherPool.Get().(hash.Hash) //nolint: forcetypeassert
25
-}
26
-
27
-func releaseSha256Hasher(h hash.Hash) {
28
-	h.Reset()
29
-	sha256HasherPool.Put(h)
30
-}
31
-
32
-func acquireBytesBuffer() *bytes.Buffer {
33
-	return bytesBufferPool.Get().(*bytes.Buffer) //nolint: forcetypeassert
34
-}
35
-
36
-func releaseBytesBuffer(buf *bytes.Buffer) {
37
-	buf.Reset()
38
-	bytesBufferPool.Put(buf)
39
-}

+ 0
- 67
mtglib/internal/obfuscated2/server_handshake.go 查看文件

@@ -1,67 +0,0 @@
1
-package obfuscated2
2
-
3
-import (
4
-	"crypto/cipher"
5
-	"crypto/rand"
6
-	"encoding/binary"
7
-	"fmt"
8
-	"io"
9
-)
10
-
11
-type serverHandshakeFrame struct {
12
-	handshakeFrame
13
-}
14
-
15
-func (s *serverHandshakeFrame) decryptor() cipher.Stream {
16
-	invertedHandshake := s.invert()
17
-
18
-	return makeAesCtr(invertedHandshake.key(), invertedHandshake.iv())
19
-}
20
-
21
-func (s *serverHandshakeFrame) encryptor() cipher.Stream {
22
-	return makeAesCtr(s.key(), s.iv())
23
-}
24
-
25
-func ServerHandshake(writer io.Writer) (cipher.Stream, cipher.Stream, error) {
26
-	handshake := generateServerHanshakeFrame()
27
-	copyHandshake := handshake
28
-	encryptor := handshake.encryptor()
29
-	decryptor := handshake.decryptor()
30
-
31
-	encryptor.XORKeyStream(handshake.data[:], handshake.data[:])
32
-	copy(handshake.key(), copyHandshake.key())
33
-	copy(handshake.iv(), copyHandshake.iv())
34
-
35
-	if _, err := writer.Write(handshake.data[:]); err != nil {
36
-		return nil, nil, fmt.Errorf("cannot send a handshake frame to telegram: %w", err)
37
-	}
38
-
39
-	return encryptor, decryptor, nil
40
-}
41
-
42
-func generateServerHanshakeFrame() serverHandshakeFrame {
43
-	frame := serverHandshakeFrame{}
44
-
45
-	for {
46
-		if _, err := rand.Read(frame.data[:]); err != nil {
47
-			panic(err)
48
-		}
49
-
50
-		if frame.data[0] == 0xef { // taken from tg sources
51
-			continue
52
-		}
53
-
54
-		switch binary.LittleEndian.Uint32(frame.data[:4]) {
55
-		case 0x44414548, 0x54534f50, 0x20544547, 0x4954504f, 0xeeeeeeee: // taken from tg sources
56
-			continue
57
-		}
58
-
59
-		if frame.data[4]|frame.data[5]|frame.data[6]|frame.data[7] == 0 {
60
-			continue
61
-		}
62
-
63
-		copy(frame.connectionType(), handshakeConnectionType)
64
-
65
-		return frame
66
-	}
67
-}

+ 0
- 58
mtglib/internal/obfuscated2/server_handshake_fuzz_test.go 查看文件

@@ -1,58 +0,0 @@
1
-package obfuscated2_test
2
-
3
-import (
4
-	"testing"
5
-
6
-	"github.com/stretchr/testify/assert"
7
-	"github.com/stretchr/testify/mock"
8
-)
9
-
10
-func FuzzServerSend(f *testing.F) {
11
-	f.Add([]byte{1, 2, 3, 4, 5})
12
-
13
-	f.Fuzz(func(t *testing.T, data []byte) {
14
-		handshakeData := NewServerHandshakeTestData(t)
15
-
16
-		handshakeData.connMock.
17
-			On("Write", mock.Anything).
18
-			Return(len(data), nil).
19
-			Once().
20
-			Run(func(args mock.Arguments) {
21
-				message := make([]byte, len(data))
22
-				handshakeData.decryptor.XORKeyStream(message, args.Get(0).([]byte)) //nolint: forcetypeassert
23
-				assert.Equal(t, message, data)
24
-			})
25
-
26
-		n, err := handshakeData.proxyConn.Write(data)
27
-
28
-		assert.EqualValues(t, len(data), n)
29
-		assert.NoError(t, err)
30
-		handshakeData.connMock.AssertExpectations(t)
31
-	})
32
-}
33
-
34
-func FuzzServerReceive(f *testing.F) {
35
-	f.Add([]byte{1, 2, 3, 4, 5})
36
-
37
-	f.Fuzz(func(t *testing.T, data []byte) {
38
-		handshakeData := NewServerHandshakeTestData(t)
39
-		buffer := make([]byte, len(data))
40
-
41
-		handshakeData.connMock.
42
-			On("Read", mock.Anything).
43
-			Return(len(data), nil).
44
-			Once().
45
-			Run(func(args mock.Arguments) {
46
-				message := make([]byte, len(data))
47
-				handshakeData.encryptor.XORKeyStream(message, data)
48
-				copy(args.Get(0).([]byte), message) //nolint: forcetypeassert
49
-			})
50
-
51
-		n, err := handshakeData.proxyConn.Read(buffer)
52
-
53
-		assert.EqualValues(t, len(data), n)
54
-		assert.NoError(t, err)
55
-		assert.Equal(t, data, buffer)
56
-		handshakeData.connMock.AssertExpectations(t)
57
-	})
58
-}

+ 0
- 65
mtglib/internal/obfuscated2/server_handshake_test.go 查看文件

@@ -1,65 +0,0 @@
1
-package obfuscated2_test
2
-
3
-import (
4
-	"testing"
5
-
6
-	"github.com/stretchr/testify/mock"
7
-	"github.com/stretchr/testify/suite"
8
-)
9
-
10
-type ServerHandshakeTestSuite struct {
11
-	suite.Suite
12
-
13
-	data ServerHandshakeTestData
14
-}
15
-
16
-func (suite *ServerHandshakeTestSuite) SetupTest() {
17
-	suite.data = NewServerHandshakeTestData(suite.T())
18
-}
19
-
20
-func (suite *ServerHandshakeTestSuite) TearDownTest() {
21
-	suite.data.connMock.AssertExpectations(suite.T())
22
-}
23
-
24
-func (suite *ServerHandshakeTestSuite) TestSendToTelegram() {
25
-	messageToTelegram := []byte{10, 11, 12, 13, 14, 'a'}
26
-
27
-	suite.data.connMock.
28
-		On("Write", mock.Anything).
29
-		Return(len(messageToTelegram), nil).
30
-		Once().
31
-		Run(func(args mock.Arguments) {
32
-			message := make([]byte, len(messageToTelegram))
33
-			suite.data.decryptor.XORKeyStream(message, args.Get(0).([]byte)) //nolint: forcetypeassert
34
-			suite.Equal(messageToTelegram, message)
35
-		})
36
-
37
-	n, err := suite.data.proxyConn.Write(messageToTelegram)
38
-	suite.EqualValues(len(messageToTelegram), n)
39
-	suite.NoError(err)
40
-}
41
-
42
-func (suite *ServerHandshakeTestSuite) TestRecieveFromTelegram() {
43
-	messageFromTelegram := []byte{10, 11, 12, 13, 14, 'a'}
44
-	buffer := make([]byte, len(messageFromTelegram))
45
-
46
-	suite.data.connMock.
47
-		On("Read", mock.Anything).
48
-		Return(len(messageFromTelegram), nil).
49
-		Once().
50
-		Run(func(args mock.Arguments) {
51
-			message := make([]byte, len(messageFromTelegram))
52
-			suite.data.encryptor.XORKeyStream(message, messageFromTelegram)
53
-			copy(args.Get(0).([]byte), message) //nolint: forcetypeassert
54
-		})
55
-
56
-	n, err := suite.data.proxyConn.Read(buffer)
57
-	suite.EqualValues(len(messageFromTelegram), n)
58
-	suite.NoError(err)
59
-	suite.Equal(messageFromTelegram, buffer)
60
-}
61
-
62
-func TestServerHandshake(t *testing.T) {
63
-	t.Parallel()
64
-	suite.Run(t, &ServerHandshakeTestSuite{})
65
-}

+ 0
- 15
mtglib/internal/obfuscated2/utils.go 查看文件

@@ -1,15 +0,0 @@
1
-package obfuscated2
2
-
3
-import (
4
-	"crypto/aes"
5
-	"crypto/cipher"
6
-)
7
-
8
-func makeAesCtr(key, iv []byte) cipher.Stream {
9
-	block, err := aes.NewCipher(key)
10
-	if err != nil {
11
-		panic(err)
12
-	}
13
-
14
-	return cipher.NewCTR(block, iv)
15
-}

+ 34
- 0
mtglib/internal/obfuscation/conn.go 查看文件

@@ -0,0 +1,34 @@
1
+package obfuscation
2
+
3
+import (
4
+	"crypto/cipher"
5
+
6
+	"github.com/9seconds/mtg/v2/essentials"
7
+)
8
+
9
+type conn struct {
10
+	essentials.Conn
11
+
12
+	sendCipher cipher.Stream
13
+	recvCipher cipher.Stream
14
+}
15
+
16
+func (c conn) Read(p []byte) (int, error) {
17
+	n, err := c.Conn.Read(p)
18
+	if err != nil {
19
+		return n, err
20
+	}
21
+
22
+	c.recvCipher.XORKeyStream(p, p[:n])
23
+
24
+	return n, nil
25
+}
26
+
27
+func (c conn) Write(p []byte) (int, error) {
28
+	// yes, this is a bit violent and goes against a contract in io.Writer
29
+	// but we do it to avoid creating a new buffer just to perform this
30
+	// encryption.
31
+	c.sendCipher.XORKeyStream(p, p)
32
+
33
+	return c.Conn.Write(p)
34
+}

+ 102
- 0
mtglib/internal/obfuscation/conn_test.go 查看文件

@@ -0,0 +1,102 @@
1
+package obfuscation
2
+
3
+import (
4
+	"crypto/aes"
5
+	"crypto/cipher"
6
+	"encoding/hex"
7
+	"testing"
8
+
9
+	"github.com/9seconds/mtg/v2/essentials"
10
+	"github.com/9seconds/mtg/v2/internal/testlib"
11
+	"github.com/stretchr/testify/assert"
12
+	"github.com/stretchr/testify/mock"
13
+	"github.com/stretchr/testify/suite"
14
+)
15
+
16
+type ConnTestSuite struct {
17
+	suite.Suite
18
+
19
+	secret []byte
20
+}
21
+
22
+func (s *ConnTestSuite) SetupSuite() {
23
+	secret := [32]byte{}
24
+	s.secret = secret[:]
25
+}
26
+
27
+func (s *ConnTestSuite) TestRead() {
28
+	testData := map[string]string{
29
+		"data1": "b8f4b41993",
30
+		"":      "",
31
+		"___":   "83ca9f",
32
+	}
33
+
34
+	for incoming, outgoing := range testData {
35
+		s.T().Run(incoming, func(t *testing.T) {
36
+			connMock := &testlib.EssentialsConnMock{}
37
+			testConn := s.makeConn(connMock)
38
+			data := make([]byte, len(incoming))
39
+
40
+			connMock.On("Read", make([]byte, len(incoming))).Return(len(incoming), nil).Run(func(args mock.Arguments) {
41
+				arg := args.Get(0).([]byte)
42
+				copy(arg, []byte(incoming))
43
+			})
44
+
45
+			n, err := testConn.Read(data)
46
+
47
+			assert.Equal(t, len(data), n)
48
+			assert.NoError(t, err)
49
+			assert.Equal(t, outgoing, hex.EncodeToString(data))
50
+
51
+			connMock.AssertExpectations(t)
52
+		})
53
+	}
54
+}
55
+
56
+func (s *ConnTestSuite) TestWrite() {
57
+	testData := map[string]string{
58
+		"b8f4b41993": "data1",
59
+		"":           "",
60
+		"83ca9f":     "___",
61
+	}
62
+
63
+	for incoming, outgoing := range testData {
64
+		s.T().Run(incoming, func(t *testing.T) {
65
+			connMock := &testlib.EssentialsConnMock{}
66
+			testConn := s.makeConn(connMock)
67
+			toWrite, _ := hex.DecodeString(incoming)
68
+			data := make([]byte, len(toWrite))
69
+
70
+			connMock.On("Write", []byte(outgoing)).Return(len(toWrite), nil)
71
+
72
+			n, err := testConn.Write(toWrite)
73
+			assert.Equal(t, len(data), n)
74
+			assert.NoError(t, err)
75
+
76
+			connMock.AssertExpectations(t)
77
+		})
78
+	}
79
+}
80
+
81
+func (s *ConnTestSuite) makeConn(rawConn *testlib.EssentialsConnMock) essentials.Conn {
82
+	rblock, err := aes.NewCipher(s.secret)
83
+	if err != nil {
84
+		panic(err)
85
+	}
86
+
87
+	wblock, err := aes.NewCipher(s.secret)
88
+	if err != nil {
89
+		panic(err)
90
+	}
91
+
92
+	return conn{
93
+		Conn:       rawConn,
94
+		sendCipher: cipher.NewCTR(wblock, s.secret[:aes.BlockSize]),
95
+		recvCipher: cipher.NewCTR(rblock, s.secret[:aes.BlockSize]),
96
+	}
97
+}
98
+
99
+func TestConn(t *testing.T) {
100
+	t.Parallel()
101
+	suite.Run(t, &ConnTestSuite{})
102
+}

+ 111
- 0
mtglib/internal/obfuscation/handshake_frame.go 查看文件

@@ -0,0 +1,111 @@
1
+package obfuscation
2
+
3
+import (
4
+	"crypto/rand"
5
+	"encoding/binary"
6
+	"slices"
7
+)
8
+
9
+// https://core.telegram.org/mtproto/mtproto-transports#transport-obfuscation
10
+const (
11
+	// default DC is nothing is selected
12
+	defaultDC = 2
13
+
14
+	// the length of the handshake frame. Always 64 bytes
15
+	hfLen = 64
16
+
17
+	hfLenKey            = 32
18
+	hfLenIV             = 16
19
+	hfLenConnectionType = 4
20
+
21
+	// A structure of obfuscated handshake frame is following:
22
+	//
23
+	//	[frameOffsetFirst:frameOffsetKey:frameOffsetIV:frameOffsetMagic:frameOffsetDC:frameOffsetEnd].
24
+	//
25
+	//	- 8 bytes of noise
26
+	//	- 32 bytes of AES Key
27
+	//	- 16 bytes of AES IV
28
+	//	- 4 bytes of 'connection type' - this has some setting like a connection type
29
+	//	- 2 bytes of 'DC'. DC is little endian int16
30
+	//	- 2 bytes of noise
31
+	hfOffsetKey            = 8
32
+	hfOffsetIV             = hfOffsetKey + hfLenKey
33
+	hfOffsetConnectionType = hfOffsetIV + hfLenIV
34
+	hfOffsetDC             = hfOffsetConnectionType + hfLenConnectionType
35
+)
36
+
37
+// Connection-Type: Secure. We support only fake tls.
38
+var hfConnectionType = [hfLenConnectionType]byte{0xdd, 0xdd, 0xdd, 0xdd}
39
+
40
+type handshakeFrame struct {
41
+	data [hfLen]byte
42
+}
43
+
44
+func (h *handshakeFrame) key() []byte {
45
+	return h.data[hfOffsetKey : hfOffsetKey+hfLenKey]
46
+}
47
+
48
+func (h *handshakeFrame) iv() []byte {
49
+	return h.data[hfOffsetIV : hfOffsetIV+hfLenIV]
50
+}
51
+
52
+func (h *handshakeFrame) connectionType() []byte {
53
+	return h.data[hfOffsetConnectionType : hfOffsetConnectionType+hfLenConnectionType]
54
+}
55
+
56
+func (h *handshakeFrame) dcSlice() []byte {
57
+	return h.data[hfOffsetDC : hfOffsetDC+2]
58
+}
59
+
60
+func (h *handshakeFrame) dc() int {
61
+	idx := int16(binary.LittleEndian.Uint16(h.dcSlice()))
62
+
63
+	switch {
64
+	case idx > 0:
65
+		return int(idx)
66
+	case idx < 0:
67
+		return -int(idx)
68
+	}
69
+
70
+	return defaultDC
71
+}
72
+
73
+func (h *handshakeFrame) revert() {
74
+	slices.Reverse(h.data[hfOffsetKey:hfOffsetConnectionType])
75
+}
76
+
77
+func generateHandshake(dc int) handshakeFrame {
78
+	frame := handshakeFrame{}
79
+
80
+	for {
81
+		if _, err := rand.Read(frame.data[:]); err != nil {
82
+			panic(err)
83
+		}
84
+
85
+		// https://github.com/tdlib/td/blob/master/td/mtproto/TcpTransport.cpp#L157-L158.
86
+		if frame.data[0] == 0xef { // abridged header
87
+			// https://core.telegram.org/mtproto/mtproto-transports#abridged
88
+			continue
89
+		}
90
+
91
+		switch binary.LittleEndian.Uint32(frame.data[:4]) {
92
+		case 0x44414548, // HEAD
93
+			0x54534f50, // POST
94
+			0x20544547, // GET
95
+			0x4954504f, // OPTI
96
+			0x02010316, // ????
97
+			0xdddddddd, // PaddedIntermediate header
98
+			0xeeeeeeee: // Intermediate header
99
+			continue
100
+		}
101
+
102
+		if frame.data[4]|frame.data[5]|frame.data[6]|frame.data[7] == 0 {
103
+			continue
104
+		}
105
+
106
+		copy(frame.connectionType(), hfConnectionType[:])
107
+		binary.LittleEndian.PutUint16(frame.dcSlice(), uint16(dc))
108
+
109
+		return frame
110
+	}
111
+}

mtglib/internal/obfuscated2/server_handshake_fuzz_internal_test.go → mtglib/internal/obfuscation/handshake_frame_fuzz_test.go 查看文件

@@ -1,4 +1,4 @@
1
-package obfuscated2
1
+package obfuscation
2 2
 
3 3
 import (
4 4
 	"encoding/binary"
@@ -7,9 +7,9 @@ import (
7 7
 	"github.com/stretchr/testify/assert"
8 8
 )
9 9
 
10
-func FuzzServerGenerateHandshakeFrame(f *testing.F) {
11
-	f.Fuzz(func(t *testing.T, arg int) {
12
-		frame := generateServerHanshakeFrame()
10
+func FuzzGenerateHandshakeFrame(f *testing.F) {
11
+	f.Fuzz(func(t *testing.T, arg int16) {
12
+		frame := generateHandshake(int(arg))
13 13
 
14 14
 		assert.NotEqualValues(t, 0xef, frame.data[0])
15 15
 
@@ -18,13 +18,23 @@ func FuzzServerGenerateHandshakeFrame(f *testing.F) {
18 18
 		assert.NotEqualValues(t, 0x54534f50, firstBytes)
19 19
 		assert.NotEqualValues(t, 0x20544547, firstBytes)
20 20
 		assert.NotEqualValues(t, 0x4954504f, firstBytes)
21
+		assert.NotEqualValues(t, 0x02010316, firstBytes)
21 22
 		assert.NotEqualValues(t, 0xeeeeeeee, firstBytes)
23
+		assert.NotEqualValues(t, 0xdddddddd, firstBytes)
22 24
 
23 25
 		assert.NotEqualValues(
24 26
 			t,
25 27
 			0,
26 28
 			frame.data[4]|frame.data[5]|frame.data[6]|frame.data[7])
27 29
 
28
-		assert.Equal(t, handshakeConnectionType, frame.connectionType())
30
+		assert.Equal(t, hfConnectionType[:], frame.connectionType())
31
+
32
+		if arg < 0 {
33
+			arg = -arg
34
+		} else if arg == 0 {
35
+			arg = defaultDC
36
+		}
37
+
38
+		assert.EqualValues(t, arg, frame.dc())
29 39
 	})
30 40
 }

+ 66
- 0
mtglib/internal/obfuscation/handshake_frame_test.go 查看文件

@@ -0,0 +1,66 @@
1
+package obfuscation
2
+
3
+import (
4
+	"testing"
5
+
6
+	"github.com/stretchr/testify/suite"
7
+)
8
+
9
+type HandshakeFrameTestSuite struct {
10
+	suite.Suite
11
+
12
+	frame    handshakeFrame
13
+	reverted handshakeFrame
14
+}
15
+
16
+func (h *HandshakeFrameTestSuite) SetupSuite() {
17
+	for i := range hfLen {
18
+		h.frame.data[i] = byte(i + 1)
19
+		h.reverted.data[i] = byte(hfLen - i)
20
+	}
21
+}
22
+
23
+func (h *HandshakeFrameTestSuite) TestKey() {
24
+	key := h.frame.key()
25
+	h.EqualValues(8+1, key[0])
26
+	h.EqualValues(8+hfLenKey, key[len(key)-1])
27
+	h.Len(key, hfLenKey)
28
+}
29
+
30
+func (h *HandshakeFrameTestSuite) TestIV() {
31
+	iv := h.frame.iv()
32
+	h.EqualValues(40+1, iv[0])
33
+	h.EqualValues(40+hfLenIV, iv[len(iv)-1])
34
+	h.Len(iv, hfLenIV)
35
+}
36
+
37
+func (h *HandshakeFrameTestSuite) TestConnectionType() {
38
+	connectionType := h.frame.connectionType()
39
+	h.EqualValues(56+1, connectionType[0])
40
+	h.EqualValues(56+hfLenConnectionType, connectionType[len(connectionType)-1])
41
+	h.Len(connectionType, hfLenConnectionType)
42
+}
43
+
44
+func (h *HandshakeFrameTestSuite) TestDCSlice() {
45
+	dcSlice := h.frame.dcSlice()
46
+	h.EqualValues(61, dcSlice[0])
47
+	h.EqualValues(61+1, dcSlice[1])
48
+	h.Len(dcSlice, 2)
49
+}
50
+
51
+func (h *HandshakeFrameTestSuite) TestDC() {
52
+	h.Equal(15933, h.frame.dc())
53
+}
54
+
55
+func (h *HandshakeFrameTestSuite) TestRevert() {
56
+	fr := h.frame
57
+	fr.revert()
58
+
59
+	h.Equal(h.reverted.key(), fr.key())
60
+	h.Equal(h.reverted.iv(), fr.iv())
61
+}
62
+
63
+func TestHandshakeFrame(t *testing.T) {
64
+	t.Parallel()
65
+	suite.Run(t, &HandshakeFrameTestSuite{})
66
+}

+ 79
- 0
mtglib/internal/obfuscation/init_test.go 查看文件

@@ -0,0 +1,79 @@
1
+package obfuscation_test
2
+
3
+import (
4
+	"encoding/base64"
5
+	"encoding/json"
6
+	"fmt"
7
+	"os"
8
+	"path/filepath"
9
+	"strings"
10
+
11
+	"github.com/stretchr/testify/require"
12
+	"github.com/stretchr/testify/suite"
13
+)
14
+
15
+type snapshotBytes struct {
16
+	data []byte
17
+}
18
+
19
+func (s snapshotBytes) MarshalText() ([]byte, error) {
20
+	if len(s.data) == 0 {
21
+		return nil, nil
22
+	}
23
+
24
+	return []byte(base64.RawStdEncoding.EncodeToString(s.data)), nil
25
+}
26
+
27
+func (s *snapshotBytes) UnmarshalText(data []byte) error {
28
+	val, err := base64.RawStdEncoding.DecodeString(string(data))
29
+	if err != nil {
30
+		return fmt.Errorf("cannot unmarshal %v: %w", len(val), err)
31
+	}
32
+
33
+	s.data = val
34
+
35
+	return nil
36
+}
37
+
38
+type ObfuscatedSnapshot struct {
39
+	Secret    snapshotBytes `json:"secret"`
40
+	Frame     snapshotBytes `json:"frame"`
41
+	DC        int16         `json:"dc"`
42
+	Encrypted struct {
43
+		Text   snapshotBytes `json:"text"`
44
+		Cipher snapshotBytes `json:"cipher"`
45
+	} `json:"encrypted"`
46
+	Decrypted struct {
47
+		Text   snapshotBytes `json:"text"`
48
+		Cipher snapshotBytes `json:"cipher"`
49
+	} `json:"decrypted"`
50
+}
51
+
52
+type SnapshotTestSuite struct {
53
+	suite.Suite
54
+
55
+	snapshots map[string]*ObfuscatedSnapshot
56
+}
57
+
58
+func (s *SnapshotTestSuite) Setup(dirname, namePrefix string) {
59
+	s.snapshots = make(map[string]*ObfuscatedSnapshot)
60
+
61
+	files, err := os.ReadDir("testdata")
62
+	require.NoError(s.T(), err)
63
+
64
+	for _, v := range files {
65
+		if !strings.HasPrefix(v.Name(), namePrefix) {
66
+			continue
67
+		}
68
+
69
+		filename := filepath.Join("testdata", v.Name())
70
+
71
+		contents, err := os.ReadFile(filename)
72
+		require.NoError(s.T(), err)
73
+
74
+		value := &ObfuscatedSnapshot{}
75
+		require.NoError(s.T(), json.Unmarshal(contents, value))
76
+
77
+		s.snapshots[v.Name()] = value
78
+	}
79
+}

+ 87
- 0
mtglib/internal/obfuscation/obfuscator.go 查看文件

@@ -0,0 +1,87 @@
1
+package obfuscation
2
+
3
+import (
4
+	"crypto/aes"
5
+	"crypto/cipher"
6
+	"crypto/sha256"
7
+	"crypto/subtle"
8
+	"encoding/hex"
9
+	"fmt"
10
+	"hash"
11
+	"io"
12
+
13
+	"github.com/9seconds/mtg/v2/essentials"
14
+)
15
+
16
+type Obfuscator struct {
17
+	Secret []byte
18
+}
19
+
20
+func (o Obfuscator) ReadHandshake(r essentials.Conn) (int, essentials.Conn, error) {
21
+	frame := handshakeFrame{}
22
+
23
+	if _, err := io.ReadFull(r, frame.data[:]); err != nil {
24
+		return 0, nil, fmt.Errorf("cannot read frame: %w", err)
25
+	}
26
+
27
+	hasher := sha256.New()
28
+	recvCipher := o.getCipher(&frame, hasher)
29
+
30
+	frame.revert()
31
+	hasher.Reset()
32
+	sendCipher := o.getCipher(&frame, hasher)
33
+
34
+	recvCipher.XORKeyStream(frame.data[:], frame.data[:])
35
+
36
+	if val := frame.connectionType(); subtle.ConstantTimeCompare(val, hfConnectionType[:]) != 1 {
37
+		return 0, nil, fmt.Errorf("unsupported connection type: %s", hex.EncodeToString(val))
38
+	}
39
+
40
+	cn := conn{
41
+		Conn:       r,
42
+		recvCipher: recvCipher,
43
+		sendCipher: sendCipher,
44
+	}
45
+
46
+	return frame.dc(), cn, nil
47
+}
48
+
49
+func (o Obfuscator) SendHandshake(w essentials.Conn, dc int) (essentials.Conn, error) {
50
+	frame := generateHandshake(dc)
51
+	copyFrame := frame
52
+	hasher := sha256.New()
53
+
54
+	sendCipher := o.getCipher(&frame, hasher)
55
+
56
+	frame.revert()
57
+	hasher.Reset()
58
+	recvCipher := o.getCipher(&frame, hasher)
59
+
60
+	sendCipher.XORKeyStream(frame.data[:], frame.data[:])
61
+	copy(frame.key(), copyFrame.key())
62
+	copy(frame.iv(), copyFrame.iv())
63
+
64
+	if _, err := w.Write(frame.data[:]); err != nil {
65
+		return nil, fmt.Errorf("cannot send a handshake: %w", err)
66
+	}
67
+
68
+	return conn{
69
+		Conn:       w,
70
+		recvCipher: recvCipher,
71
+		sendCipher: sendCipher,
72
+	}, nil
73
+}
74
+
75
+func (o Obfuscator) getCipher(f *handshakeFrame, hasher hash.Hash) cipher.Stream {
76
+	blockKey := f.key()
77
+
78
+	if o.Secret != nil {
79
+		hasher.Write(blockKey)
80
+		hasher.Write(o.Secret)
81
+		blockKey = hasher.Sum(nil)
82
+	}
83
+
84
+	block, _ := aes.NewCipher(blockKey)
85
+
86
+	return cipher.NewCTR(block, f.iv())
87
+}

+ 63
- 0
mtglib/internal/obfuscation/obfuscator_fuzz_test.go 查看文件

@@ -0,0 +1,63 @@
1
+package obfuscation_test
2
+
3
+import (
4
+	"bytes"
5
+	"testing"
6
+
7
+	"github.com/9seconds/mtg/v2/internal/testlib"
8
+	"github.com/9seconds/mtg/v2/mtglib"
9
+	"github.com/9seconds/mtg/v2/mtglib/internal/obfuscation"
10
+	"github.com/stretchr/testify/assert"
11
+	"github.com/stretchr/testify/mock"
12
+)
13
+
14
+func FuzzClientServerHandshakes(f *testing.F) {
15
+	f.Add(int16(1), make([]byte, mtglib.SecretKeyLength))
16
+
17
+	f.Fuzz(func(t *testing.T, dc int16, data []byte) {
18
+		if dc <= 0 {
19
+			dc = 1
20
+		}
21
+
22
+		client := obfuscation.Obfuscator{
23
+			Secret: data,
24
+		}
25
+		server := client
26
+
27
+		clientToServerBuf := &bytes.Buffer{}
28
+
29
+		writeConnMock := &testlib.EssentialsConnMock{}
30
+		writeConnMock.
31
+			On("Write", mock.AnythingOfType("[]uint8")).
32
+			Once().
33
+			Return(64, nil).
34
+			Run(func(args mock.Arguments) {
35
+				arg := args.Get(0).([]byte)
36
+				n, err := clientToServerBuf.Write(arg)
37
+				assert.Equal(t, 64, n)
38
+				assert.NoError(t, err)
39
+			})
40
+
41
+		readConnMock := &testlib.EssentialsConnMock{}
42
+		readConnMock.
43
+			On("Read", mock.AnythingOfType("[]uint8")).
44
+			Once().
45
+			Return(64, nil).
46
+			Run(func(args mock.Arguments) {
47
+				arg := args.Get(0).([]byte)
48
+				n, err := clientToServerBuf.Read(arg)
49
+				assert.Equal(t, 64, n)
50
+				assert.NoError(t, err)
51
+			})
52
+
53
+		_, err := client.SendHandshake(writeConnMock, int(dc))
54
+		assert.NoError(t, err)
55
+
56
+		readDc, _, err := server.ReadHandshake(readConnMock)
57
+		assert.NoError(t, err)
58
+		assert.EqualValues(t, dc, readDc)
59
+
60
+		writeConnMock.AssertExpectations(t)
61
+		readConnMock.AssertExpectations(t)
62
+	})
63
+}

+ 94
- 0
mtglib/internal/obfuscation/obfuscator_test.go 查看文件

@@ -0,0 +1,94 @@
1
+package obfuscation_test
2
+
3
+import (
4
+	"bytes"
5
+	"testing"
6
+
7
+	"github.com/9seconds/mtg/v2/internal/testlib"
8
+	"github.com/9seconds/mtg/v2/mtglib"
9
+	"github.com/9seconds/mtg/v2/mtglib/internal/obfuscation"
10
+	"github.com/stretchr/testify/assert"
11
+	"github.com/stretchr/testify/mock"
12
+	"github.com/stretchr/testify/require"
13
+	"github.com/stretchr/testify/suite"
14
+)
15
+
16
+type ObfuscatorTestSuite struct {
17
+	SnapshotTestSuite
18
+
19
+	secret *mtglib.Secret
20
+}
21
+
22
+func (s *ObfuscatorTestSuite) SetupSuite() {
23
+	s.Setup("", "client-handshake")
24
+
25
+	secret := mtglib.GenerateSecret("hostname.com")
26
+	s.secret = &secret
27
+}
28
+
29
+func (s *ObfuscatorTestSuite) TestSnapshot() {
30
+	for name, snapshot := range s.snapshots {
31
+		s.T().Run(name, func(t *testing.T) {
32
+			obfs := obfuscation.Obfuscator{
33
+				Secret: snapshot.Secret.data,
34
+			}
35
+
36
+			connMock := &testlib.EssentialsConnMock{}
37
+
38
+			connMockReadBuffer := &bytes.Buffer{}
39
+			connMockReadBuffer.Write(snapshot.Frame.data)
40
+			connMockReadBuffer.Write(snapshot.Decrypted.Cipher.data)
41
+
42
+			connMockWriteBuffer := &bytes.Buffer{}
43
+
44
+			connMock.
45
+				On("Read", mock.AnythingOfType("[]uint8")).
46
+				Return(64, nil).
47
+				Run(func(args mock.Arguments) {
48
+					arr := args.Get(0).([]byte)
49
+					_, err := connMockReadBuffer.Read(arr)
50
+					require.NoError(t, err)
51
+				})
52
+
53
+			dc, cn, err := obfs.ReadHandshake(connMock)
54
+			assert.EqualValues(t, 2, dc)
55
+			assert.NoError(t, err)
56
+
57
+			connMock.Calls = []mock.Call{}
58
+			connMock.ExpectedCalls = []*mock.Call{}
59
+
60
+			connMock.
61
+				On("Read", mock.AnythingOfType("[]uint8")).
62
+				Return(len(snapshot.Decrypted.Cipher.data), nil).
63
+				Run(func(args mock.Arguments) {
64
+					arr := args.Get(0).([]byte)
65
+					_, err := connMockReadBuffer.Read(arr)
66
+					require.NoError(t, err)
67
+				})
68
+			connMock.
69
+				On("Write", mock.AnythingOfType("[]uint8")).
70
+				Return(len(snapshot.Encrypted.Cipher.data), nil).
71
+				Run(func(args mock.Arguments) {
72
+					arr := args.Get(0).([]byte)
73
+					_, err := connMockWriteBuffer.Write(arr)
74
+					require.NoError(t, err)
75
+				})
76
+
77
+			readBuf := make([]byte, len(snapshot.Decrypted.Text.data))
78
+			_, err = cn.Read(readBuf)
79
+			assert.NoError(t, err)
80
+			assert.Equal(t, readBuf, snapshot.Decrypted.Text.data)
81
+
82
+			_, err = cn.Write(snapshot.Encrypted.Text.data)
83
+			assert.NoError(t, err)
84
+			assert.Equal(t, connMockWriteBuffer.Bytes(), snapshot.Encrypted.Cipher.data)
85
+
86
+			connMock.AssertExpectations(t)
87
+		})
88
+	}
89
+}
90
+
91
+func TestObfuscator(t *testing.T) {
92
+	t.Parallel()
93
+	suite.Run(t, &ObfuscatorTestSuite{})
94
+}

mtglib/internal/obfuscated2/testdata/client-handshake-snapshot-4529d55776e2d427.json → mtglib/internal/obfuscation/testdata/client-handshake-snapshot-4529d55776e2d427.json 查看文件


mtglib/internal/obfuscated2/testdata/client-handshake-snapshot-585c944d672f60a2.json → mtglib/internal/obfuscation/testdata/client-handshake-snapshot-585c944d672f60a2.json 查看文件


+ 0
- 19
mtglib/internal/relay/pools.go 查看文件

@@ -1,19 +0,0 @@
1
-package relay
2
-
3
-import "sync"
4
-
5
-var copyBufferPool = sync.Pool{
6
-	New: func() any {
7
-		rv := make([]byte, copyBufferSize)
8
-
9
-		return &rv
10
-	},
11
-}
12
-
13
-func acquireCopyBuffer() *[]byte {
14
-	return copyBufferPool.Get().(*[]byte) //nolint: forcetypeassert
15
-}
16
-
17
-func releaseCopyBuffer(buf *[]byte) {
18
-	copyBufferPool.Put(buf)
19
-}

+ 3
- 4
mtglib/internal/relay/relay.go 查看文件

@@ -35,13 +35,12 @@ func Relay(ctx context.Context, log Logger, telegramConn, clientConn essentials.
35 35
 }
36 36
 
37 37
 func pump(log Logger, src, dst essentials.Conn, direction string) {
38
+	var buf [copyBufferSize]byte
39
+
38 40
 	defer src.CloseRead()  //nolint: errcheck
39 41
 	defer dst.CloseWrite() //nolint: errcheck
40 42
 
41
-	copyBuffer := acquireCopyBuffer()
42
-	defer releaseCopyBuffer(copyBuffer)
43
-
44
-	n, err := io.CopyBuffer(src, dst, *copyBuffer)
43
+	n, err := io.CopyBuffer(src, dst, buf[:])
45 44
 
46 45
 	switch {
47 46
 	case err == nil:

+ 61
- 34
mtglib/proxy.go 查看文件

@@ -13,7 +13,7 @@ import (
13 13
 	"github.com/9seconds/mtg/v2/mtglib/internal/dc"
14 14
 	"github.com/9seconds/mtg/v2/mtglib/internal/faketls"
15 15
 	"github.com/9seconds/mtg/v2/mtglib/internal/faketls/record"
16
-	"github.com/9seconds/mtg/v2/mtglib/internal/obfuscated2"
16
+	"github.com/9seconds/mtg/v2/mtglib/internal/obfuscation"
17 17
 	"github.com/9seconds/mtg/v2/mtglib/internal/relay"
18 18
 	"github.com/panjf2000/ants/v2"
19 19
 )
@@ -24,11 +24,15 @@ type Proxy struct {
24 24
 	ctxCancel       context.CancelFunc
25 25
 	streamWaitGroup sync.WaitGroup
26 26
 
27
-	allowFallbackOnUnknownDC bool
28
-	tolerateTimeSkewness     time.Duration
29
-	domainFrontingPort       int
30
-	workerPool               *ants.PoolWithFunc
31
-	telegram                 *dc.Telegram
27
+	allowFallbackOnUnknownDC    bool
28
+	tolerateTimeSkewness        time.Duration
29
+	domainFrontingPort          int
30
+	domainFrontingIP            string
31
+	domainFrontingProxyProtocol bool
32
+	workerPool                  *ants.PoolWithFunc
33
+	telegram                    *dc.Telegram
34
+	configUpdater               *dc.PublicConfigUpdater
35
+	clientObfuscatror           obfuscation.Obfuscator
32 36
 
33 37
 	secret          Secret
34 38
 	network         Network
@@ -40,8 +44,14 @@ type Proxy struct {
40 44
 }
41 45
 
42 46
 // DomainFrontingAddress returns a host:port pair for a fronting domain.
47
+// If DomainFrontingIP is set, it is used instead of resolving the hostname.
43 48
 func (p *Proxy) DomainFrontingAddress() string {
44
-	return net.JoinHostPort(p.secret.Host, strconv.Itoa(p.domainFrontingPort))
49
+	host := p.secret.Host
50
+	if p.domainFrontingIP != "" {
51
+		host = p.domainFrontingIP
52
+	}
53
+
54
+	return net.JoinHostPort(host, strconv.Itoa(p.domainFrontingPort))
45 55
 }
46 56
 
47 57
 // ServeConn serves a connection. We do not check IP blocklist and concurrency
@@ -70,8 +80,8 @@ func (p *Proxy) ServeConn(conn essentials.Conn) {
70 80
 		return
71 81
 	}
72 82
 
73
-	if err := p.doObfuscated2Handshake(ctx); err != nil {
74
-		p.logger.InfoError("obfuscated2 handshake is failed", err)
83
+	if err := p.doObfuscatedHandshake(ctx); err != nil {
84
+		p.logger.InfoError("obfuscated handshake is failed", err)
75 85
 
76 86
 		return
77 87
 	}
@@ -144,6 +154,7 @@ func (p *Proxy) Shutdown() {
144 154
 	p.ctxCancel()
145 155
 	p.streamWaitGroup.Wait()
146 156
 	p.workerPool.Release()
157
+	p.configUpdater.Wait()
147 158
 
148 159
 	p.allowlist.Shutdown()
149 160
 	p.blocklist.Shutdown()
@@ -201,19 +212,15 @@ func (p *Proxy) doFakeTLSHandshake(ctx *streamContext) bool {
201 212
 	return true
202 213
 }
203 214
 
204
-func (p *Proxy) doObfuscated2Handshake(ctx *streamContext) error {
205
-	dc, encryptor, decryptor, err := obfuscated2.ClientHandshake(p.secret.Key[:], ctx.clientConn)
215
+func (p *Proxy) doObfuscatedHandshake(ctx *streamContext) error {
216
+	dc, conn, err := p.clientObfuscatror.ReadHandshake(ctx.clientConn)
206 217
 	if err != nil {
207 218
 		return fmt.Errorf("cannot process client handshake: %w", err)
208 219
 	}
209 220
 
210 221
 	ctx.dc = dc
222
+	ctx.clientConn = conn
211 223
 	ctx.logger = ctx.logger.BindInt("dc", dc)
212
-	ctx.clientConn = obfuscated2.Conn{
213
-		Conn:      ctx.clientConn,
214
-		Encryptor: encryptor,
215
-		Decryptor: decryptor,
216
-	}
217 224
 
218 225
 	return nil
219 226
 }
@@ -223,17 +230,22 @@ func (p *Proxy) doTelegramCall(ctx *streamContext) error {
223 230
 
224 231
 	addresses := p.telegram.GetAddresses(dcid)
225 232
 	if len(addresses) == 0 && p.allowFallbackOnUnknownDC {
226
-		ctx.logger = ctx.logger.BindInt("fallback_dc", dc.DefaultDC)
233
+		ctx.logger = ctx.logger.BindInt("original_dc", dcid)
227 234
 		ctx.logger.Warning("unknown DC, fallbacks")
235
+		ctx.dc = dc.DefaultDC
228 236
 		addresses = p.telegram.GetAddresses(dc.DefaultDC)
229 237
 	}
230 238
 
231
-	var conn essentials.Conn
232
-	var err error
239
+	var (
240
+		conn      essentials.Conn
241
+		err       error
242
+		foundAddr dc.Addr
243
+	)
233 244
 
234 245
 	for _, addr := range addresses {
235 246
 		conn, err = p.network.Dial(addr.Network, addr.Address)
236 247
 		if err == nil {
248
+			foundAddr = addr
237 249
 			break
238 250
 		}
239 251
 	}
@@ -241,22 +253,17 @@ func (p *Proxy) doTelegramCall(ctx *streamContext) error {
241 253
 		return fmt.Errorf("no addresses to call: %w", err)
242 254
 	}
243 255
 
244
-	encryptor, decryptor, err := obfuscated2.ServerHandshake(conn)
256
+	tgConn, err := foundAddr.Obfuscator.SendHandshake(conn, ctx.dc)
245 257
 	if err != nil {
246
-		conn.Close() //nolint: errcheck
247
-
248
-		return fmt.Errorf("cannot perform obfuscated2 handshake: %w", err)
258
+		conn.Close() // nolint: errcheck
259
+		return fmt.Errorf("cannot perform server handshake: %w", err)
249 260
 	}
250 261
 
251
-	ctx.telegramConn = obfuscated2.Conn{
252
-		Conn: connTraffic{
253
-			Conn:     conn,
254
-			streamID: ctx.streamID,
255
-			stream:   p.eventStream,
256
-			ctx:      ctx,
257
-		},
258
-		Encryptor: encryptor,
259
-		Decryptor: decryptor,
262
+	ctx.telegramConn = connTraffic{
263
+		Conn:     tgConn,
264
+		streamID: ctx.streamID,
265
+		stream:   p.eventStream,
266
+		ctx:      ctx,
260 267
 	}
261 268
 
262 269
 	p.eventStream.Send(ctx,
@@ -279,6 +286,10 @@ func (p *Proxy) doDomainFronting(ctx *streamContext, conn *connRewind) {
279 286
 		return
280 287
 	}
281 288
 
289
+	if p.domainFrontingProxyProtocol {
290
+		frontConn = newConnProxyProtocol(ctx.clientConn, frontConn)
291
+	}
292
+
282 293
 	frontConn = connTraffic{
283 294
 		Conn:     frontConn,
284 295
 		ctx:      ctx,
@@ -300,12 +311,15 @@ func NewProxy(opts ProxyOpts) (*Proxy, error) {
300 311
 		return nil, fmt.Errorf("invalid settings: %w", err)
301 312
 	}
302 313
 
303
-	tg, err := dc.New(opts.getPreferIP(), opts.DCOverrides)
314
+	tg, err := dc.New(opts.getPreferIP())
304 315
 	if err != nil {
305 316
 		return nil, fmt.Errorf("cannot build telegram dc fetcher: %w", err)
306 317
 	}
307 318
 
308 319
 	ctx, cancel := context.WithCancel(context.Background())
320
+	logger := opts.getLogger("proxy")
321
+	updatersLogger := logger.Named("telegram-updaters")
322
+
309 323
 	proxy := &Proxy{
310 324
 		ctx:                      ctx,
311 325
 		ctxCancel:                cancel,
@@ -315,13 +329,26 @@ func NewProxy(opts ProxyOpts) (*Proxy, error) {
315 329
 		blocklist:                opts.IPBlocklist,
316 330
 		allowlist:                opts.IPAllowlist,
317 331
 		eventStream:              opts.EventStream,
318
-		logger:                   opts.getLogger("proxy"),
332
+		logger:                   logger,
319 333
 		domainFrontingPort:       opts.getDomainFrontingPort(),
334
+		domainFrontingIP:         opts.DomainFrontingIP,
320 335
 		tolerateTimeSkewness:     opts.getTolerateTimeSkewness(),
321 336
 		allowFallbackOnUnknownDC: opts.AllowFallbackOnUnknownDC,
322 337
 		telegram:                 tg,
338
+		configUpdater: dc.NewPublicConfigUpdater(
339
+			tg,
340
+			updatersLogger.Named("public-config"),
341
+			opts.Network.MakeHTTPClient(nil),
342
+		),
343
+		clientObfuscatror: obfuscation.Obfuscator{
344
+			Secret: opts.Secret.Key[:],
345
+		},
346
+		domainFrontingProxyProtocol: opts.DomainFrontingProxyProtocol,
323 347
 	}
324 348
 
349
+	proxy.configUpdater.Run(ctx, dc.PublicConfigUpdateURLv4, "tcp4")
350
+	proxy.configUpdater.Run(ctx, dc.PublicConfigUpdateURLv6, "tcp6")
351
+
325 352
 	pool, err := ants.NewPoolWithFunc(opts.getConcurrency(),
326 353
 		func(arg any) {
327 354
 			proxy.ServeConn(arg.(essentials.Conn)) //nolint: forcetypeassert

+ 18
- 1
mtglib/proxy_opts.go 查看文件

@@ -93,6 +93,23 @@ type ProxyOpts struct {
93 93
 	// This is an optional setting.
94 94
 	DomainFrontingPort uint
95 95
 
96
+	// DomainFrontingIP is an IP address to use when connecting to the fronting
97
+	// domain instead of resolving the hostname from the secret via DNS.
98
+	//
99
+	// This is useful when DNS resolution of the fronting host is blocked.
100
+	// The hostname from the secret is still used for SNI in the TLS handshake.
101
+	//
102
+	// This is an optional setting.
103
+	DomainFrontingIP string
104
+
105
+	// DomainFrontingProxyProtocol is used if communication between upstream
106
+	// endpoint and mtg supports proxy protocol. This is useful in case
107
+	// if mtg is also placed behind load balancer, and this will make
108
+	// fronting webserver to know about real IP addresses
109
+	//
110
+	// This is an optional setting.
111
+	DomainFrontingProxyProtocol bool
112
+
96 113
 	// AllowFallbackOnUnknownDC defines how proxy behaves if unknown DC was
97 114
 	// requested. If this setting is set to false, then such connection will be
98 115
 	// rejected. Otherwise, proxy will chose any DC.
@@ -117,7 +134,7 @@ type ProxyOpts struct {
117 134
 	// DCOverrides defines a set of IP addresses that should be used
118 135
 	// with a higher priority to those that are calculated somehow by mtg.
119 136
 	//
120
-	// This is an optional setting
137
+	// OBSOLETE and DEPRECATED. Ignored.
121 138
 	DCOverrides map[int][]string
122 139
 }
123 140
 

正在加载...
取消
保存