Преглед изворни кода

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

tags/v2.2.5^2
9seconds пре 1 месец
родитељ
комит
9d43c2d759
39 измењених фајлова са 484 додато и 241 уклоњено
  1. 33
    0
      .github/workflows/ci.yaml
  2. 7
    5
      .github/workflows/codeql-analysis.yml
  3. 6
    0
      .mise.toml
  4. 3
    0
      README.md
  5. BIN
      default.pgo
  6. 1
    1
      events/event_stream.go
  7. 7
    0
      example.config.toml
  8. 2
    2
      go.mod
  9. 4
    13
      go.sum
  10. 6
    0
      internal/cli/access.go
  11. 10
    3
      internal/cli/doctor.go
  12. 2
    0
      internal/cli/run_proxy.go
  13. 6
    4
      internal/config/config.go
  14. 26
    0
      internal/config/config_test.go
  15. 6
    4
      internal/config/parse.go
  16. 4
    0
      internal/config/testdata/public_ip.toml
  17. 3
    0
      internal/config/testdata/public_ip_invalid.toml
  18. 3
    0
      internal/config/testdata/public_ip_v4_only.toml
  19. 19
    0
      mtglib/conns.go
  20. 0
    35
      mtglib/internal/doppel/clock.go
  21. 0
    80
      mtglib/internal/doppel/clock_test.go
  22. 21
    19
      mtglib/internal/doppel/conn.go
  23. 2
    2
      mtglib/internal/doppel/conn_test.go
  24. 92
    10
      mtglib/internal/doppel/ganger.go
  25. 47
    12
      mtglib/internal/doppel/scout.go
  26. 17
    4
      mtglib/internal/doppel/scout_conn.go
  27. 14
    3
      mtglib/internal/doppel/scout_conn_collected.go
  28. 4
    4
      mtglib/internal/doppel/scout_conn_collected_test.go
  29. 2
    2
      mtglib/internal/doppel/scout_test.go
  30. 2
    2
      mtglib/internal/doppel/stats.go
  31. 13
    0
      mtglib/internal/relay/pool_settings_constrained.go
  32. 9
    0
      mtglib/internal/relay/pool_settings_other.go
  33. 18
    0
      mtglib/internal/relay/pools.go
  34. 6
    6
      mtglib/internal/relay/relay.go
  35. 34
    15
      mtglib/internal/tls/fake/server_side.go
  36. 32
    6
      mtglib/internal/tls/fake/server_side_test.go
  37. 14
    8
      mtglib/proxy.go
  38. 9
    0
      mtglib/proxy_opts.go
  39. 0
    1
      run_profile_tag_prof.go

+ 33
- 0
.github/workflows/ci.yaml Прегледај датотеку

119
       - name: Run linter
119
       - name: Run linter
120
         run: mise tasks run lint
120
         run: mise tasks run lint
121
 
121
 
122
+  artifacts:
123
+    name: Build release artifacts
124
+    runs-on: ubuntu-latest
125
+    timeout-minutes: 10
126
+    steps:
127
+      - name: Checkout
128
+        uses: actions/checkout@v6
129
+        with:
130
+          submodules: recursive
131
+
132
+      - uses: jdx/mise-action@v3
133
+        name: Install mise
134
+
135
+      - name: Cache Go modules
136
+        uses: actions/cache@v5
137
+        with:
138
+          path: ~/go/pkg/mod
139
+          key: ${{ runner.os }}-gomod-${{ hashFiles('go.sum') }}
140
+          restore-keys: |
141
+            ${{ runner.os }}-gomod-
142
+
143
+      - name: Cache cross-compilation build
144
+        uses: actions/cache@v5
145
+        with:
146
+          path: ~/.cache/go-build
147
+          key: ${{ runner.os }}-goreleaser-${{ hashFiles('go.sum') }}-${{ hashFiles('**/*.go') }}
148
+          restore-keys: |
149
+            ${{ runner.os }}-goreleaser-${{ hashFiles('go.sum') }}-
150
+            ${{ runner.os }}-goreleaser-
151
+
152
+      - name: Run release
153
+        run: mise tasks run release
154
+
122
   docker:
155
   docker:
123
     name: Docker
156
     name: Docker
124
     runs-on: ubuntu-latest
157
     runs-on: ubuntu-latest

+ 7
- 5
.github/workflows/codeql-analysis.yml Прегледај датотеку

21
 
21
 
22
 on:
22
 on:
23
   push:
23
   push:
24
-    branches: 
24
+    branches:
25
       - master
25
       - master
26
       - stable
26
       - stable
27
   pull_request:
27
   pull_request:
45
 
45
 
46
     steps:
46
     steps:
47
     - name: Checkout repository
47
     - name: Checkout repository
48
-      uses: actions/checkout@v2
48
+      uses: actions/checkout@v6
49
+      with:
50
+        submodules: recursive
49
 
51
 
50
     # Initializes the CodeQL tools for scanning.
52
     # Initializes the CodeQL tools for scanning.
51
     - name: Initialize CodeQL
53
     - name: Initialize CodeQL
52
-      uses: github/codeql-action/init@v1
54
+      uses: github/codeql-action/init@v4
53
       with:
55
       with:
54
         languages: ${{ matrix.language }}
56
         languages: ${{ matrix.language }}
55
         # If you wish to specify custom queries, you can do so here or in a config file.
57
         # If you wish to specify custom queries, you can do so here or in a config file.
60
     # Autobuild attempts to build any compiled languages  (C/C++, C#, or Java).
62
     # Autobuild attempts to build any compiled languages  (C/C++, C#, or Java).
61
     # If this step fails, then you should remove it and run the build manually (see below)
63
     # If this step fails, then you should remove it and run the build manually (see below)
62
     - name: Autobuild
64
     - name: Autobuild
63
-      uses: github/codeql-action/autobuild@v1
65
+      uses: github/codeql-action/autobuild@v4
64
 
66
 
65
     # ℹ️ Command-line programs to run using the OS shell.
67
     # ℹ️ Command-line programs to run using the OS shell.
66
     # 📚 https://git.io/JvXDl
68
     # 📚 https://git.io/JvXDl
74
     #   make release
76
     #   make release
75
 
77
 
76
     - name: Perform CodeQL Analysis
78
     - name: Perform CodeQL Analysis
77
-      uses: github/codeql-action/analyze@v1
79
+      uses: github/codeql-action/analyze@v4

+ 6
- 0
.mise.toml Прегледај датотеку

16
 outputs = ["mtg"]
16
 outputs = ["mtg"]
17
 run = "go build"
17
 run = "go build"
18
 
18
 
19
+[tasks."build:prof"]
20
+description = "Build binary with profiling enabled"
21
+sources = ["**/*.go", "go.mod", "go.sum"]
22
+outputs = ["mtg"]
23
+run = "go build -tags prof"
24
+
19
 [tasks.update]
25
 [tasks.update]
20
 description = "Update dependencies"
26
 description = "Update dependencies"
21
 run = [
27
 run = [

+ 3
- 0
README.md Прегледај датотеку

91
   software. I also believe that in the case of throwout proxies, this
91
   software. I also believe that in the case of throwout proxies, this
92
   the feature is a useless luxury.
92
   the feature is a useless luxury.
93
 
93
 
94
+  This is very controversial topic. Please read [rationale (in russian)](https://github.com/9seconds/mtg/issues/376#issuecomment-4118726699)
95
+  and use [mtg-multi](https://github.com/dolonet/mtg-multi) fork if you are disagree with.
96
+
94
 * **No adtag support**
97
 * **No adtag support**
95
 
98
 
96
   Please read [Version 2](#version-2) chapter.
99
   Please read [Version 2](#version-2) chapter.


+ 1
- 1
events/event_stream.go Прегледај датотеку

38
 	select {
38
 	select {
39
 	case <-ctx.Done():
39
 	case <-ctx.Done():
40
 	case <-e.ctx.Done():
40
 	case <-e.ctx.Done():
41
-	case e.chans[int(chanNo)%len(e.chans)] <- evt:
41
+	case e.chans[chanNo%uint32(len(e.chans))] <- evt:
42
 	}
42
 	}
43
 }
43
 }
44
 
44
 

+ 7
- 0
example.config.toml Прегледај датотеку

48
 #     Only ipv4 connectivity is used
48
 #     Only ipv4 connectivity is used
49
 prefer-ip = "prefer-ipv6"
49
 prefer-ip = "prefer-ipv6"
50
 
50
 
51
+# Public IP addresses of this server. Used by 'mtg access' to generate
52
+# proxy links and by 'mtg doctor' to validate SNI-DNS match.
53
+# If not set, mtg tries to detect them automatically via ifconfig.co.
54
+# Set these if ifconfig.co is unreachable from your server.
55
+# public-ipv4 = "1.2.3.4"
56
+# public-ipv6 = "2001:db8::1"
57
+
51
 # If this setting is set, then mtg will try to get proxy updates from Telegram
58
 # If this setting is set, then mtg will try to get proxy updates from Telegram
52
 # Usually this is completely fine to have it disabled, because mtg has a list
59
 # Usually this is completely fine to have it disabled, because mtg has a list
53
 # of some core proxies hardcoded.
60
 # of some core proxies hardcoded.

+ 2
- 2
go.mod Прегледај датотеку

15
 	github.com/prometheus/client_golang v1.23.2
15
 	github.com/prometheus/client_golang v1.23.2
16
 	github.com/prometheus/common v0.67.5 // indirect
16
 	github.com/prometheus/common v0.67.5 // indirect
17
 	github.com/prometheus/procfs v0.20.1 // indirect
17
 	github.com/prometheus/procfs v0.20.1 // indirect
18
-	github.com/rs/zerolog v1.34.0
18
+	github.com/rs/zerolog v1.35.0
19
 	github.com/smira/go-statsd v1.3.4
19
 	github.com/smira/go-statsd v1.3.4
20
 	github.com/stretchr/objx v0.5.2 // indirect
20
 	github.com/stretchr/objx v0.5.2 // indirect
21
 	github.com/stretchr/testify v1.11.1
21
 	github.com/stretchr/testify v1.11.1
29
 require (
29
 require (
30
 	github.com/beevik/ntp v1.5.0
30
 	github.com/beevik/ntp v1.5.0
31
 	github.com/ncruces/go-dns v1.3.2
31
 	github.com/ncruces/go-dns v1.3.2
32
-	github.com/pelletier/go-toml/v2 v2.2.4
32
+	github.com/pelletier/go-toml/v2 v2.3.0
33
 	github.com/pires/go-proxyproto v0.11.0
33
 	github.com/pires/go-proxyproto v0.11.0
34
 	github.com/things-go/go-socks5 v0.1.0
34
 	github.com/things-go/go-socks5 v0.1.0
35
 	github.com/txthinking/socks5 v0.0.0-20251011041537-5c31f201a10e
35
 	github.com/txthinking/socks5 v0.0.0-20251011041537-5c31f201a10e

+ 4
- 13
go.sum Прегледај датотеку

18
 github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
18
 github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
19
 github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
19
 github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
20
 github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
20
 github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
21
-github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
22
 github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
21
 github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
23
 github.com/d4l3k/messagediff v1.2.1 h1:ZcAIMYsUg0EAp9X+tt8/enBE/Q8Yd5kzPynLyKptt9U=
22
 github.com/d4l3k/messagediff v1.2.1 h1:ZcAIMYsUg0EAp9X+tt8/enBE/Q8Yd5kzPynLyKptt9U=
24
 github.com/d4l3k/messagediff v1.2.1/go.mod h1:Oozbb1TVXFac9FtSIxHBMnBCq2qeH/2KkEQxENCrlLo=
23
 github.com/d4l3k/messagediff v1.2.1/go.mod h1:Oozbb1TVXFac9FtSIxHBMnBCq2qeH/2KkEQxENCrlLo=
25
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
24
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
26
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
25
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
27
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
26
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
28
-github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
29
 github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
27
 github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
30
 github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
28
 github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
31
 github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
29
 github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
40
 github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
38
 github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
41
 github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
39
 github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
42
 github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
40
 github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
43
-github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
44
 github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
41
 github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
45
 github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
42
 github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
46
-github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
47
-github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
48
 github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
43
 github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
49
 github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
44
 github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
50
 github.com/mccutchen/go-httpbin v1.1.1 h1:aEws49HEJEyXHLDnshQVswfUlCVoS8g6h9YaDyaW7RE=
45
 github.com/mccutchen/go-httpbin v1.1.1 h1:aEws49HEJEyXHLDnshQVswfUlCVoS8g6h9YaDyaW7RE=
59
 github.com/panjf2000/ants/v2 v2.12.0/go.mod h1:tSQuaNQ6r6NRhPt+IZVUevvDyFMTs+eS4ztZc52uJTY=
54
 github.com/panjf2000/ants/v2 v2.12.0/go.mod h1:tSQuaNQ6r6NRhPt+IZVUevvDyFMTs+eS4ztZc52uJTY=
60
 github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
55
 github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
61
 github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
56
 github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
62
-github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
63
-github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
57
+github.com/pelletier/go-toml/v2 v2.3.0 h1:k59bC/lIZREW0/iVaQR8nDHxVq8OVlIzYCOJf421CaM=
58
+github.com/pelletier/go-toml/v2 v2.3.0/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
64
 github.com/pires/go-proxyproto v0.11.0 h1:gUQpS85X/VJMdUsYyEgyn59uLJvGqPhJV5YvG68wXH4=
59
 github.com/pires/go-proxyproto v0.11.0 h1:gUQpS85X/VJMdUsYyEgyn59uLJvGqPhJV5YvG68wXH4=
65
 github.com/pires/go-proxyproto v0.11.0/go.mod h1:ZKAAyp3cgy5Y5Mo4n9AlScrkCZwUy0g3Jf+slqQVcuU=
60
 github.com/pires/go-proxyproto v0.11.0/go.mod h1:ZKAAyp3cgy5Y5Mo4n9AlScrkCZwUy0g3Jf+slqQVcuU=
66
-github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
67
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
61
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
68
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
62
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
69
 github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
63
 github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
76
 github.com/prometheus/procfs v0.20.1/go.mod h1:o9EMBZGRyvDrSPH1RqdxhojkuXstoe4UlK79eF5TGGo=
70
 github.com/prometheus/procfs v0.20.1/go.mod h1:o9EMBZGRyvDrSPH1RqdxhojkuXstoe4UlK79eF5TGGo=
77
 github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
71
 github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
78
 github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
72
 github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
79
-github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
80
-github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY=
81
-github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ=
73
+github.com/rs/zerolog v1.35.0 h1:VD0ykx7HMiMJytqINBsKcbLS+BJ4WYjz+05us+LRTdI=
74
+github.com/rs/zerolog v1.35.0/go.mod h1:EjML9kdfa/RMA7h/6z6pYmq1ykOuA8/mjWaEvGI+jcw=
82
 github.com/smira/go-statsd v1.3.4 h1:kBYWcLSGT+qC6JVbvfz48kX7mQys32fjDOPrfmsSx2c=
75
 github.com/smira/go-statsd v1.3.4 h1:kBYWcLSGT+qC6JVbvfz48kX7mQys32fjDOPrfmsSx2c=
83
 github.com/smira/go-statsd v1.3.4/go.mod h1:RjdsESPgDODtg1VpVVf9MJrEW2Hw0wtRNbmB1CAhu6A=
76
 github.com/smira/go-statsd v1.3.4/go.mod h1:RjdsESPgDODtg1VpVVf9MJrEW2Hw0wtRNbmB1CAhu6A=
84
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
77
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
133
 golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
126
 golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
134
 golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
127
 golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
135
 golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
128
 golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
136
-golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
137
 golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
129
 golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
138
 golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
130
 golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
139
-golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
140
 golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
131
 golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
141
 golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
132
 golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
142
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
133
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=

+ 6
- 0
internal/cli/access.go Прегледај датотеку

58
 
58
 
59
 	wg.Go(func() {
59
 	wg.Go(func() {
60
 		ip := a.PublicIPv4
60
 		ip := a.PublicIPv4
61
+		if ip == nil {
62
+			ip = conf.PublicIPv4.Get(nil)
63
+		}
61
 		if ip == nil {
64
 		if ip == nil {
62
 			ip = getIP(ntw, "tcp4")
65
 			ip = getIP(ntw, "tcp4")
63
 		}
66
 		}
70
 	})
73
 	})
71
 	wg.Go(func() {
74
 	wg.Go(func() {
72
 		ip := a.PublicIPv6
75
 		ip := a.PublicIPv6
76
+		if ip == nil {
77
+			ip = conf.PublicIPv6.Get(nil)
78
+		}
73
 		if ip == nil {
79
 		if ip == nil {
74
 			ip = getIP(ntw, "tcp6")
80
 			ip = getIP(ntw, "tcp6")
75
 		}
81
 		}

+ 10
- 3
internal/cli/doctor.go Прегледај датотеку

332
 		return false
332
 		return false
333
 	}
333
 	}
334
 
334
 
335
-	ourIP4 := getIP(ntw, "tcp4")
336
-	ourIP6 := getIP(ntw, "tcp6")
335
+	ourIP4 := d.conf.PublicIPv4.Get(nil)
336
+	if ourIP4 == nil {
337
+		ourIP4 = getIP(ntw, "tcp4")
338
+	}
339
+
340
+	ourIP6 := d.conf.PublicIPv6.Get(nil)
341
+	if ourIP6 == nil {
342
+		ourIP6 = getIP(ntw, "tcp6")
343
+	}
337
 
344
 
338
 	if ourIP4 == nil && ourIP6 == nil {
345
 	if ourIP4 == nil && ourIP6 == nil {
339
 		tplError.Execute(os.Stdout, map[string]any{ //nolint: errcheck
346
 		tplError.Execute(os.Stdout, map[string]any{ //nolint: errcheck
340
 			"description": "cannot detect public IP address",
347
 			"description": "cannot detect public IP address",
341
-			"error":       errors.New("ifconfig.co is unreachable for both IPv4 and IPv6"),
348
+			"error":       errors.New("cannot detect automatically and public-ipv4/public-ipv6 are not set in config"),
342
 		})
349
 		})
343
 		return false
350
 		return false
344
 	}
351
 	}

+ 2
- 0
internal/cli/run_proxy.go Прегледај датотеку

5
 	"fmt"
5
 	"fmt"
6
 	"net"
6
 	"net"
7
 	"os"
7
 	"os"
8
+	"time"
8
 
9
 
9
 	"github.com/9seconds/mtg/v2/antireplay"
10
 	"github.com/9seconds/mtg/v2/antireplay"
10
 	"github.com/9seconds/mtg/v2/events"
11
 	"github.com/9seconds/mtg/v2/events"
262
 
263
 
263
 		AllowFallbackOnUnknownDC: conf.AllowFallbackOnUnknownDC.Get(false),
264
 		AllowFallbackOnUnknownDC: conf.AllowFallbackOnUnknownDC.Get(false),
264
 		TolerateTimeSkewness:     conf.TolerateTimeSkewness.Value,
265
 		TolerateTimeSkewness:     conf.TolerateTimeSkewness.Value,
266
+		IdleTimeout:              conf.Network.Timeout.Idle.Get(time.Minute),
265
 
267
 
266
 		DoppelGangerURLs:    doppelGangerURLs,
268
 		DoppelGangerURLs:    doppelGangerURLs,
267
 		DoppelGangerPerRaid: conf.Defense.Doppelganger.Repeats.Get(mtglib.DoppelGangerPerRaid),
269
 		DoppelGangerPerRaid: conf.Defense.Doppelganger.Repeats.Get(mtglib.DoppelGangerPerRaid),

+ 6
- 4
internal/config/config.go Прегледај датотеку

35
 	DomainFrontingProxyProtocol TypeBool        `json:"domainFrontingProxyProtocol"`
35
 	DomainFrontingProxyProtocol TypeBool        `json:"domainFrontingProxyProtocol"`
36
 	TolerateTimeSkewness        TypeDuration    `json:"tolerateTimeSkewness"`
36
 	TolerateTimeSkewness        TypeDuration    `json:"tolerateTimeSkewness"`
37
 	Concurrency                 TypeConcurrency `json:"concurrency"`
37
 	Concurrency                 TypeConcurrency `json:"concurrency"`
38
+	PublicIPv4                  TypeIP          `json:"publicIpv4"`
39
+	PublicIPv6                  TypeIP          `json:"publicIpv6"`
38
 	DomainFronting              struct {
40
 	DomainFronting              struct {
39
 		IP            TypeIP   `json:"ip"`
41
 		IP            TypeIP   `json:"ip"`
40
 		Port          TypePort `json:"port"`
42
 		Port          TypePort `json:"port"`
50
 		Blocklist    ListConfig `json:"blocklist"`
52
 		Blocklist    ListConfig `json:"blocklist"`
51
 		Allowlist    ListConfig `json:"allowlist"`
53
 		Allowlist    ListConfig `json:"allowlist"`
52
 		Doppelganger struct {
54
 		Doppelganger struct {
53
-			URLs       []TypeHttpsURL  `json:"urls"`
54
-			Repeats    TypeConcurrency `json:"repeats_per_raid"`
55
-			UpdateEach TypeDuration    `json:"raid_each"`
56
-			DRS        TypeBool        `json:"drs"`
55
+			URLs            []TypeHttpsURL  `json:"urls"`
56
+			Repeats         TypeConcurrency `json:"repeats_per_raid"`
57
+			UpdateEach      TypeDuration    `json:"raid_each"`
58
+			DRS             TypeBool        `json:"drs"`
57
 		} `json:"doppelganger"`
59
 		} `json:"doppelganger"`
58
 	} `json:"defense"`
60
 	} `json:"defense"`
59
 	Network struct {
61
 	Network struct {

+ 26
- 0
internal/config/config_test.go Прегледај датотеку

42
 	suite.Equal("0.0.0.0:3128", conf.BindTo.String())
42
 	suite.Equal("0.0.0.0:3128", conf.BindTo.String())
43
 }
43
 }
44
 
44
 
45
+func (suite *ConfigTestSuite) TestParsePublicIP() {
46
+	conf, err := config.Parse(suite.ReadConfig("public_ip.toml"))
47
+	suite.NoError(err)
48
+	suite.Equal("203.0.113.1", conf.PublicIPv4.Get(nil).String())
49
+	suite.Equal("2001:db8::1", conf.PublicIPv6.Get(nil).String())
50
+}
51
+
52
+func (suite *ConfigTestSuite) TestParsePublicIPv4Only() {
53
+	conf, err := config.Parse(suite.ReadConfig("public_ip_v4_only.toml"))
54
+	suite.NoError(err)
55
+	suite.Equal("203.0.113.1", conf.PublicIPv4.Get(nil).String())
56
+	suite.Nil(conf.PublicIPv6.Get(nil))
57
+}
58
+
59
+func (suite *ConfigTestSuite) TestParsePublicIPInvalid() {
60
+	_, err := config.Parse(suite.ReadConfig("public_ip_invalid.toml"))
61
+	suite.Error(err)
62
+}
63
+
64
+func (suite *ConfigTestSuite) TestParsePublicIPNotSet() {
65
+	conf, err := config.Parse(suite.ReadConfig("minimal.toml"))
66
+	suite.NoError(err)
67
+	suite.Nil(conf.PublicIPv4.Get(nil))
68
+	suite.Nil(conf.PublicIPv6.Get(nil))
69
+}
70
+
45
 func (suite *ConfigTestSuite) TestString() {
71
 func (suite *ConfigTestSuite) TestString() {
46
 	conf, err := config.Parse(suite.ReadConfig("minimal.toml"))
72
 	conf, err := config.Parse(suite.ReadConfig("minimal.toml"))
47
 	suite.NoError(err)
73
 	suite.NoError(err)

+ 6
- 4
internal/config/parse.go Прегледај датотеку

21
 	DomainFrontingProxyProtocol bool   `toml:"domain-fronting-proxy-protocol" json:"domainFrontingProxyProtocol,omitempty"`
21
 	DomainFrontingProxyProtocol bool   `toml:"domain-fronting-proxy-protocol" json:"domainFrontingProxyProtocol,omitempty"`
22
 	TolerateTimeSkewness        string `toml:"tolerate-time-skewness" json:"tolerateTimeSkewness,omitempty"`
22
 	TolerateTimeSkewness        string `toml:"tolerate-time-skewness" json:"tolerateTimeSkewness,omitempty"`
23
 	Concurrency                 uint   `toml:"concurrency" json:"concurrency,omitempty"`
23
 	Concurrency                 uint   `toml:"concurrency" json:"concurrency,omitempty"`
24
+	PublicIPv4                  string `toml:"public-ipv4" json:"publicIpv4,omitempty"`
25
+	PublicIPv6                  string `toml:"public-ipv6" json:"publicIpv6,omitempty"`
24
 	DomainFronting              struct {
26
 	DomainFronting              struct {
25
 		IP            string `toml:"ip" json:"ip,omitempty"`
27
 		IP            string `toml:"ip" json:"ip,omitempty"`
26
 		Port          uint   `toml:"port" json:"port,omitempty"`
28
 		Port          uint   `toml:"port" json:"port,omitempty"`
45
 			UpdateEach          string   `toml:"update-each" json:"updateEach,omitempty"`
47
 			UpdateEach          string   `toml:"update-each" json:"updateEach,omitempty"`
46
 		} `toml:"allowlist" json:"allowlist,omitempty"`
48
 		} `toml:"allowlist" json:"allowlist,omitempty"`
47
 		Doppelganger struct {
49
 		Doppelganger struct {
48
-			URLs       []string `toml:"urls" json:"urls,omitempty"`
49
-			Repeats    uint     `toml:"repeats-per-raid" json:"repeats_per_raid,omitempty"`
50
-			UpdateEach string   `toml:"raid-each" json:"raid_each,omitempty"`
51
-			DRS        bool     `toml:"drs" json:"drs,omitempty"`
50
+			URLs            []string `toml:"urls" json:"urls,omitempty"`
51
+			Repeats         uint     `toml:"repeats-per-raid" json:"repeats_per_raid,omitempty"`
52
+			UpdateEach      string   `toml:"raid-each" json:"raid_each,omitempty"`
53
+			DRS             bool     `toml:"drs" json:"drs,omitempty"`
52
 		} `toml:"doppelganger" json:"doppelganger,omitempty"`
54
 		} `toml:"doppelganger" json:"doppelganger,omitempty"`
53
 	} `toml:"defense" json:"defense,omitempty"`
55
 	} `toml:"defense" json:"defense,omitempty"`
54
 	Network struct {
56
 	Network struct {

+ 4
- 0
internal/config/testdata/public_ip.toml Прегледај датотеку

1
+secret = "7oe1GqLy6TBc38CV3jx7q09nb29nbGUuY29t"
2
+bind-to = "0.0.0.0:3128"
3
+public-ipv4 = "203.0.113.1"
4
+public-ipv6 = "2001:db8::1"

+ 3
- 0
internal/config/testdata/public_ip_invalid.toml Прегледај датотеку

1
+secret = "7oe1GqLy6TBc38CV3jx7q09nb29nbGUuY29t"
2
+bind-to = "0.0.0.0:3128"
3
+public-ipv4 = "not-an-ip"

+ 3
- 0
internal/config/testdata/public_ip_v4_only.toml Прегледај датотеку

1
+secret = "7oe1GqLy6TBc38CV3jx7q09nb29nbGUuY29t"
2
+bind-to = "0.0.0.0:3128"
3
+public-ipv4 = "203.0.113.1"

+ 19
- 0
mtglib/conns.go Прегледај датотеку

6
 	"fmt"
6
 	"fmt"
7
 	"io"
7
 	"io"
8
 	"net"
8
 	"net"
9
+	"time"
9
 
10
 
10
 	"github.com/9seconds/mtg/v2/essentials"
11
 	"github.com/9seconds/mtg/v2/essentials"
11
 	"github.com/pires/go-proxyproto"
12
 	"github.com/pires/go-proxyproto"
95
 		sourceAddr: source.RemoteAddr(),
96
 		sourceAddr: source.RemoteAddr(),
96
 	}
97
 	}
97
 }
98
 }
99
+
100
+type connIdleTimeout struct {
101
+	essentials.Conn
102
+
103
+	timeout time.Duration
104
+}
105
+
106
+func (c connIdleTimeout) Read(b []byte) (int, error) {
107
+	c.SetReadDeadline(time.Now().Add(c.timeout)) //nolint: errcheck
108
+
109
+	return c.Conn.Read(b) //nolint: wrapcheck
110
+}
111
+
112
+func (c connIdleTimeout) Write(b []byte) (int, error) {
113
+	c.SetWriteDeadline(time.Now().Add(c.timeout)) //nolint: errcheck
114
+
115
+	return c.Conn.Write(b) //nolint: wrapcheck
116
+}

+ 0
- 35
mtglib/internal/doppel/clock.go Прегледај датотеку

1
-package doppel
2
-
3
-import (
4
-	"context"
5
-	"time"
6
-)
7
-
8
-type Clock struct {
9
-	stats *Stats
10
-	tick  chan struct{}
11
-}
12
-
13
-func (c Clock) Start(ctx context.Context) {
14
-	tickTock := time.NewTimer(c.stats.Delay())
15
-	defer func() {
16
-		tickTock.Stop()
17
-		select {
18
-		case <-tickTock.C:
19
-		default:
20
-		}
21
-	}()
22
-
23
-	for {
24
-		select {
25
-		case <-ctx.Done():
26
-			return
27
-		case <-tickTock.C:
28
-			select {
29
-			case <-ctx.Done():
30
-			case c.tick <- struct{}{}:
31
-			}
32
-			tickTock.Reset(c.stats.Delay())
33
-		}
34
-	}
35
-}

+ 0
- 80
mtglib/internal/doppel/clock_test.go Прегледај датотеку

1
-package doppel
2
-
3
-import (
4
-	"context"
5
-	"sync"
6
-	"testing"
7
-	"time"
8
-
9
-	"github.com/stretchr/testify/suite"
10
-)
11
-
12
-type ClockTestSuite struct {
13
-	suite.Suite
14
-
15
-	clock     Clock
16
-	wg        sync.WaitGroup
17
-	ctx       context.Context
18
-	ctxCancel context.CancelFunc
19
-}
20
-
21
-func (suite *ClockTestSuite) SetupTest() {
22
-	ctx, cancel := context.WithCancel(context.Background())
23
-
24
-	suite.ctx = ctx
25
-	suite.ctxCancel = cancel
26
-	suite.clock = Clock{
27
-		stats: &Stats{
28
-			k:      StatsDefaultK,
29
-			lambda: StatsDefaultLambda,
30
-		},
31
-		tick: make(chan struct{}),
32
-	}
33
-
34
-	suite.wg.Go(func() {
35
-		suite.clock.Start(suite.ctx)
36
-	})
37
-}
38
-
39
-func (suite *ClockTestSuite) TearDownTest() {
40
-	suite.ctxCancel()
41
-	suite.wg.Wait()
42
-}
43
-
44
-func (suite *ClockTestSuite) TestTicks() {
45
-	received := 0
46
-
47
-	for range 3 {
48
-		select {
49
-		case <-suite.clock.tick:
50
-			received++
51
-		case <-time.After(2 * time.Second):
52
-			suite.Fail("timed out waiting for tick")
53
-		}
54
-	}
55
-
56
-	suite.Equal(3, received)
57
-}
58
-
59
-func (suite *ClockTestSuite) TestStopsOnCancel() {
60
-	select {
61
-	case <-suite.clock.tick:
62
-	case <-time.After(2 * time.Second):
63
-		suite.Fail("timed out waiting for first tick")
64
-	}
65
-
66
-	suite.ctxCancel()
67
-
68
-	time.Sleep(50 * time.Millisecond)
69
-
70
-	select {
71
-	case <-suite.clock.tick:
72
-		suite.Fail("received tick after cancel")
73
-	default:
74
-	}
75
-}
76
-
77
-func TestClock(t *testing.T) {
78
-	t.Parallel()
79
-	suite.Run(t, &ClockTestSuite{})
80
-}

+ 21
- 19
mtglib/internal/doppel/conn.go Прегледај датотеку

4
 	"bytes"
4
 	"bytes"
5
 	"context"
5
 	"context"
6
 	"sync"
6
 	"sync"
7
+	"time"
7
 
8
 
8
 	"github.com/9seconds/mtg/v2/essentials"
9
 	"github.com/9seconds/mtg/v2/essentials"
9
 	"github.com/9seconds/mtg/v2/mtglib/internal/tls"
10
 	"github.com/9seconds/mtg/v2/mtglib/internal/tls"
10
 )
11
 )
11
 
12
 
13
+var doppelBufPool = sync.Pool{
14
+	New: func() any {
15
+		b := make([]byte, tls.MaxRecordSize)
16
+		return &b
17
+	},
18
+}
19
+
12
 type Conn struct {
20
 type Conn struct {
13
 	essentials.Conn
21
 	essentials.Conn
14
 
22
 
18
 type connPayload struct {
26
 type connPayload struct {
19
 	ctx         context.Context
27
 	ctx         context.Context
20
 	ctxCancel   context.CancelCauseFunc
28
 	ctxCancel   context.CancelCauseFunc
21
-	clock       Clock
29
+	stats       Stats
22
 	wg          sync.WaitGroup
30
 	wg          sync.WaitGroup
23
 	writeStream bytes.Buffer
31
 	writeStream bytes.Buffer
24
 	writtenCond sync.Cond
32
 	writtenCond sync.Cond
39
 	return len(p), context.Cause(c.p.ctx)
47
 	return len(p), context.Cause(c.p.ctx)
40
 }
48
 }
41
 
49
 
42
-func (c Conn) Start() {
43
-	c.p.wg.Go(func() {
44
-		c.start()
45
-	})
46
-}
47
-
48
 func (c Conn) start() {
50
 func (c Conn) start() {
49
-	buf := [tls.MaxRecordSize]byte{}
51
+	bp := doppelBufPool.Get().(*[]byte)
52
+	buf := *bp
53
+	defer doppelBufPool.Put(bp)
54
+
55
+	timer := time.NewTimer(c.p.stats.Delay())
56
+	defer timer.Stop()
50
 
57
 
51
 	for {
58
 	for {
52
 		select {
59
 		select {
53
 		case <-c.p.ctx.Done():
60
 		case <-c.p.ctx.Done():
54
 			return
61
 			return
55
-		case <-c.p.clock.tick:
62
+		case <-timer.C:
63
+			timer.Reset(c.p.stats.Delay())
56
 		}
64
 		}
57
 
65
 
58
-		size := c.p.clock.stats.Size()
66
+		size := c.p.stats.Size()
59
 
67
 
60
 		c.p.writtenCond.L.Lock()
68
 		c.p.writtenCond.L.Lock()
61
 		for c.p.writeStream.Len() == 0 && !c.p.done {
69
 		for c.p.writeStream.Len() == 0 && !c.p.done {
68
 			continue
76
 			continue
69
 		}
77
 		}
70
 
78
 
71
-		if err := tls.WriteRecordInPlace(c.Conn, buf[:], n); err != nil {
79
+		if err := tls.WriteRecordInPlace(c.Conn, buf, n); err != nil {
72
 			c.p.ctxCancel(err)
80
 			c.p.ctxCancel(err)
73
 			return
81
 			return
74
 		}
82
 		}
86
 	c.p.wg.Wait()
94
 	c.p.wg.Wait()
87
 }
95
 }
88
 
96
 
89
-func NewConn(ctx context.Context, conn essentials.Conn, stats *Stats) Conn {
97
+func NewConn(ctx context.Context, conn essentials.Conn, stats Stats) Conn {
90
 	ctx, cancel := context.WithCancelCause(ctx)
98
 	ctx, cancel := context.WithCancelCause(ctx)
91
 	rv := Conn{
99
 	rv := Conn{
92
 		Conn: conn,
100
 		Conn: conn,
93
 		p: &connPayload{
101
 		p: &connPayload{
94
 			ctx:       ctx,
102
 			ctx:       ctx,
95
 			ctxCancel: cancel,
103
 			ctxCancel: cancel,
104
+			stats:     stats,
96
 			writtenCond: sync.Cond{
105
 			writtenCond: sync.Cond{
97
 				L: &sync.Mutex{},
106
 				L: &sync.Mutex{},
98
 			},
107
 			},
99
-			clock: Clock{
100
-				stats: stats,
101
-				tick:  make(chan struct{}),
102
-			},
103
 		},
108
 		},
104
 	}
109
 	}
105
 
110
 
106
 	rv.p.writeStream.Grow(tls.DefaultBufferSize)
111
 	rv.p.writeStream.Grow(tls.DefaultBufferSize)
107
 
112
 
108
-	rv.p.wg.Go(func() {
109
-		rv.p.clock.Start(ctx)
110
-	})
111
 	rv.p.wg.Go(func() {
113
 	rv.p.wg.Go(func() {
112
 		rv.start()
114
 		rv.start()
113
 	})
115
 	})

+ 2
- 2
mtglib/internal/doppel/conn_test.go Прегледај датотеку

63
 }
63
 }
64
 
64
 
65
 func (suite *ConnTestSuite) makeConn() Conn {
65
 func (suite *ConnTestSuite) makeConn() Conn {
66
-	return NewConn(suite.ctx, suite.connMock, &Stats{
66
+	return NewConn(suite.ctx, suite.connMock, Stats{
67
 		k:      2.0,
67
 		k:      2.0,
68
 		lambda: 0.01,
68
 		lambda: 0.01,
69
 	})
69
 	})
152
 			ctx, cancel := context.WithCancel(suite.ctx)
152
 			ctx, cancel := context.WithCancel(suite.ctx)
153
 			defer cancel()
153
 			defer cancel()
154
 
154
 
155
-			c := NewConn(ctx, suite.connMock, &Stats{
155
+			c := NewConn(ctx, suite.connMock, Stats{
156
 				k:      2.0,
156
 				k:      2.0,
157
 				lambda: 0.01,
157
 				lambda: 0.01,
158
 			})
158
 			})

+ 92
- 10
mtglib/internal/doppel/ganger.go Прегледај датотеку

2
 
2
 
3
 import (
3
 import (
4
 	"context"
4
 	"context"
5
+	"fmt"
5
 	"sync"
6
 	"sync"
7
+	"sync/atomic"
6
 	"time"
8
 	"time"
7
 
9
 
8
 	"github.com/9seconds/mtg/v2/essentials"
10
 	"github.com/9seconds/mtg/v2/essentials"
12
 	DoppelGangerMaxDurations  = 4096
14
 	DoppelGangerMaxDurations  = 4096
13
 	DoppelGangerScoutRaidEach = 6 * time.Hour
15
 	DoppelGangerScoutRaidEach = 6 * time.Hour
14
 	DoppelGangerScoutRepeats  = 10
16
 	DoppelGangerScoutRepeats  = 10
17
+
18
+	MinCertSizesToCalculate = 3
15
 )
19
 )
16
 
20
 
21
+// NoiseParams holds the measured cert chain size for FakeTLS noise calibration.
22
+// If Mean is 0, the caller should use a legacy fallback.
23
+type NoiseParams struct {
24
+	Mean   int
25
+	Jitter int
26
+}
27
+
28
+type scoutRaidResult struct {
29
+	durations []time.Duration
30
+	certSizes []int
31
+}
32
+
17
 type gangerConnRequest struct {
33
 type gangerConnRequest struct {
18
 	ret     chan<- Conn
34
 	ret     chan<- Conn
19
 	payload essentials.Conn
35
 	payload essentials.Conn
31
 
47
 
32
 	drs bool
48
 	drs bool
33
 
49
 
34
-	stats     *Stats
50
+	stats     Stats
35
 	durations []time.Duration
51
 	durations []time.Duration
52
+	certSizes []int
53
+
54
+	noiseParams atomic.Pointer[NoiseParams]
36
 
55
 
37
 	connRequests chan gangerConnRequest
56
 	connRequests chan gangerConnRequest
38
 }
57
 }
48
 	})
67
 	})
49
 }
68
 }
50
 
69
 
70
+// NoiseParams returns the current cert-size-based noise parameters.
71
+// Returns zero-value NoiseParams if not yet measured (caller should use fallback).
72
+func (g *Ganger) NoiseParams() NoiseParams {
73
+	if p := g.noiseParams.Load(); p != nil {
74
+		return *p
75
+	}
76
+
77
+	return NoiseParams{}
78
+}
79
+
51
 func (g *Ganger) NewConn(conn essentials.Conn) (Conn, error) {
80
 func (g *Ganger) NewConn(conn essentials.Conn) (Conn, error) {
52
 	rvChan := make(chan Conn)
81
 	rvChan := make(chan Conn)
53
 	req := gangerConnRequest{
82
 	req := gangerConnRequest{
81
 		}
110
 		}
82
 	}()
111
 	}()
83
 
112
 
84
-	scoutCollectedChan := make(chan []time.Duration)
113
+	scoutCollectedChan := make(chan scoutRaidResult)
85
 	currentScoutCollectedChan := scoutCollectedChan
114
 	currentScoutCollectedChan := scoutCollectedChan
86
 
115
 
87
-	updatedStatsChan := make(chan *Stats)
116
+	updatedStatsChan := make(chan Stats)
88
 
117
 
89
 	g.wg.Go(func() {
118
 	g.wg.Go(func() {
90
 		g.runScoutRaid(scoutCollectedChan)
119
 		g.runScoutRaid(scoutCollectedChan)
94
 		select {
123
 		select {
95
 		case <-g.ctx.Done():
124
 		case <-g.ctx.Done():
96
 			return
125
 			return
97
-		case durations := <-currentScoutCollectedChan:
98
-			g.durations = append(g.durations, durations...)
126
+		case result := <-currentScoutCollectedChan:
127
+			g.durations = append(g.durations, result.durations...)
99
 
128
 
100
 			if len(g.durations) > DoppelGangerMaxDurations {
129
 			if len(g.durations) > DoppelGangerMaxDurations {
101
 				copy(g.durations, g.durations[len(g.durations)-DoppelGangerMaxDurations:])
130
 				copy(g.durations, g.durations[len(g.durations)-DoppelGangerMaxDurations:])
102
 				g.durations = g.durations[:DoppelGangerMaxDurations]
131
 				g.durations = g.durations[:DoppelGangerMaxDurations]
103
 			}
132
 			}
104
 
133
 
134
+			// Update cert sizes and recompute noise params.
135
+			g.certSizes = append(g.certSizes, result.certSizes...)
136
+			if len(g.certSizes) > DoppelGangerMaxDurations {
137
+				g.certSizes = g.certSizes[len(g.certSizes)-DoppelGangerMaxDurations:]
138
+			}
139
+
140
+			if len(g.certSizes) >= MinCertSizesToCalculate {
141
+				g.updateNoiseParams()
142
+			}
143
+
105
 			if len(g.durations) < MinDurationsToCalculate {
144
 			if len(g.durations) < MinDurationsToCalculate {
106
 				continue
145
 				continue
107
 			}
146
 			}
108
 
147
 
148
+			durations := g.durations
109
 			currentScoutCollectedChan = nil
149
 			currentScoutCollectedChan = nil
110
 			g.wg.Go(func() {
150
 			g.wg.Go(func() {
111
 				select {
151
 				select {
129
 	}
169
 	}
130
 }
170
 }
131
 
171
 
132
-func (g *Ganger) runScoutRaid(rvChan chan<- []time.Duration) {
133
-	durations := []time.Duration{}
172
+func (g *Ganger) updateNoiseParams() {
173
+	if len(g.certSizes) == 0 {
174
+		return
175
+	}
176
+
177
+	sum := 0
178
+	for _, s := range g.certSizes {
179
+		sum += s
180
+	}
181
+
182
+	mean := sum / len(g.certSizes)
183
+
184
+	maxDev := 0
185
+	for _, s := range g.certSizes {
186
+		d := s - mean
187
+		if d < 0 {
188
+			d = -d
189
+		}
190
+
191
+		if d > maxDev {
192
+			maxDev = d
193
+		}
194
+	}
195
+
196
+	if maxDev < 100 {
197
+		maxDev = 100
198
+	}
199
+
200
+	np := &NoiseParams{Mean: mean, Jitter: maxDev}
201
+	g.noiseParams.Store(np)
202
+
203
+	g.logger.Info(fmt.Sprintf(
204
+		"updated noise params: mean=%d jitter=%d samples=%d",
205
+		mean, maxDev, len(g.certSizes),
206
+	))
207
+}
208
+
209
+func (g *Ganger) runScoutRaid(rvChan chan<- scoutRaidResult) {
210
+	var result scoutRaidResult
134
 
211
 
135
 	for range g.scoutRaidRepeats {
212
 	for range g.scoutRaidRepeats {
136
 		learned, err := g.scout.Learn(g.ctx)
213
 		learned, err := g.scout.Learn(g.ctx)
138
 			g.logger.WarningError("cannot learn", err)
215
 			g.logger.WarningError("cannot learn", err)
139
 			continue
216
 			continue
140
 		}
217
 		}
141
-		durations = append(durations, learned...)
218
+
219
+		result.durations = append(result.durations, learned.Durations...)
220
+
221
+		if learned.CertSize > 0 {
222
+			result.certSizes = append(result.certSizes, learned.CertSize)
223
+		}
142
 	}
224
 	}
143
 
225
 
144
 	select {
226
 	select {
145
 	case <-g.ctx.Done():
227
 	case <-g.ctx.Done():
146
 		return
228
 		return
147
-	case rvChan <- durations:
229
+	case rvChan <- result:
148
 	}
230
 	}
149
 }
231
 }
150
 
232
 
174
 		scoutRaidEach:    scoutEach,
256
 		scoutRaidEach:    scoutEach,
175
 		scoutRaidRepeats: scoutRepeats,
257
 		scoutRaidRepeats: scoutRepeats,
176
 		drs:              drs,
258
 		drs:              drs,
177
-		stats: &Stats{
259
+		stats: Stats{
178
 			k:      StatsDefaultK,
260
 			k:      StatsDefaultK,
179
 			lambda: StatsDefaultLambda,
261
 			lambda: StatsDefaultLambda,
180
 			drs:    drs,
262
 			drs:    drs,

+ 47
- 12
mtglib/internal/doppel/scout.go Прегледај датотеку

12
 	"github.com/9seconds/mtg/v2/mtglib/internal/tls"
12
 	"github.com/9seconds/mtg/v2/mtglib/internal/tls"
13
 )
13
 )
14
 
14
 
15
+// ScoutResult holds measurements from a single scout HTTP request.
16
+type ScoutResult struct {
17
+	Durations []time.Duration
18
+	CertSize  int // total ApplicationData bytes during TLS handshake; 0 if unknown
19
+}
20
+
15
 type Scout struct {
21
 type Scout struct {
16
 	network Network
22
 	network Network
17
 	urls    []string
23
 	urls    []string
18
 }
24
 }
19
 
25
 
20
-func (s Scout) Learn(ctx context.Context) ([]time.Duration, error) {
21
-	var durations []time.Duration
26
+func (s Scout) Learn(ctx context.Context) (ScoutResult, error) {
27
+	var combined ScoutResult
22
 
28
 
23
 	for _, url := range s.urls {
29
 	for _, url := range s.urls {
24
 		learned, err := s.learn(ctx, url)
30
 		learned, err := s.learn(ctx, url)
25
 		if err != nil {
31
 		if err != nil {
26
-			return nil, err
32
+			return ScoutResult{}, err
27
 		}
33
 		}
28
 
34
 
29
-		durations = append(durations, learned...)
35
+		combined.Durations = append(combined.Durations, learned.Durations...)
36
+
37
+		if learned.CertSize > 0 && combined.CertSize == 0 {
38
+			combined.CertSize = learned.CertSize
39
+		}
30
 	}
40
 	}
31
 
41
 
32
-	return durations, nil
42
+	return combined, nil
33
 }
43
 }
34
 
44
 
35
-func (s Scout) learn(ctx context.Context, url string) ([]time.Duration, error) {
45
+func (s Scout) learn(ctx context.Context, url string) (ScoutResult, error) {
36
 	client, results := s.makeClient()
46
 	client, results := s.makeClient()
37
 
47
 
38
 	if !strings.HasPrefix(url, "https://") {
48
 	if !strings.HasPrefix(url, "https://") {
39
-		return nil, fmt.Errorf("url %s must be https", url)
49
+		return ScoutResult{}, fmt.Errorf("url %s must be https", url)
40
 	}
50
 	}
41
 
51
 
42
 	req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
52
 	req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
43
 	if err != nil {
53
 	if err != nil {
44
-		return nil, err
54
+		return ScoutResult{}, err
45
 	}
55
 	}
46
 
56
 
47
 	resp, err := client.Do(req)
57
 	resp, err := client.Do(req)
52
 	}
62
 	}
53
 
63
 
54
 	if err != nil || len(results.data) == 0 {
64
 	if err != nil || len(results.data) == 0 {
55
-		return nil, err
65
+		return ScoutResult{}, err
56
 	}
66
 	}
57
 
67
 
58
-	durations := []time.Duration{}
68
+	var result ScoutResult
69
+
70
+	// Compute inter-record durations (existing logic).
59
 	lastTimestamp := time.Time{}
71
 	lastTimestamp := time.Time{}
60
 
72
 
61
 	for i, v := range results.data {
73
 	for i, v := range results.data {
71
 			}
83
 			}
72
 		}
84
 		}
73
 
85
 
74
-		durations = append(durations, v.timestamp.Sub(lastTimestamp))
86
+		result.Durations = append(result.Durations, v.timestamp.Sub(lastTimestamp))
75
 		lastTimestamp = v.timestamp
87
 		lastTimestamp = v.timestamp
76
 	}
88
 	}
77
 
89
 
78
-	return durations, nil
90
+	// Compute cert size: sum of ApplicationData payload between CCS and
91
+	// the first client Write (which marks the end of server handshake).
92
+	seenCCS := false
93
+	boundary := results.writeIndex
94
+	if boundary < 0 {
95
+		boundary = len(results.data)
96
+	}
97
+
98
+	for i, v := range results.data {
99
+		if i >= boundary {
100
+			break
101
+		}
102
+
103
+		if v.recordType == tls.TypeChangeCipherSpec {
104
+			seenCCS = true
105
+			continue
106
+		}
107
+
108
+		if seenCCS && v.recordType == tls.TypeApplicationData {
109
+			result.CertSize += v.payloadLen
110
+		}
111
+	}
112
+
113
+	return result, nil
79
 }
114
 }
80
 
115
 
81
 func (s Scout) makeClient() (*http.Client, *ScoutConnCollected) {
116
 func (s Scout) makeClient() (*http.Client, *ScoutConnCollected) {

+ 17
- 4
mtglib/internal/doppel/scout_conn.go Прегледај датотеку

14
 
14
 
15
 	results *ScoutConnCollected
15
 	results *ScoutConnCollected
16
 	rawBuf  *bytes.Buffer
16
 	rawBuf  *bytes.Buffer
17
+	seenCCS bool
17
 }
18
 }
18
 
19
 
19
-func (s ScoutConn) Read(p []byte) (int, error) {
20
+func (s *ScoutConn) Read(p []byte) (int, error) {
20
 	buf := &bytes.Buffer{}
21
 	buf := &bytes.Buffer{}
21
 
22
 
22
 	for {
23
 	for {
31
 			return 0, err
32
 			return 0, err
32
 		}
33
 		}
33
 
34
 
34
-		s.results.Add(recordType)
35
+		if recordType == tls.TypeChangeCipherSpec {
36
+			s.seenCCS = true
37
+		}
38
+
39
+		s.results.Add(recordType, int(length))
35
 		s.rawBuf.Write([]byte{recordType})
40
 		s.rawBuf.Write([]byte{recordType})
36
 		s.rawBuf.Write(tls.TLSVersion[:])
41
 		s.rawBuf.Write(tls.TLSVersion[:])
37
 
42
 
45
 	}
50
 	}
46
 }
51
 }
47
 
52
 
48
-func NewScoutConn(conn essentials.Conn, results *ScoutConnCollected) ScoutConn {
53
+func (s *ScoutConn) Write(p []byte) (int, error) {
54
+	if s.seenCCS {
55
+		s.results.MarkWrite()
56
+	}
57
+
58
+	return s.Conn.Write(p)
59
+}
60
+
61
+func NewScoutConn(conn essentials.Conn, results *ScoutConnCollected) *ScoutConn {
49
 	rawBuf := &bytes.Buffer{}
62
 	rawBuf := &bytes.Buffer{}
50
 	rawBuf.Grow(tls.MaxRecordSize)
63
 	rawBuf.Grow(tls.MaxRecordSize)
51
 
64
 
52
-	return ScoutConn{
65
+	return &ScoutConn{
53
 		Conn:    tls.New(conn, false, false),
66
 		Conn:    tls.New(conn, false, false),
54
 		results: results,
67
 		results: results,
55
 		rawBuf:  rawBuf,
68
 		rawBuf:  rawBuf,

+ 14
- 3
mtglib/internal/doppel/scout_conn_collected.go Прегледај датотеку

9
 type ScoutConnResult struct {
9
 type ScoutConnResult struct {
10
 	timestamp  time.Time
10
 	timestamp  time.Time
11
 	recordType byte
11
 	recordType byte
12
+	payloadLen int
12
 }
13
 }
13
 
14
 
14
 type ScoutConnCollected struct {
15
 type ScoutConnCollected struct {
15
-	data []ScoutConnResult
16
+	data       []ScoutConnResult
17
+	writeIndex int // index at which client first wrote post-handshake data; -1 if not set
16
 }
18
 }
17
 
19
 
18
-func (s *ScoutConnCollected) Add(record byte) {
20
+func (s *ScoutConnCollected) Add(record byte, payloadLen int) {
19
 	s.data = append(s.data, ScoutConnResult{
21
 	s.data = append(s.data, ScoutConnResult{
20
 		timestamp:  time.Now(),
22
 		timestamp:  time.Now(),
21
 		recordType: record,
23
 		recordType: record,
24
+		payloadLen: payloadLen,
22
 	})
25
 	})
23
 }
26
 }
24
 
27
 
28
+// MarkWrite records the current data length as the handshake boundary.
29
+func (s *ScoutConnCollected) MarkWrite() {
30
+	if s.writeIndex < 0 {
31
+		s.writeIndex = len(s.data)
32
+	}
33
+}
34
+
25
 func NewScoutConnCollected() *ScoutConnCollected {
35
 func NewScoutConnCollected() *ScoutConnCollected {
26
 	return &ScoutConnCollected{
36
 	return &ScoutConnCollected{
27
-		data: make([]ScoutConnResult, 0, ScoutConnCollectedPreallocSize),
37
+		data:       make([]ScoutConnResult, 0, ScoutConnCollectedPreallocSize),
38
+		writeIndex: -1,
28
 	}
39
 	}
29
 }
40
 }

+ 4
- 4
mtglib/internal/doppel/scout_conn_collected_test.go Прегледај датотеку

14
 
14
 
15
 func (suite *ScoutConnCollectedTestSuite) TestAddSingle() {
15
 func (suite *ScoutConnCollectedTestSuite) TestAddSingle() {
16
 	collected := NewScoutConnCollected()
16
 	collected := NewScoutConnCollected()
17
-	collected.Add(tls.TypeApplicationData)
17
+	collected.Add(tls.TypeApplicationData, 100)
18
 
18
 
19
 	suite.Len(collected.data, 1)
19
 	suite.Len(collected.data, 1)
20
 	suite.Equal(byte(tls.TypeApplicationData), collected.data[0].recordType)
20
 	suite.Equal(byte(tls.TypeApplicationData), collected.data[0].recordType)
23
 func (suite *ScoutConnCollectedTestSuite) TestAddTimestampsAreMonotonic() {
23
 func (suite *ScoutConnCollectedTestSuite) TestAddTimestampsAreMonotonic() {
24
 	collected := NewScoutConnCollected()
24
 	collected := NewScoutConnCollected()
25
 
25
 
26
-	collected.Add(tls.TypeApplicationData)
26
+	collected.Add(tls.TypeApplicationData, 100)
27
 
27
 
28
 	time.Sleep(time.Microsecond)
28
 	time.Sleep(time.Microsecond)
29
-	collected.Add(tls.TypeApplicationData)
29
+	collected.Add(tls.TypeApplicationData, 100)
30
 
30
 
31
 	time.Sleep(time.Microsecond)
31
 	time.Sleep(time.Microsecond)
32
-	collected.Add(tls.TypeApplicationData)
32
+	collected.Add(tls.TypeApplicationData, 100)
33
 
33
 
34
 	for i := 1; i < len(collected.data); i++ {
34
 	for i := 1; i < len(collected.data); i++ {
35
 		suite.True(collected.data[i].timestamp.After(collected.data[i-1].timestamp))
35
 		suite.True(collected.data[i].timestamp.After(collected.data[i-1].timestamp))

+ 2
- 2
mtglib/internal/doppel/scout_test.go Прегледај датотеку

22
 }
22
 }
23
 
23
 
24
 func (suite *ScoutTestSuite) TestCollectResults() {
24
 func (suite *ScoutTestSuite) TestCollectResults() {
25
-	durations, err := suite.scout.Learn(suite.ctx)
25
+	result, err := suite.scout.Learn(suite.ctx)
26
 	suite.NoError(err)
26
 	suite.NoError(err)
27
-	suite.Less(3, len(durations))
27
+	suite.Less(3, len(result.Durations))
28
 }
28
 }
29
 
29
 
30
 func (suite *ScoutTestSuite) TestCollectNothing() {
30
 func (suite *ScoutTestSuite) TestCollectNothing() {

+ 2
- 2
mtglib/internal/doppel/stats.go Прегледај датотеку

112
 	return TLSRecordSizeMax
112
 	return TLSRecordSizeMax
113
 }
113
 }
114
 
114
 
115
-func NewStats(durations []time.Duration, drs bool) *Stats {
115
+func NewStats(durations []time.Duration, drs bool) Stats {
116
 	n := float64(len(durations))
116
 	n := float64(len(durations))
117
 
117
 
118
 	// in milliseconds
118
 	// in milliseconds
162
 	// λ = (Σxᵢᵏ / n)^(1/k)
162
 	// λ = (Σxᵢᵏ / n)^(1/k)
163
 	lambda := math.Pow(sumXK/n, 1.0/k)
163
 	lambda := math.Pow(sumXK/n, 1.0/k)
164
 
164
 
165
-	return &Stats{
165
+	return Stats{
166
 		k:      k,
166
 		k:      k,
167
 		lambda: lambda,
167
 		lambda: lambda,
168
 		drs:    drs,
168
 		drs:    drs,

+ 13
- 0
mtglib/internal/relay/pool_settings_constrained.go Прегледај датотеку

1
+//go:build mips || mipsle
2
+
3
+package relay
4
+
5
+import "github.com/9seconds/mtg/v2/mtglib/internal/tls"
6
+
7
+const (
8
+	// MIPS is quite short in resources, and usually it means that it will run
9
+	// on Microtiks, OpenWRT-based routers or similar hardware. I think it worth
10
+	// to sacrifice a number of read syscalls (read, CPU load) to shrink
11
+	// limited RAM resources.
12
+	bufPoolSize = tls.MaxRecordPayloadSize / 2
13
+)

+ 9
- 0
mtglib/internal/relay/pool_settings_other.go Прегледај датотеку

1
+//go:build !mips && !mipsle
2
+
3
+package relay
4
+
5
+import "github.com/9seconds/mtg/v2/mtglib/internal/tls"
6
+
7
+const (
8
+	bufPoolSize = tls.MaxRecordPayloadSize
9
+)

+ 18
- 0
mtglib/internal/relay/pools.go Прегледај датотеку

1
+package relay
2
+
3
+import "sync"
4
+
5
+var bufPool = sync.Pool{
6
+	New: func() any {
7
+		b := make([]byte, bufPoolSize)
8
+		return &b
9
+	},
10
+}
11
+
12
+func acquireBuffer() *[]byte {
13
+	return bufPool.Get().(*[]byte)
14
+}
15
+
16
+func releaseBuffer(p *[]byte) {
17
+	bufPool.Put(p)
18
+}

+ 6
- 6
mtglib/internal/relay/relay.go Прегледај датотеку

6
 	"io"
6
 	"io"
7
 
7
 
8
 	"github.com/9seconds/mtg/v2/essentials"
8
 	"github.com/9seconds/mtg/v2/essentials"
9
-	"github.com/9seconds/mtg/v2/mtglib/internal/tls"
10
 )
9
 )
11
 
10
 
12
 func Relay(ctx context.Context, log Logger, telegramConn, clientConn essentials.Conn) {
11
 func Relay(ctx context.Context, log Logger, telegramConn, clientConn essentials.Conn) {
16
 	ctx, cancel := context.WithCancel(ctx)
15
 	ctx, cancel := context.WithCancel(ctx)
17
 	defer cancel()
16
 	defer cancel()
18
 
17
 
19
-	go func() {
20
-		<-ctx.Done()
18
+	stop := context.AfterFunc(ctx, func() {
21
 		telegramConn.Close() //nolint: errcheck
19
 		telegramConn.Close() //nolint: errcheck
22
 		clientConn.Close()   //nolint: errcheck
20
 		clientConn.Close()   //nolint: errcheck
23
-	}()
21
+	})
22
+	defer stop()
24
 
23
 
25
 	closeChan := make(chan struct{})
24
 	closeChan := make(chan struct{})
26
 
25
 
36
 }
35
 }
37
 
36
 
38
 func pump(log Logger, src, dst essentials.Conn, direction string) {
37
 func pump(log Logger, src, dst essentials.Conn, direction string) {
39
-	var buf [tls.MaxRecordPayloadSize]byte
38
+	buf := acquireBuffer()
39
+	defer releaseBuffer(buf)
40
 
40
 
41
 	defer src.CloseRead()  //nolint: errcheck
41
 	defer src.CloseRead()  //nolint: errcheck
42
 	defer dst.CloseWrite() //nolint: errcheck
42
 	defer dst.CloseWrite() //nolint: errcheck
43
 
43
 
44
-	n, err := io.CopyBuffer(src, dst, buf[:])
44
+	n, err := io.CopyBuffer(src, dst, *buf)
45
 
45
 
46
 	switch {
46
 	switch {
47
 	case err == nil:
47
 	case err == nil:

+ 34
- 15
mtglib/internal/tls/fake/server_side.go Прегледај датотеку

9
 	"io"
9
 	"io"
10
 	rnd "math/rand/v2"
10
 	rnd "math/rand/v2"
11
 
11
 
12
-	"github.com/9seconds/mtg/v2/mtglib/internal/doppel"
13
 	"github.com/9seconds/mtg/v2/mtglib/internal/tls"
12
 	"github.com/9seconds/mtg/v2/mtglib/internal/tls"
14
 	"golang.org/x/crypto/curve25519"
13
 	"golang.org/x/crypto/curve25519"
15
 )
14
 )
16
 
15
 
16
+// NoiseParams controls the size of the fake ApplicationData record
17
+// in ServerHello. If Mean is 0, the legacy random range (2500-4700)
18
+// is used.
19
+type NoiseParams struct {
20
+	Mean   int
21
+	Jitter int
22
+}
23
+
17
 const (
24
 const (
18
 	TypeHandshakeServer = 0x02
25
 	TypeHandshakeServer = 0x02
19
 	ChangeCipherValue   = 0x01
26
 	ChangeCipherValue   = 0x01
33
 	0x00, 0x20, // 32 bytes of key
40
 	0x00, 0x20, // 32 bytes of key
34
 }
41
 }
35
 
42
 
36
-func SendServerHello(w io.Writer, secret []byte, clientHello *ClientHello) error {
43
+func SendServerHello(w io.Writer, secret []byte, clientHello *ClientHello, noise NoiseParams) error {
37
 	buf := &bytes.Buffer{}
44
 	buf := &bytes.Buffer{}
38
 	buf.Grow(tls.MaxRecordSize)
45
 	buf.Grow(tls.MaxRecordSize)
39
 
46
 
40
 	generateServerHello(buf, clientHello)
47
 	generateServerHello(buf, clientHello)
41
 	generateChangeCipherValue(buf)
48
 	generateChangeCipherValue(buf)
42
-	generateNoise(buf)
49
+	generateNoise(buf, noise)
43
 
50
 
44
 	packet := buf.Bytes()
51
 	packet := buf.Bytes()
45
 	digest := hmac.New(sha256.New, secret)
52
 	digest := hmac.New(sha256.New, secret)
125
 	buf.WriteByte(ChangeCipherValue)
132
 	buf.WriteByte(ChangeCipherValue)
126
 }
133
 }
127
 
134
 
128
-func generateNoise(buf *bytes.Buffer) {
129
-	data := make(
130
-		[]byte,
131
-		int64(
132
-			doppel.TLSRecordSizeStart+rnd.IntN(
133
-				doppel.TLSRecordSizeAccel-doppel.TLSRecordSizeStart,
134
-			),
135
-		),
136
-	)
137
-
138
-	if _, err := rand.Read(data[:]); err != nil {
135
+// generateNoise writes a single ApplicationData record mimicking the combined
136
+// size of a real TLS 1.3 encrypted server handshake (EncryptedExtensions +
137
+// Certificate chain + CertificateVerify + Finished).
138
+//
139
+// NOTE: Must be exactly ONE ApplicationData record — the Telegram client reads
140
+// ServerHello + CCS + 1 ApplicationData and computes HMAC over all three.
141
+// Multiple records would cause HMAC mismatch and connection failure.
142
+func generateNoise(buf *bytes.Buffer, noise NoiseParams) {
143
+	var size int
144
+
145
+	if noise.Mean > 0 && noise.Jitter > 0 {
146
+		// Calibrated: use measured cert chain size ± jitter.
147
+		size = noise.Mean - noise.Jitter + rnd.IntN(2*noise.Jitter)
148
+		if size < 1000 {
149
+			size = 1000
150
+		}
151
+	} else {
152
+		// Legacy fallback: random in 2500-4700 range.
153
+		size = 2500 + rnd.IntN(2200)
154
+	}
155
+
156
+	data := make([]byte, size)
157
+	if _, err := rand.Read(data); err != nil {
139
 		panic(err)
158
 		panic(err)
140
 	}
159
 	}
141
 
160
 
142
-	tls.WriteRecord(buf, data[:]) //nolint: errcheck
161
+	tls.WriteRecord(buf, data) //nolint: errcheck
143
 }
162
 }

+ 32
- 6
mtglib/internal/tls/fake/server_side_test.go Прегледај датотеку

8
 	"testing"
8
 	"testing"
9
 
9
 
10
 	"github.com/9seconds/mtg/v2/mtglib"
10
 	"github.com/9seconds/mtg/v2/mtglib"
11
-	"github.com/9seconds/mtg/v2/mtglib/internal/doppel"
12
 	"github.com/9seconds/mtg/v2/mtglib/internal/tls"
11
 	"github.com/9seconds/mtg/v2/mtglib/internal/tls"
13
 	"github.com/9seconds/mtg/v2/mtglib/internal/tls/fake"
12
 	"github.com/9seconds/mtg/v2/mtglib/internal/tls/fake"
14
 	"github.com/stretchr/testify/suite"
13
 	"github.com/stretchr/testify/suite"
39
 }
38
 }
40
 
39
 
41
 func (suite *SendServerHelloTestSuite) TestRecordStructure() {
40
 func (suite *SendServerHelloTestSuite) TestRecordStructure() {
42
-	err := fake.SendServerHello(suite.buf, suite.secret.Key[:], suite.hello)
41
+	err := fake.SendServerHello(suite.buf, suite.secret.Key[:], suite.hello, fake.NoiseParams{})
43
 	suite.NoError(err)
42
 	suite.NoError(err)
44
 
43
 
45
 	var rec bytes.Buffer
44
 	var rec bytes.Buffer
59
 	recordType, length, err := tls.ReadRecord(suite.buf, &rec)
58
 	recordType, length, err := tls.ReadRecord(suite.buf, &rec)
60
 	suite.NoError(err)
59
 	suite.NoError(err)
61
 	suite.Equal(byte(tls.TypeApplicationData), recordType)
60
 	suite.Equal(byte(tls.TypeApplicationData), recordType)
62
-	suite.Greater(length, int64(doppel.TLSRecordSizeStart))
61
+	suite.Greater(length, int64(2500))
63
 
62
 
64
 	suite.Empty(suite.buf.Bytes())
63
 	suite.Empty(suite.buf.Bytes())
65
 }
64
 }
66
 
65
 
67
 func (suite *SendServerHelloTestSuite) TestHMAC() {
66
 func (suite *SendServerHelloTestSuite) TestHMAC() {
68
-	err := fake.SendServerHello(suite.buf, suite.secret.Key[:], suite.hello)
67
+	err := fake.SendServerHello(suite.buf, suite.secret.Key[:], suite.hello, fake.NoiseParams{})
69
 	suite.NoError(err)
68
 	suite.NoError(err)
70
 
69
 
71
 	packet := make([]byte, suite.buf.Len())
70
 	packet := make([]byte, suite.buf.Len())
83
 }
82
 }
84
 
83
 
85
 func (suite *SendServerHelloTestSuite) TestHandshakePayload() {
84
 func (suite *SendServerHelloTestSuite) TestHandshakePayload() {
86
-	err := fake.SendServerHello(suite.buf, suite.secret.Key[:], suite.hello)
85
+	err := fake.SendServerHello(suite.buf, suite.secret.Key[:], suite.hello, fake.NoiseParams{})
87
 	suite.NoError(err)
86
 	suite.NoError(err)
88
 
87
 
89
 	packet := suite.buf.Bytes()
88
 	packet := suite.buf.Bytes()
105
 }
104
 }
106
 
105
 
107
 func (suite *SendServerHelloTestSuite) TestChangeCipherSpec() {
106
 func (suite *SendServerHelloTestSuite) TestChangeCipherSpec() {
108
-	err := fake.SendServerHello(suite.buf, suite.secret.Key[:], suite.hello)
107
+	err := fake.SendServerHello(suite.buf, suite.secret.Key[:], suite.hello, fake.NoiseParams{})
109
 	suite.NoError(err)
108
 	suite.NoError(err)
110
 
109
 
111
 	// Skip first record
110
 	// Skip first record
124
 	suite.Equal([]byte{fake.ChangeCipherValue}, rec.Bytes())
123
 	suite.Equal([]byte{fake.ChangeCipherValue}, rec.Bytes())
125
 }
124
 }
126
 
125
 
126
+func (suite *SendServerHelloTestSuite) TestCalibratedNoiseSize() {
127
+	noise := fake.NoiseParams{Mean: 6480, Jitter: 100}
128
+	err := fake.SendServerHello(suite.buf, suite.secret.Key[:], suite.hello, noise)
129
+	suite.NoError(err)
130
+
131
+	var rec bytes.Buffer
132
+
133
+	// Skip ServerHello
134
+	_, _, err = tls.ReadRecord(suite.buf, &rec)
135
+	suite.NoError(err)
136
+
137
+	// Skip ChangeCipherSpec
138
+	rec.Reset()
139
+	_, _, err = tls.ReadRecord(suite.buf, &rec)
140
+	suite.NoError(err)
141
+
142
+	// Read noise ApplicationData
143
+	rec.Reset()
144
+	recordType, length, err := tls.ReadRecord(suite.buf, &rec)
145
+	suite.NoError(err)
146
+	suite.Equal(byte(tls.TypeApplicationData), recordType)
147
+
148
+	// Should be within mean ± jitter range.
149
+	suite.GreaterOrEqual(length, int64(noise.Mean-noise.Jitter))
150
+	suite.LessOrEqual(length, int64(noise.Mean+noise.Jitter))
151
+}
152
+
127
 func TestSendServerHello(t *testing.T) {
153
 func TestSendServerHello(t *testing.T) {
128
 	t.Parallel()
154
 	t.Parallel()
129
 	suite.Run(t, &SendServerHelloTestSuite{})
155
 	suite.Run(t, &SendServerHelloTestSuite{})

+ 14
- 8
mtglib/proxy.go Прегледај датотеку

27
 
27
 
28
 	allowFallbackOnUnknownDC    bool
28
 	allowFallbackOnUnknownDC    bool
29
 	tolerateTimeSkewness        time.Duration
29
 	tolerateTimeSkewness        time.Duration
30
+	idleTimeout                 time.Duration
30
 	domainFrontingPort          int
31
 	domainFrontingPort          int
31
 	domainFrontingIP            string
32
 	domainFrontingIP            string
32
 	domainFrontingProxyProtocol bool
33
 	domainFrontingProxyProtocol bool
65
 	ctx := newStreamContext(p.ctx, p.logger, conn)
66
 	ctx := newStreamContext(p.ctx, p.logger, conn)
66
 	defer ctx.Close()
67
 	defer ctx.Close()
67
 
68
 
68
-	go func() {
69
-		<-ctx.Done()
69
+	stop := context.AfterFunc(ctx, func() {
70
 		ctx.Close()
70
 		ctx.Close()
71
-	}()
71
+	})
72
+	defer stop()
72
 
73
 
73
 	p.eventStream.Send(ctx, NewEventStart(ctx.streamID, ctx.ClientIP()))
74
 	p.eventStream.Send(ctx, NewEventStart(ctx.streamID, ctx.ClientIP()))
74
 	ctx.logger.Info("Stream has been started")
75
 	ctx.logger.Info("Stream has been started")
104
 	relay.Relay(
105
 	relay.Relay(
105
 		ctx,
106
 		ctx,
106
 		ctx.logger.Named("relay"),
107
 		ctx.logger.Named("relay"),
107
-		ctx.telegramConn,
108
-		ctx.clientConn,
108
+		connIdleTimeout{Conn: ctx.telegramConn, timeout: p.idleTimeout},
109
+		connIdleTimeout{Conn: ctx.clientConn, timeout: p.idleTimeout},
109
 	)
110
 	)
110
 }
111
 }
111
 
112
 
151
 		case errors.Is(err, ants.ErrPoolClosed):
152
 		case errors.Is(err, ants.ErrPoolClosed):
152
 			return nil
153
 			return nil
153
 		case errors.Is(err, ants.ErrPoolOverload):
154
 		case errors.Is(err, ants.ErrPoolOverload):
155
+			conn.Close() //nolint: errcheck
154
 			logger.Info("connection was concurrency limited")
156
 			logger.Info("connection was concurrency limited")
155
 			p.eventStream.Send(p.ctx, NewEventConcurrencyLimited())
157
 			p.eventStream.Send(p.ctx, NewEventConcurrencyLimited())
156
 		}
158
 		}
192
 		return false
194
 		return false
193
 	}
195
 	}
194
 
196
 
195
-	if err := fake.SendServerHello(ctx.clientConn, p.secret.Key[:], clientHello); err != nil {
197
+	gangerNoise := p.doppelGanger.NoiseParams()
198
+	noiseParams := fake.NoiseParams{Mean: gangerNoise.Mean, Jitter: gangerNoise.Jitter}
199
+
200
+	if err := fake.SendServerHello(ctx.clientConn, p.secret.Key[:], clientHello, noiseParams); err != nil {
196
 		p.logger.InfoError("cannot send welcome packet", err)
201
 		p.logger.InfoError("cannot send welcome packet", err)
197
 		return false
202
 		return false
198
 	}
203
 	}
303
 	relay.Relay(
308
 	relay.Relay(
304
 		ctx,
309
 		ctx,
305
 		ctx.logger.Named("domain-fronting"),
310
 		ctx.logger.Named("domain-fronting"),
306
-		frontConn,
307
-		conn,
311
+		connIdleTimeout{Conn: frontConn, timeout: p.idleTimeout},
312
+		connIdleTimeout{Conn: conn, timeout: p.idleTimeout},
308
 	)
313
 	)
309
 }
314
 }
310
 
315
 
336
 		domainFrontingPort:       opts.getDomainFrontingPort(),
341
 		domainFrontingPort:       opts.getDomainFrontingPort(),
337
 		domainFrontingIP:         opts.DomainFrontingIP,
342
 		domainFrontingIP:         opts.DomainFrontingIP,
338
 		tolerateTimeSkewness:     opts.getTolerateTimeSkewness(),
343
 		tolerateTimeSkewness:     opts.getTolerateTimeSkewness(),
344
+		idleTimeout:              opts.getIdleTimeout(),
339
 		allowFallbackOnUnknownDC: opts.AllowFallbackOnUnknownDC,
345
 		allowFallbackOnUnknownDC: opts.AllowFallbackOnUnknownDC,
340
 		telegram:                 tg,
346
 		telegram:                 tg,
341
 		doppelGanger: doppel.NewGanger(
347
 		doppelGanger: doppel.NewGanger(

+ 9
- 0
mtglib/proxy_opts.go Прегледај датотеку

160
 
160
 
161
 	// DoppelGangerDRS defines if TLS Dynamic Record Sizing is active.
161
 	// DoppelGangerDRS defines if TLS Dynamic Record Sizing is active.
162
 	DoppelGangerDRS bool
162
 	DoppelGangerDRS bool
163
+
163
 }
164
 }
164
 
165
 
165
 func (p ProxyOpts) valid() error {
166
 func (p ProxyOpts) valid() error {
215
 	return p.PreferIP
216
 	return p.PreferIP
216
 }
217
 }
217
 
218
 
219
+func (p ProxyOpts) getIdleTimeout() time.Duration {
220
+	if p.IdleTimeout == 0 {
221
+		return time.Minute
222
+	}
223
+
224
+	return p.IdleTimeout
225
+}
226
+
218
 func (p ProxyOpts) getLogger(name string) Logger {
227
 func (p ProxyOpts) getLogger(name string) Logger {
219
 	return p.Logger.Named(name)
228
 	return p.Logger.Named(name)
220
 }
229
 }

+ 0
- 1
run_profile_tag_prof.go Прегледај датотеку

3
 package main
3
 package main
4
 
4
 
5
 import (
5
 import (
6
-	"fmt"
7
 	"net"
6
 	"net"
8
 	"net/http"
7
 	"net/http"
9
 	_ "net/http/pprof" //nolint: gosec
8
 	_ "net/http/pprof" //nolint: gosec

Loading…
Откажи
Сачувај