Просмотр исходного кода

Merge pull request #336 from 9seconds/obfuscated2

Fetch DC203 from Telegram
tags/v2.1.11^2^2
Sergei Arkhipov 2 месяцев назад
Родитель
Сommit
58cb0b2caf
Аккаунт пользователя с таким Email не найден
48 измененных файлов: 1146 добавлений и 995 удалений
  1. 4
    12
      .mise.toml
  2. 0
    9
      example.config.toml
  3. 0
    9
      internal/cli/run_proxy.go
  4. 1
    1
      internal/cli/simple_run.go
  5. 0
    4
      internal/config/config.go
  6. 0
    4
      internal/config/parse.go
  7. 2
    10
      mtglib/conns.go
  8. 10
    3
      mtglib/internal/dc/addr.go
  9. 0
    16
      mtglib/internal/dc/addr_test.go
  10. 54
    49
      mtglib/internal/dc/init.go
  11. 43
    0
      mtglib/internal/dc/init_test.go
  12. 93
    0
      mtglib/internal/dc/public_config_updater.go
  13. 113
    0
      mtglib/internal/dc/public_config_updater_test.go
  14. 6
    35
      mtglib/internal/dc/telegram.go
  15. 47
    0
      mtglib/internal/dc/updater.go
  16. 55
    0
      mtglib/internal/dc/updater_test.go
  17. 3
    5
      mtglib/internal/dc/view.go
  18. 7
    9
      mtglib/internal/dc/view_test.go
  19. 9
    10
      mtglib/internal/faketls/conn.go
  20. 0
    21
      mtglib/internal/faketls/pools.go
  21. 3
    4
      mtglib/internal/faketls/welcome.go
  22. 0
    54
      mtglib/internal/obfuscated2/client_handshake.go
  23. 0
    32
      mtglib/internal/obfuscated2/client_handshake_fuzz_internal_test.go
  24. 0
    89
      mtglib/internal/obfuscated2/client_handshake_test.go
  25. 0
    37
      mtglib/internal/obfuscated2/conn.go
  26. 0
    71
      mtglib/internal/obfuscated2/handshake_frame.go
  27. 0
    73
      mtglib/internal/obfuscated2/handshake_frame_internal_test.go
  28. 0
    137
      mtglib/internal/obfuscated2/init_test.go
  29. 0
    39
      mtglib/internal/obfuscated2/pools.go
  30. 0
    67
      mtglib/internal/obfuscated2/server_handshake.go
  31. 0
    58
      mtglib/internal/obfuscated2/server_handshake_fuzz_test.go
  32. 0
    65
      mtglib/internal/obfuscated2/server_handshake_test.go
  33. 0
    15
      mtglib/internal/obfuscated2/utils.go
  34. 34
    0
      mtglib/internal/obfuscation/conn.go
  35. 102
    0
      mtglib/internal/obfuscation/conn_test.go
  36. 111
    0
      mtglib/internal/obfuscation/handshake_frame.go
  37. 15
    5
      mtglib/internal/obfuscation/handshake_frame_fuzz_test.go
  38. 66
    0
      mtglib/internal/obfuscation/handshake_frame_test.go
  39. 79
    0
      mtglib/internal/obfuscation/init_test.go
  40. 87
    0
      mtglib/internal/obfuscation/obfuscator.go
  41. 63
    0
      mtglib/internal/obfuscation/obfuscator_fuzz_test.go
  42. 94
    0
      mtglib/internal/obfuscation/obfuscator_test.go
  43. 0
    0
      mtglib/internal/obfuscation/testdata/client-handshake-snapshot-4529d55776e2d427.json
  44. 0
    0
      mtglib/internal/obfuscation/testdata/client-handshake-snapshot-585c944d672f60a2.json
  45. 0
    19
      mtglib/internal/relay/pools.go
  46. 3
    4
      mtglib/internal/relay/relay.go
  47. 41
    28
      mtglib/proxy.go
  48. 1
    1
      mtglib/proxy_opts.go

+ 4
- 12
.mise.toml Просмотреть файл

52
 
52
 
53
 [tasks."test:fuzz:client-handshake"]
53
 [tasks."test:fuzz:client-handshake"]
54
 description = "Run fuzzy test for ClientHandshake"
54
 description = "Run fuzzy test for ClientHandshake"
55
-run = "go test -v {{ vars.fuzzflags }} -fuzz=FuzzClientHandshake ./mtglib/internal/obfuscated2"
55
+run = "go test -v {{ vars.fuzzflags }} -fuzz=FuzzClientServerHandshake ./mtglib/internal/obfuscation"
56
 
56
 
57
-[tasks."test:fuzz:server-generate-handshake-frame"]
58
-description = "Run fuzzy test for ServerGenerateHandshakeFrame"
59
-run = "go test -v {{ vars.fuzzflags }} -fuzz=FuzzServerGenerateHandshakeFrame ./mtglib/internal/obfuscated2"
60
-
61
-[tasks."test:fuzz:server-receive"]
62
-description = "Run fuzzy test for ServerReceive"
63
-run = "go test -v {{ vars.fuzzflags }} -fuzz=FuzzServerReceive ./mtglib/internal/obfuscated2"
64
-
65
-[tasks."test:fuzz:server-send"]
66
-description = "Run fuzzy test for ServerSend"
67
-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"
68
 
60
 
69
 [tasks.static]
61
 [tasks.static]
70
 description = "Build static binary"
62
 description = "Build static binary"

+ 0
- 9
example.config.toml Просмотреть файл

88
 # Otherwise, chose a new DC.
88
 # Otherwise, chose a new DC.
89
 allow-fallback-on-unknown-dc = false
89
 allow-fallback-on-unknown-dc = false
90
 
90
 
91
-# Telegram uses different DCs for different purposes. Unfortunately, most of
92
-# DCs are not public, and dependent on a location of the current user, so
93
-# mtg cannot know upfront about all of them, and how to access them. It has
94
-# a default list of DCs, including some CDN IPs, but it is possible that some
95
-# of them are not working for you. In this case, you can override them here.
96
-[[dc-overrides]]
97
-dc = 101
98
-ips = ["127.0.0.1:443"]
99
-
100
 # network defines different network-related settings
91
 # network defines different network-related settings
101
 [network]
92
 [network]
102
 # please be aware that mtg needs to do some external requests. For
93
 # please be aware that mtg needs to do some external requests. For

+ 0
- 9
internal/cli/run_proxy.go Просмотреть файл

242
 		return fmt.Errorf("cannot build ip allowlist: %w", err)
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
 	opts := mtglib.ProxyOpts{
245
 	opts := mtglib.ProxyOpts{
254
 		Logger:          logger,
246
 		Logger:          logger,
255
 		Network:         ntw,
247
 		Network:         ntw,
265
 
257
 
266
 		AllowFallbackOnUnknownDC: conf.AllowFallbackOnUnknownDC.Get(false),
258
 		AllowFallbackOnUnknownDC: conf.AllowFallbackOnUnknownDC.Get(false),
267
 		TolerateTimeSkewness:     conf.TolerateTimeSkewness.Value,
259
 		TolerateTimeSkewness:     conf.TolerateTimeSkewness.Value,
268
-		DCOverrides:              dcOverrides,
269
 	}
260
 	}
270
 
261
 
271
 	proxy, err := mtglib.NewProxy(opts)
262
 	proxy, err := mtglib.NewProxy(opts)

+ 1
- 1
internal/cli/simple_run.go Просмотреть файл

18
 	TCPBuffer           string        `kong:"name='tcp-buffer',short='b',default='4KB',help='Deprecated and ignored'"`                                                 //nolint: lll
18
 	TCPBuffer           string        `kong:"name='tcp-buffer',short='b',default='4KB',help='Deprecated and ignored'"`                                                 //nolint: lll
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
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
 	DomainFrontingPort  uint64        `kong:"name='domain-fronting-port',short='p',default='443',help='A port to access for domain fronting.'"`                        //nolint: lll
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
+	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
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
 	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
23
 	Timeout             time.Duration `kong:"name='timeout',short='t',default='10s',help='Network timeout to use'"`                                                    //nolint: lll
23
 	Timeout             time.Duration `kong:"name='timeout',short='t',default='10s',help='Network timeout to use'"`                                                    //nolint: lll
24
 	Socks5Proxies       []string      `kong:"name='socks5-proxy',short='s',help='Socks5 proxies to use for network access.'"`                                          //nolint: lll
24
 	Socks5Proxies       []string      `kong:"name='socks5-proxy',short='s',help='Socks5 proxies to use for network access.'"`                                          //nolint: lll

+ 0
- 4
internal/config/config.go Просмотреть файл

66
 			MetricPrefix TypeMetricPrefix `json:"metricPrefix"`
66
 			MetricPrefix TypeMetricPrefix `json:"metricPrefix"`
67
 		} `json:"prometheus"`
67
 		} `json:"prometheus"`
68
 	} `json:"stats"`
68
 	} `json:"stats"`
69
-	DCOverrides []struct {
70
-		DC  TypeDC         `json:"dc"`
71
-		IPs []TypeHostPort `json:"ips"`
72
-	} `json:"dcOverrides"`
73
 }
69
 }
74
 
70
 
75
 func (c *Config) Validate() error {
71
 func (c *Config) Validate() error {

+ 0
- 4
internal/config/parse.go Просмотреть файл

61
 			MetricPrefix string `toml:"metric-prefix" json:"metricPrefix,omitempty"`
61
 			MetricPrefix string `toml:"metric-prefix" json:"metricPrefix,omitempty"`
62
 		} `toml:"prometheus" json:"prometheus,omitempty"`
62
 		} `toml:"prometheus" json:"prometheus,omitempty"`
63
 	} `toml:"stats" json:"stats,omitempty"`
63
 	} `toml:"stats" json:"stats,omitempty"`
64
-	DCOverrides []struct {
65
-		DC  uint     `toml:"dc" json:"dc"`
66
-		IPs []string `toml:"ips" json:"ips"`
67
-	} `toml:"dc-overrides" json:"dcOverrides,omitempty"`
68
 }
64
 }
69
 
65
 
70
 func Parse(rawData []byte) (*Config, error) {
66
 func Parse(rawData []byte) (*Config, error) {

+ 2
- 10
mtglib/conns.go Просмотреть файл

4
 	"bytes"
4
 	"bytes"
5
 	"context"
5
 	"context"
6
 	"io"
6
 	"io"
7
-	"sync"
8
 
7
 
9
 	"github.com/9seconds/mtg/v2/essentials"
8
 	"github.com/9seconds/mtg/v2/essentials"
10
 )
9
 )
40
 type connRewind struct {
39
 type connRewind struct {
41
 	essentials.Conn
40
 	essentials.Conn
42
 
41
 
43
-	active io.Reader
44
 	buf    bytes.Buffer
42
 	buf    bytes.Buffer
45
-	mutex  sync.RWMutex
43
+	active io.Reader
46
 }
44
 }
47
 
45
 
48
 func (c *connRewind) Read(p []byte) (int, error) {
46
 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
47
+	return c.active.Read(p)
53
 }
48
 }
54
 
49
 
55
 func (c *connRewind) Rewind() {
50
 func (c *connRewind) Rewind() {
56
-	c.mutex.Lock()
57
-	defer c.mutex.Unlock()
58
-
59
 	c.active = io.MultiReader(&c.buf, c.Conn)
51
 	c.active = io.MultiReader(&c.buf, c.Conn)
60
 }
52
 }
61
 
53
 

+ 10
- 3
mtglib/internal/dc/addr.go Просмотреть файл

1
 package dc
1
 package dc
2
 
2
 
3
+import (
4
+	"fmt"
5
+
6
+	"github.com/9seconds/mtg/v2/mtglib/internal/obfuscation"
7
+)
8
+
3
 type Addr struct {
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
 func (d Addr) String() string {
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
-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
 package dc
1
 package dc
2
 
2
 
3
+import (
4
+	"context"
5
+	"time"
6
+)
7
+
3
 type preferIP uint8
8
 type preferIP uint8
4
 
9
 
5
 const (
10
 const (
10
 )
15
 )
11
 
16
 
12
 const (
17
 const (
18
+	// Default DC to connect to if not sure.
13
 	DefaultDC = 2
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
 type Logger interface {
32
 type Logger interface {
18
 	WarningError(msg string, err error)
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 Просмотреть файл

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 Просмотреть файл

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 Просмотреть файл

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
 
2
 
3
 import (
3
 import (
4
 	"fmt"
4
 	"fmt"
5
-	"net"
6
 	"strings"
5
 	"strings"
6
+	"sync"
7
 )
7
 )
8
 
8
 
9
 type Telegram struct {
9
 type Telegram struct {
10
+	lock     sync.RWMutex
10
 	view     dcView
11
 	view     dcView
11
 	preferIP preferIP
12
 	preferIP preferIP
12
 }
13
 }
13
 
14
 
14
 func (t *Telegram) GetAddresses(dc int) []Addr {
15
 func (t *Telegram) GetAddresses(dc int) []Addr {
16
+	t.lock.RLock()
17
+	defer t.lock.RUnlock()
18
+
15
 	switch t.preferIP {
19
 	switch t.preferIP {
16
 	case preferIPOnlyIPv4:
20
 	case preferIPOnlyIPv4:
17
 		return t.view.getV4(dc)
21
 		return t.view.getV4(dc)
24
 	return append(t.view.getV6(dc), t.view.getV4(dc)...)
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
 	var pref preferIP
32
 	var pref preferIP
29
 
33
 
30
 	switch strings.ToLower(ipPreference) {
34
 	switch strings.ToLower(ipPreference) {
40
 		return nil, fmt.Errorf("unknown ip preference %s", ipPreference)
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
 	return &Telegram{
47
 	return &Telegram{
74
-		view: dcView{
75
-			overrides: overrides,
76
-		},
77
 		preferIP: pref,
48
 		preferIP: pref,
78
 	}, nil
49
 	}, nil
79
 }
50
 }

+ 47
- 0
mtglib/internal/dc/updater.go Просмотреть файл

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 Просмотреть файл

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

+ 7
- 9
mtglib/internal/dc/view_test.go Просмотреть файл

16
 
16
 
17
 func (suite *ViewTestSuite) SetupSuite() {
17
 func (suite *ViewTestSuite) SetupSuite() {
18
 	suite.view = dcView{
18
 	suite.view = dcView{
19
-		overrides: dcAddrSet{
19
+		publicConfigs: dcAddrSet{
20
 			v4: map[int][]Addr{
20
 			v4: map[int][]Addr{
21
 				111: {
21
 				111: {
22
 					{Network: "tcp4", Address: "127.0.0.1:443"},
22
 					{Network: "tcp4", Address: "127.0.0.1:443"},
37
 func (suite *ViewTestSuite) TestGetV4() {
37
 func (suite *ViewTestSuite) TestGetV4() {
38
 	testData := map[int][]Addr{
38
 	testData := map[int][]Addr{
39
 		111: {
39
 		111: {
40
-			{"tcp4", "127.0.0.1:443"},
40
+			{Network: "tcp4", Address: "127.0.0.1:443"},
41
 		},
41
 		},
42
 		203: {
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
 		2: {
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
 	testData := map[int][]Addr{
59
 	testData := map[int][]Addr{
61
 		111: {},
60
 		111: {},
62
 		203: {
61
 		203: {
63
-			{"tcp6", "xxx"},
64
-			{"tcp6", "[2a0a:f280:0203:000a:5000:0000:0000:0100]:443"},
62
+			{Network: "tcp6", Address: "xxx"},
65
 		},
63
 		},
66
 		1: {
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
 	rec.Type = record.TypeApplicationData
47
 	rec.Type = record.TypeApplicationData
48
 	rec.Version = record.Version12
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
 	for len(p) > 0 {
52
 	for len(p) > 0 {
56
 		chunkSize := rand.IntN(record.TLSMaxRecordSize)
53
 		chunkSize := rand.IntN(record.TLSMaxRecordSize)
60
 
57
 
61
 		rec.Payload.Reset()
58
 		rec.Payload.Reset()
62
 		rec.Payload.Write(p[:chunkSize])
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
-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
 package faketls
1
 package faketls
2
 
2
 
3
 import (
3
 import (
4
+	"bytes"
4
 	"crypto/hmac"
5
 	"crypto/hmac"
5
 	"crypto/rand"
6
 	"crypto/rand"
6
 	"crypto/sha256"
7
 	"crypto/sha256"
13
 )
14
 )
14
 
15
 
15
 func SendWelcomePacket(writer io.Writer, secret []byte, clientHello ClientHello) error {
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
 	rec := record.AcquireRecord()
19
 	rec := record.AcquireRecord()
20
 	defer record.ReleaseRecord(rec)
20
 	defer record.ReleaseRecord(rec)
58
 }
58
 }
59
 
59
 
60
 func generateServerHello(writer io.Writer, clientHello ClientHello) {
60
 func generateServerHello(writer io.Writer, clientHello ClientHello) {
61
-	bodyBuf := acquireBytesBuffer()
62
-	defer releaseBytesBuffer(bodyBuf)
61
+	bodyBuf := &bytes.Buffer{}
63
 
62
 
64
 	sliceBuf := [2]byte{}
63
 	sliceBuf := [2]byte{}
65
 	digest := [RandomLen]byte{}
64
 	digest := [RandomLen]byte{}

+ 0
- 54
mtglib/internal/obfuscated2/client_handshake.go Просмотреть файл

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
-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
-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
-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
-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
-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
-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
-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
-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
-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
-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
-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 Просмотреть файл

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 Просмотреть файл

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 Просмотреть файл

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
-package obfuscated2
1
+package obfuscation
2
 
2
 
3
 import (
3
 import (
4
 	"encoding/binary"
4
 	"encoding/binary"
7
 	"github.com/stretchr/testify/assert"
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
 		assert.NotEqualValues(t, 0xef, frame.data[0])
14
 		assert.NotEqualValues(t, 0xef, frame.data[0])
15
 
15
 
18
 		assert.NotEqualValues(t, 0x54534f50, firstBytes)
18
 		assert.NotEqualValues(t, 0x54534f50, firstBytes)
19
 		assert.NotEqualValues(t, 0x20544547, firstBytes)
19
 		assert.NotEqualValues(t, 0x20544547, firstBytes)
20
 		assert.NotEqualValues(t, 0x4954504f, firstBytes)
20
 		assert.NotEqualValues(t, 0x4954504f, firstBytes)
21
+		assert.NotEqualValues(t, 0x02010316, firstBytes)
21
 		assert.NotEqualValues(t, 0xeeeeeeee, firstBytes)
22
 		assert.NotEqualValues(t, 0xeeeeeeee, firstBytes)
23
+		assert.NotEqualValues(t, 0xdddddddd, firstBytes)
22
 
24
 
23
 		assert.NotEqualValues(
25
 		assert.NotEqualValues(
24
 			t,
26
 			t,
25
 			0,
27
 			0,
26
 			frame.data[4]|frame.data[5]|frame.data[6]|frame.data[7])
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 Просмотреть файл

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 Просмотреть файл

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 Просмотреть файл

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 Просмотреть файл

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 Просмотреть файл

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
-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
 }
35
 }
36
 
36
 
37
 func pump(log Logger, src, dst essentials.Conn, direction string) {
37
 func pump(log Logger, src, dst essentials.Conn, direction string) {
38
+	var buf [copyBufferSize]byte
39
+
38
 	defer src.CloseRead()  //nolint: errcheck
40
 	defer src.CloseRead()  //nolint: errcheck
39
 	defer dst.CloseWrite() //nolint: errcheck
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
 	switch {
45
 	switch {
47
 	case err == nil:
46
 	case err == nil:

+ 41
- 28
mtglib/proxy.go Просмотреть файл

13
 	"github.com/9seconds/mtg/v2/mtglib/internal/dc"
13
 	"github.com/9seconds/mtg/v2/mtglib/internal/dc"
14
 	"github.com/9seconds/mtg/v2/mtglib/internal/faketls"
14
 	"github.com/9seconds/mtg/v2/mtglib/internal/faketls"
15
 	"github.com/9seconds/mtg/v2/mtglib/internal/faketls/record"
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
 	"github.com/9seconds/mtg/v2/mtglib/internal/relay"
17
 	"github.com/9seconds/mtg/v2/mtglib/internal/relay"
18
 	"github.com/panjf2000/ants/v2"
18
 	"github.com/panjf2000/ants/v2"
19
 )
19
 )
30
 	domainFrontingIP         string
30
 	domainFrontingIP         string
31
 	workerPool               *ants.PoolWithFunc
31
 	workerPool               *ants.PoolWithFunc
32
 	telegram                 *dc.Telegram
32
 	telegram                 *dc.Telegram
33
+	configUpdater            *dc.PublicConfigUpdater
34
+	clientObfuscatror        obfuscation.Obfuscator
33
 
35
 
34
 	secret          Secret
36
 	secret          Secret
35
 	network         Network
37
 	network         Network
77
 		return
79
 		return
78
 	}
80
 	}
79
 
81
 
80
-	if err := p.doObfuscated2Handshake(ctx); err != nil {
81
-		p.logger.InfoError("obfuscated2 handshake is failed", err)
82
+	if err := p.doObfuscatedHandshake(ctx); err != nil {
83
+		p.logger.InfoError("obfuscated handshake is failed", err)
82
 
84
 
83
 		return
85
 		return
84
 	}
86
 	}
151
 	p.ctxCancel()
153
 	p.ctxCancel()
152
 	p.streamWaitGroup.Wait()
154
 	p.streamWaitGroup.Wait()
153
 	p.workerPool.Release()
155
 	p.workerPool.Release()
156
+	p.configUpdater.Wait()
154
 
157
 
155
 	p.allowlist.Shutdown()
158
 	p.allowlist.Shutdown()
156
 	p.blocklist.Shutdown()
159
 	p.blocklist.Shutdown()
208
 	return true
211
 	return true
209
 }
212
 }
210
 
213
 
211
-func (p *Proxy) doObfuscated2Handshake(ctx *streamContext) error {
212
-	dc, encryptor, decryptor, err := obfuscated2.ClientHandshake(p.secret.Key[:], ctx.clientConn)
214
+func (p *Proxy) doObfuscatedHandshake(ctx *streamContext) error {
215
+	dc, conn, err := p.clientObfuscatror.ReadHandshake(ctx.clientConn)
213
 	if err != nil {
216
 	if err != nil {
214
 		return fmt.Errorf("cannot process client handshake: %w", err)
217
 		return fmt.Errorf("cannot process client handshake: %w", err)
215
 	}
218
 	}
216
 
219
 
217
 	ctx.dc = dc
220
 	ctx.dc = dc
221
+	ctx.clientConn = conn
218
 	ctx.logger = ctx.logger.BindInt("dc", dc)
222
 	ctx.logger = ctx.logger.BindInt("dc", dc)
219
-	ctx.clientConn = obfuscated2.Conn{
220
-		Conn:      ctx.clientConn,
221
-		Encryptor: encryptor,
222
-		Decryptor: decryptor,
223
-	}
224
 
223
 
225
 	return nil
224
 	return nil
226
 }
225
 }
230
 
229
 
231
 	addresses := p.telegram.GetAddresses(dcid)
230
 	addresses := p.telegram.GetAddresses(dcid)
232
 	if len(addresses) == 0 && p.allowFallbackOnUnknownDC {
231
 	if len(addresses) == 0 && p.allowFallbackOnUnknownDC {
233
-		ctx.logger = ctx.logger.BindInt("fallback_dc", dc.DefaultDC)
232
+		ctx.logger = ctx.logger.BindInt("original_dc", dcid)
234
 		ctx.logger.Warning("unknown DC, fallbacks")
233
 		ctx.logger.Warning("unknown DC, fallbacks")
234
+		ctx.dc = dc.DefaultDC
235
 		addresses = p.telegram.GetAddresses(dc.DefaultDC)
235
 		addresses = p.telegram.GetAddresses(dc.DefaultDC)
236
 	}
236
 	}
237
 
237
 
238
-	var conn essentials.Conn
239
-	var err error
238
+	var (
239
+		conn      essentials.Conn
240
+		err       error
241
+		foundAddr dc.Addr
242
+	)
240
 
243
 
241
 	for _, addr := range addresses {
244
 	for _, addr := range addresses {
242
 		conn, err = p.network.Dial(addr.Network, addr.Address)
245
 		conn, err = p.network.Dial(addr.Network, addr.Address)
243
 		if err == nil {
246
 		if err == nil {
247
+			foundAddr = addr
244
 			break
248
 			break
245
 		}
249
 		}
246
 	}
250
 	}
248
 		return fmt.Errorf("no addresses to call: %w", err)
252
 		return fmt.Errorf("no addresses to call: %w", err)
249
 	}
253
 	}
250
 
254
 
251
-	encryptor, decryptor, err := obfuscated2.ServerHandshake(conn)
255
+	tgConn, err := foundAddr.Obfuscator.SendHandshake(conn, ctx.dc)
252
 	if err != nil {
256
 	if err != nil {
253
-		conn.Close() //nolint: errcheck
254
-
255
-		return fmt.Errorf("cannot perform obfuscated2 handshake: %w", err)
257
+		conn.Close() // nolint: errcheck
258
+		return fmt.Errorf("cannot perform server handshake: %w", err)
256
 	}
259
 	}
257
 
260
 
258
-	ctx.telegramConn = obfuscated2.Conn{
259
-		Conn: connTraffic{
260
-			Conn:     conn,
261
-			streamID: ctx.streamID,
262
-			stream:   p.eventStream,
263
-			ctx:      ctx,
264
-		},
265
-		Encryptor: encryptor,
266
-		Decryptor: decryptor,
261
+	ctx.telegramConn = connTraffic{
262
+		Conn:     tgConn,
263
+		streamID: ctx.streamID,
264
+		stream:   p.eventStream,
265
+		ctx:      ctx,
267
 	}
266
 	}
268
 
267
 
269
 	p.eventStream.Send(ctx,
268
 	p.eventStream.Send(ctx,
307
 		return nil, fmt.Errorf("invalid settings: %w", err)
306
 		return nil, fmt.Errorf("invalid settings: %w", err)
308
 	}
307
 	}
309
 
308
 
310
-	tg, err := dc.New(opts.getPreferIP(), opts.DCOverrides)
309
+	tg, err := dc.New(opts.getPreferIP())
311
 	if err != nil {
310
 	if err != nil {
312
 		return nil, fmt.Errorf("cannot build telegram dc fetcher: %w", err)
311
 		return nil, fmt.Errorf("cannot build telegram dc fetcher: %w", err)
313
 	}
312
 	}
314
 
313
 
315
 	ctx, cancel := context.WithCancel(context.Background())
314
 	ctx, cancel := context.WithCancel(context.Background())
315
+	logger := opts.getLogger("proxy")
316
+	updatersLogger := logger.Named("telegram-updaters")
317
+
316
 	proxy := &Proxy{
318
 	proxy := &Proxy{
317
 		ctx:                      ctx,
319
 		ctx:                      ctx,
318
 		ctxCancel:                cancel,
320
 		ctxCancel:                cancel,
322
 		blocklist:                opts.IPBlocklist,
324
 		blocklist:                opts.IPBlocklist,
323
 		allowlist:                opts.IPAllowlist,
325
 		allowlist:                opts.IPAllowlist,
324
 		eventStream:              opts.EventStream,
326
 		eventStream:              opts.EventStream,
325
-		logger:                   opts.getLogger("proxy"),
327
+		logger:                   logger,
326
 		domainFrontingPort:       opts.getDomainFrontingPort(),
328
 		domainFrontingPort:       opts.getDomainFrontingPort(),
327
 		domainFrontingIP:         opts.DomainFrontingIP,
329
 		domainFrontingIP:         opts.DomainFrontingIP,
328
 		tolerateTimeSkewness:     opts.getTolerateTimeSkewness(),
330
 		tolerateTimeSkewness:     opts.getTolerateTimeSkewness(),
329
 		allowFallbackOnUnknownDC: opts.AllowFallbackOnUnknownDC,
331
 		allowFallbackOnUnknownDC: opts.AllowFallbackOnUnknownDC,
330
 		telegram:                 tg,
332
 		telegram:                 tg,
333
+		configUpdater: dc.NewPublicConfigUpdater(
334
+			tg,
335
+			updatersLogger.Named("public-config"),
336
+			opts.Network.MakeHTTPClient(nil),
337
+		),
338
+		clientObfuscatror: obfuscation.Obfuscator{
339
+			Secret: opts.Secret.Key[:],
340
+		},
331
 	}
341
 	}
332
 
342
 
343
+	proxy.configUpdater.Run(ctx, dc.PublicConfigUpdateURLv4, "tcp4")
344
+	proxy.configUpdater.Run(ctx, dc.PublicConfigUpdateURLv6, "tcp6")
345
+
333
 	pool, err := ants.NewPoolWithFunc(opts.getConcurrency(),
346
 	pool, err := ants.NewPoolWithFunc(opts.getConcurrency(),
334
 		func(arg any) {
347
 		func(arg any) {
335
 			proxy.ServeConn(arg.(essentials.Conn)) //nolint: forcetypeassert
348
 			proxy.ServeConn(arg.(essentials.Conn)) //nolint: forcetypeassert

+ 1
- 1
mtglib/proxy_opts.go Просмотреть файл

126
 	// DCOverrides defines a set of IP addresses that should be used
126
 	// DCOverrides defines a set of IP addresses that should be used
127
 	// with a higher priority to those that are calculated somehow by mtg.
127
 	// with a higher priority to those that are calculated somehow by mtg.
128
 	//
128
 	//
129
-	// This is an optional setting
129
+	// OBSOLETE and DEPRECATED. Ignored.
130
 	DCOverrides map[int][]string
130
 	DCOverrides map[int][]string
131
 }
131
 }
132
 
132
 

Загрузка…
Отмена
Сохранить