From de81ed565d5c38224ba9cd14f6e5bedc5d71a503 Mon Sep 17 00:00:00 2001 From: 9seconds Date: Tue, 24 Mar 2026 15:28:33 +0100 Subject: [PATCH 01/22] Add mention of fork --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 1aec0cf..7b5021c 100644 --- a/README.md +++ b/README.md @@ -91,6 +91,9 @@ that probably matter. software. I also believe that in the case of throwout proxies, this the feature is a useless luxury. + This is very controversial topic. Please read [rationale (in russian)](https://github.com/9seconds/mtg/issues/376#issuecomment-4118726699) + and use [mtg-multi](https://github.com/dolonet/mtg-multi) fork if you are disagree with. + * **No adtag support** Please read [Version 2](#version-2) chapter. From b47e13556ef57288ac1af03fa9e9c655e644ead9 Mon Sep 17 00:00:00 2001 From: 9seconds Date: Tue, 24 Mar 2026 20:46:41 +0100 Subject: [PATCH 02/22] Fix build with profiling --- .mise.toml | 6 ++++++ run_profile_tag_prof.go | 1 - 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/.mise.toml b/.mise.toml index e49496c..35b295b 100644 --- a/.mise.toml +++ b/.mise.toml @@ -16,6 +16,12 @@ sources = ["**/*.go", "go.mod", "go.sum"] outputs = ["mtg"] run = "go build" +[tasks."build:prof"] +description = "Build binary with profiling enabled" +sources = ["**/*.go", "go.mod", "go.sum"] +outputs = ["mtg"] +run = "go build -tags prof" + [tasks.update] description = "Update dependencies" run = [ diff --git a/run_profile_tag_prof.go b/run_profile_tag_prof.go index 63ca2d8..6fa3d9c 100644 --- a/run_profile_tag_prof.go +++ b/run_profile_tag_prof.go @@ -3,7 +3,6 @@ package main import ( - "fmt" "net" "net/http" _ "net/http/pprof" //nolint: gosec From a8e4acb6f88ab102df63c5281abdb58d5451a2f3 Mon Sep 17 00:00:00 2001 From: 9seconds Date: Tue, 24 Mar 2026 21:04:30 +0100 Subject: [PATCH 03/22] Update PGO --- default.pgo | Bin 21497 -> 39105 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/default.pgo b/default.pgo index c6a64540ffdb75315fbd5e5be11a5b5bec2688e8..d7bf481caf3a57734ffbe8cfda436bb0dc0f04d7 100644 GIT binary patch literal 39105 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 From 60c57c23062e8692dfc2d41cf8c7d4b977533645 Mon Sep 17 00:00:00 2001 From: 9seconds Date: Wed, 25 Mar 2026 10:17:30 +0100 Subject: [PATCH 04/22] Update stale codeql configuration --- .github/workflows/codeql-analysis.yml | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 61688f8..f4542d7 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -21,7 +21,7 @@ permissions: on: push: - branches: + branches: - master - stable pull_request: @@ -45,11 +45,13 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v2 + uses: actions/checkout@v6 + with: + submodules: recursive # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v1 + uses: github/codeql-action/init@v4 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -60,7 +62,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@v1 + uses: github/codeql-action/autobuild@v4 # ℹ️ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl @@ -74,4 +76,4 @@ jobs: # make release - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v1 + uses: github/codeql-action/analyze@v4 From 80213ad35dd62297bef399e6b762052742a56aeb Mon Sep 17 00:00:00 2001 From: Alexey Dolotov Date: Thu, 26 Mar 2026 23:38:58 +0300 Subject: [PATCH 05/22] Add dynamic cert noise calibration for FakeTLS handshake MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The hardcoded noise range (2500-4700 bytes) in the FakeTLS ServerHello does not match the real certificate chain sizes of many popular fronting domains (e.g., dl.google.com ≈ 6480 bytes, microsoft.com ≈ 13004 bytes). This makes the proxy detectable by DPI systems that compare the ApplicationData size with the real cert chain size for the SNI domain. On startup, probe the fronting domain's actual TLS handshake size and use the measured value ± jitter instead of the static range. Falls back to the legacy 2500-4700 range if the probe fails. Also adds optional caching of probe results between restarts (noise-cache-path, noise-cache-ttl) and a configurable probe count (noise-probe-count) under [defense.doppelganger]. Closes #408 --- internal/cli/run_proxy.go | 4 + internal/config/config.go | 11 +- internal/config/parse.go | 11 +- mtglib/internal/tls/fake/cert_probe.go | 261 +++++++++++++++++++ mtglib/internal/tls/fake/server_side.go | 47 +++- mtglib/internal/tls/fake/server_side_test.go | 38 ++- mtglib/proxy.go | 43 ++- mtglib/proxy_opts.go | 19 ++ 8 files changed, 405 insertions(+), 29 deletions(-) create mode 100644 mtglib/internal/tls/fake/cert_probe.go diff --git a/internal/cli/run_proxy.go b/internal/cli/run_proxy.go index 643f645..1deac95 100644 --- a/internal/cli/run_proxy.go +++ b/internal/cli/run_proxy.go @@ -267,6 +267,10 @@ func runProxy(conf *config.Config, version string) error { //nolint: funlen DoppelGangerPerRaid: conf.Defense.Doppelganger.Repeats.Get(mtglib.DoppelGangerPerRaid), DoppelGangerEach: conf.Defense.Doppelganger.UpdateEach.Get(mtglib.DoppelGangerEach), DoppelGangerDRS: conf.Defense.Doppelganger.DRS.Get(false), + + NoiseProbeCount: conf.Defense.Doppelganger.NoiseProbeCount.Get(0), + NoiseCacheTTL: conf.Defense.Doppelganger.NoiseCacheTTL.Get(0), + NoiseCachePath: conf.Defense.Doppelganger.NoiseCachePath, } proxy, err := mtglib.NewProxy(opts) diff --git a/internal/config/config.go b/internal/config/config.go index cb51c63..e266d16 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -50,10 +50,13 @@ type Config struct { Blocklist ListConfig `json:"blocklist"` Allowlist ListConfig `json:"allowlist"` Doppelganger struct { - URLs []TypeHttpsURL `json:"urls"` - Repeats TypeConcurrency `json:"repeats_per_raid"` - UpdateEach TypeDuration `json:"raid_each"` - DRS TypeBool `json:"drs"` + URLs []TypeHttpsURL `json:"urls"` + Repeats TypeConcurrency `json:"repeats_per_raid"` + UpdateEach TypeDuration `json:"raid_each"` + DRS TypeBool `json:"drs"` + NoiseProbeCount TypeConcurrency `json:"noise_probe_count"` + NoiseCacheTTL TypeDuration `json:"noise_cache_ttl"` + NoiseCachePath string `json:"noise_cache_path"` } `json:"doppelganger"` } `json:"defense"` Network struct { diff --git a/internal/config/parse.go b/internal/config/parse.go index 81a50f3..125e0a9 100644 --- a/internal/config/parse.go +++ b/internal/config/parse.go @@ -45,10 +45,13 @@ type tomlConfig struct { UpdateEach string `toml:"update-each" json:"updateEach,omitempty"` } `toml:"allowlist" json:"allowlist,omitempty"` Doppelganger struct { - URLs []string `toml:"urls" json:"urls,omitempty"` - Repeats uint `toml:"repeats-per-raid" json:"repeats_per_raid,omitempty"` - UpdateEach string `toml:"raid-each" json:"raid_each,omitempty"` - DRS bool `toml:"drs" json:"drs,omitempty"` + URLs []string `toml:"urls" json:"urls,omitempty"` + Repeats uint `toml:"repeats-per-raid" json:"repeats_per_raid,omitempty"` + UpdateEach string `toml:"raid-each" json:"raid_each,omitempty"` + DRS bool `toml:"drs" json:"drs,omitempty"` + NoiseProbeCount uint `toml:"noise-probe-count" json:"noise_probe_count,omitempty"` + NoiseCacheTTL string `toml:"noise-cache-ttl" json:"noise_cache_ttl,omitempty"` + NoiseCachePath string `toml:"noise-cache-path" json:"noise_cache_path,omitempty"` } `toml:"doppelganger" json:"doppelganger,omitempty"` } `toml:"defense" json:"defense,omitempty"` Network struct { diff --git a/mtglib/internal/tls/fake/cert_probe.go b/mtglib/internal/tls/fake/cert_probe.go new file mode 100644 index 0000000..4c92968 --- /dev/null +++ b/mtglib/internal/tls/fake/cert_probe.go @@ -0,0 +1,261 @@ +package fake + +import ( + "crypto/tls" + "encoding/binary" + "encoding/json" + "fmt" + "net" + "os" + "sync" + "time" +) + +const ( + probeDialTimeout = 10 * time.Second + probeHandshakeTimeout = 10 * time.Second + defaultProbeCount = 15 + defaultCacheTTL = 24 * time.Hour + + tlsTypeChangeCipherSpec = 0x14 + tlsTypeApplicationData = 0x17 +) + +// CertProbeResult holds the measured encrypted handshake size. +type CertProbeResult struct { + Mean int `json:"mean"` + Jitter int `json:"jitter"` +} + +// CertProbeCache is the on-disk format for cached probe results. +type CertProbeCache struct { + Hostname string `json:"hostname"` + Port int `json:"port"` + Mean int `json:"mean"` + Jitter int `json:"jitter"` + ProbedAt time.Time `json:"probed_at"` +} + +// LoadCachedProbe reads a cached probe result from path. Returns the result +// and true if the cache exists, matches hostname:port, and is younger than ttl. +// Otherwise returns zero value and false. +func LoadCachedProbe(path, hostname string, port int, ttl time.Duration) (CertProbeResult, bool) { + if ttl <= 0 { + ttl = defaultCacheTTL + } + + data, err := os.ReadFile(path) + if err != nil { + return CertProbeResult{}, false + } + + var cache CertProbeCache + if err := json.Unmarshal(data, &cache); err != nil { + return CertProbeResult{}, false + } + + if cache.Hostname != hostname || cache.Port != port { + return CertProbeResult{}, false + } + + if time.Since(cache.ProbedAt) > ttl { + return CertProbeResult{}, false + } + + if cache.Mean <= 0 { + return CertProbeResult{}, false + } + + return CertProbeResult{Mean: cache.Mean, Jitter: cache.Jitter}, true +} + +// SaveCachedProbe writes a probe result to path as JSON. +func SaveCachedProbe(path, hostname string, port int, result CertProbeResult) error { + cache := CertProbeCache{ + Hostname: hostname, + Port: port, + Mean: result.Mean, + Jitter: result.Jitter, + ProbedAt: time.Now(), + } + + data, err := json.MarshalIndent(cache, "", " ") + if err != nil { + return err + } + + return os.WriteFile(path, data, 0o644) //nolint: gosec +} + +// ProbeCertSize connects to hostname:port via TLS multiple times and measures +// the total ApplicationData payload bytes sent by the server during the +// handshake (between ChangeCipherSpec and the first application-level data). +// This corresponds to EncryptedExtensions + Certificate + CertificateVerify + +// Finished in TLS 1.3, which is what the FakeTLS noise must mimic. +func ProbeCertSize(hostname string, port int, count int) (CertProbeResult, error) { + if count <= 0 { + count = defaultProbeCount + } + + addr := net.JoinHostPort(hostname, fmt.Sprintf("%d", port)) + sizes := make([]int, 0, count) + + for i := 0; i < count; i++ { + size, err := probeSingle(addr, hostname) + if err != nil { + if len(sizes) > 0 { + break // use what we have + } + + return CertProbeResult{}, fmt.Errorf("probe %d failed: %w", i, err) + } + + sizes = append(sizes, size) + } + + if len(sizes) == 0 { + return CertProbeResult{}, fmt.Errorf("no successful probes") + } + + // Calculate mean and jitter (max deviation from mean). + sum := 0 + for _, s := range sizes { + sum += s + } + + mean := sum / len(sizes) + + maxDev := 0 + for _, s := range sizes { + d := s - mean + if d < 0 { + d = -d + } + + if d > maxDev { + maxDev = d + } + } + + // Ensure minimum jitter of 100 bytes for variability. + if maxDev < 100 { + maxDev = 100 + } + + return CertProbeResult{Mean: mean, Jitter: maxDev}, nil +} + +// probeSingle does one TLS handshake and measures ApplicationData bytes +// received during the handshake. +func probeSingle(addr, hostname string) (int, error) { + rawConn, err := net.DialTimeout("tcp", addr, probeDialTimeout) + if err != nil { + return 0, err + } + defer rawConn.Close() //nolint: errcheck + + capture := &recordCapture{conn: rawConn} + + tlsConn := tls.Client(capture, &tls.Config{ + ServerName: hostname, + MinVersion: tls.VersionTLS12, + }) + tlsConn.SetDeadline(time.Now().Add(probeHandshakeTimeout)) //nolint: errcheck + + if err := tlsConn.Handshake(); err != nil { + return 0, err + } + + tlsConn.Close() //nolint: errcheck + + return capture.appDataBytes, nil +} + +// recordCapture wraps a net.Conn and parses the raw TLS record stream to +// measure ApplicationData payload sizes sent by the server during handshake. +// It tracks record boundaries by maintaining a state machine over Read calls. +type recordCapture struct { + conn net.Conn + mu sync.Mutex + appDataBytes int + seenCCS bool + done bool + + // Record boundary tracking for the read side. + readRemaining int // bytes left in current record payload + readHeaderBuf [5]byte + readHeaderPos int +} + +func (rc *recordCapture) Read(p []byte) (int, error) { + n, err := rc.conn.Read(p) + if n > 0 && !rc.done { + rc.mu.Lock() + rc.parseReadBytes(p[:n]) + rc.mu.Unlock() + } + + return n, err +} + +func (rc *recordCapture) parseReadBytes(data []byte) { + for len(data) > 0 { + if rc.readRemaining > 0 { + // Consuming payload of current record. + consume := rc.readRemaining + if consume > len(data) { + consume = len(data) + } + + rc.readRemaining -= consume + data = data[consume:] + + continue + } + + // Accumulate header bytes (5 bytes per record). + need := 5 - rc.readHeaderPos + if need > len(data) { + need = len(data) + } + + copy(rc.readHeaderBuf[rc.readHeaderPos:], data[:need]) + rc.readHeaderPos += need + data = data[need:] + + if rc.readHeaderPos < 5 { + return // incomplete header + } + + // Full header available. + recordType := rc.readHeaderBuf[0] + payloadLen := int(binary.BigEndian.Uint16(rc.readHeaderBuf[3:5])) + rc.readHeaderPos = 0 + rc.readRemaining = payloadLen + + if recordType == tlsTypeChangeCipherSpec { + rc.seenCCS = true + } else if recordType == tlsTypeApplicationData && rc.seenCCS { + rc.appDataBytes += payloadLen + } + } +} + +func (rc *recordCapture) Write(p []byte) (int, error) { + // After client writes post-CCS data, server handshake records are done. + if rc.seenCCS && rc.appDataBytes > 0 { + rc.done = true + } + + return rc.conn.Write(p) +} + +func (rc *recordCapture) Close() error { return rc.conn.Close() } +func (rc *recordCapture) LocalAddr() net.Addr { return rc.conn.LocalAddr() } +func (rc *recordCapture) RemoteAddr() net.Addr { return rc.conn.RemoteAddr() } +func (rc *recordCapture) SetDeadline(t time.Time) error { return rc.conn.SetDeadline(t) } +func (rc *recordCapture) SetReadDeadline(t time.Time) error { return rc.conn.SetReadDeadline(t) } +func (rc *recordCapture) SetWriteDeadline(t time.Time) error { return rc.conn.SetWriteDeadline(t) } + +// Ensure recordCapture implements net.Conn. +var _ net.Conn = (*recordCapture)(nil) diff --git a/mtglib/internal/tls/fake/server_side.go b/mtglib/internal/tls/fake/server_side.go index ec1c5cc..a0bf1da 100644 --- a/mtglib/internal/tls/fake/server_side.go +++ b/mtglib/internal/tls/fake/server_side.go @@ -9,11 +9,18 @@ import ( "io" rnd "math/rand/v2" - "github.com/9seconds/mtg/v2/mtglib/internal/doppel" "github.com/9seconds/mtg/v2/mtglib/internal/tls" "golang.org/x/crypto/curve25519" ) +// NoiseParams controls the size of the fake ApplicationData record +// in ServerHello. If Mean is 0, the legacy random range (2500-4700) +// is used. +type NoiseParams struct { + Mean int + Jitter int +} + const ( TypeHandshakeServer = 0x02 ChangeCipherValue = 0x01 @@ -33,13 +40,13 @@ var serverHelloSuffix = []byte{ 0x00, 0x20, // 32 bytes of key } -func SendServerHello(w io.Writer, secret []byte, clientHello *ClientHello) error { +func SendServerHello(w io.Writer, secret []byte, clientHello *ClientHello, noise NoiseParams) error { buf := &bytes.Buffer{} buf.Grow(tls.MaxRecordSize) generateServerHello(buf, clientHello) generateChangeCipherValue(buf) - generateNoise(buf) + generateNoise(buf, noise) packet := buf.Bytes() digest := hmac.New(sha256.New, secret) @@ -125,19 +132,31 @@ func generateChangeCipherValue(buf *bytes.Buffer) { buf.WriteByte(ChangeCipherValue) } -func generateNoise(buf *bytes.Buffer) { - data := make( - []byte, - int64( - doppel.TLSRecordSizeStart+rnd.IntN( - doppel.TLSRecordSizeAccel-doppel.TLSRecordSizeStart, - ), - ), - ) +// generateNoise writes a single ApplicationData record mimicking the combined +// size of a real TLS 1.3 encrypted server handshake (EncryptedExtensions + +// Certificate chain + CertificateVerify + Finished). +// +// NOTE: Must be exactly ONE ApplicationData record — the Telegram client reads +// ServerHello + CCS + 1 ApplicationData and computes HMAC over all three. +// Multiple records would cause HMAC mismatch and connection failure. +func generateNoise(buf *bytes.Buffer, noise NoiseParams) { + var size int - if _, err := rand.Read(data[:]); err != nil { + if noise.Mean > 0 && noise.Jitter > 0 { + // Calibrated: use measured cert chain size ± jitter. + size = noise.Mean - noise.Jitter + rnd.IntN(2*noise.Jitter) + if size < 1000 { + size = 1000 + } + } else { + // Legacy fallback: random in 2500-4700 range. + size = 2500 + rnd.IntN(2200) + } + + data := make([]byte, size) + if _, err := rand.Read(data); err != nil { panic(err) } - tls.WriteRecord(buf, data[:]) //nolint: errcheck + tls.WriteRecord(buf, data) //nolint: errcheck } diff --git a/mtglib/internal/tls/fake/server_side_test.go b/mtglib/internal/tls/fake/server_side_test.go index c4b54a6..1a9b5f2 100644 --- a/mtglib/internal/tls/fake/server_side_test.go +++ b/mtglib/internal/tls/fake/server_side_test.go @@ -8,7 +8,6 @@ import ( "testing" "github.com/9seconds/mtg/v2/mtglib" - "github.com/9seconds/mtg/v2/mtglib/internal/doppel" "github.com/9seconds/mtg/v2/mtglib/internal/tls" "github.com/9seconds/mtg/v2/mtglib/internal/tls/fake" "github.com/stretchr/testify/suite" @@ -39,7 +38,7 @@ func (suite *SendServerHelloTestSuite) SetupTest() { } func (suite *SendServerHelloTestSuite) TestRecordStructure() { - err := fake.SendServerHello(suite.buf, suite.secret.Key[:], suite.hello) + err := fake.SendServerHello(suite.buf, suite.secret.Key[:], suite.hello, fake.NoiseParams{}) suite.NoError(err) var rec bytes.Buffer @@ -59,13 +58,13 @@ func (suite *SendServerHelloTestSuite) TestRecordStructure() { recordType, length, err := tls.ReadRecord(suite.buf, &rec) suite.NoError(err) suite.Equal(byte(tls.TypeApplicationData), recordType) - suite.Greater(length, int64(doppel.TLSRecordSizeStart)) + suite.Greater(length, int64(2500)) suite.Empty(suite.buf.Bytes()) } func (suite *SendServerHelloTestSuite) TestHMAC() { - err := fake.SendServerHello(suite.buf, suite.secret.Key[:], suite.hello) + err := fake.SendServerHello(suite.buf, suite.secret.Key[:], suite.hello, fake.NoiseParams{}) suite.NoError(err) packet := make([]byte, suite.buf.Len()) @@ -83,7 +82,7 @@ func (suite *SendServerHelloTestSuite) TestHMAC() { } func (suite *SendServerHelloTestSuite) TestHandshakePayload() { - err := fake.SendServerHello(suite.buf, suite.secret.Key[:], suite.hello) + err := fake.SendServerHello(suite.buf, suite.secret.Key[:], suite.hello, fake.NoiseParams{}) suite.NoError(err) packet := suite.buf.Bytes() @@ -105,7 +104,7 @@ func (suite *SendServerHelloTestSuite) TestHandshakePayload() { } func (suite *SendServerHelloTestSuite) TestChangeCipherSpec() { - err := fake.SendServerHello(suite.buf, suite.secret.Key[:], suite.hello) + err := fake.SendServerHello(suite.buf, suite.secret.Key[:], suite.hello, fake.NoiseParams{}) suite.NoError(err) // Skip first record @@ -124,6 +123,33 @@ func (suite *SendServerHelloTestSuite) TestChangeCipherSpec() { suite.Equal([]byte{fake.ChangeCipherValue}, rec.Bytes()) } +func (suite *SendServerHelloTestSuite) TestCalibratedNoiseSize() { + noise := fake.NoiseParams{Mean: 6480, Jitter: 100} + err := fake.SendServerHello(suite.buf, suite.secret.Key[:], suite.hello, noise) + suite.NoError(err) + + var rec bytes.Buffer + + // Skip ServerHello + _, _, err = tls.ReadRecord(suite.buf, &rec) + suite.NoError(err) + + // Skip ChangeCipherSpec + rec.Reset() + _, _, err = tls.ReadRecord(suite.buf, &rec) + suite.NoError(err) + + // Read noise ApplicationData + rec.Reset() + recordType, length, err := tls.ReadRecord(suite.buf, &rec) + suite.NoError(err) + suite.Equal(byte(tls.TypeApplicationData), recordType) + + // Should be within mean ± jitter range. + suite.GreaterOrEqual(length, int64(noise.Mean-noise.Jitter)) + suite.LessOrEqual(length, int64(noise.Mean+noise.Jitter)) +} + func TestSendServerHello(t *testing.T) { t.Parallel() suite.Run(t, &SendServerHelloTestSuite{}) diff --git a/mtglib/proxy.go b/mtglib/proxy.go index 0e66e7c..5784f19 100644 --- a/mtglib/proxy.go +++ b/mtglib/proxy.go @@ -36,6 +36,7 @@ type Proxy struct { doppelGanger *doppel.Ganger clientObfuscatror obfuscation.Obfuscator + noiseParams fake.NoiseParams secret Secret network Network antiReplayCache AntiReplayCache @@ -192,7 +193,7 @@ func (p *Proxy) doFakeTLSHandshake(ctx *streamContext) bool { return false } - if err := fake.SendServerHello(ctx.clientConn, p.secret.Key[:], clientHello); err != nil { + if err := fake.SendServerHello(ctx.clientConn, p.secret.Key[:], clientHello, p.noiseParams); err != nil { p.logger.InfoError("cannot send welcome packet", err) return false } @@ -323,9 +324,49 @@ func NewProxy(opts ProxyOpts) (*Proxy, error) { logger := opts.getLogger("proxy") updatersLogger := logger.Named("telegram-updaters") + // Probe the fronting domain's cert chain size for noise calibration. + probeHost := opts.Secret.Host + probePort := opts.getDomainFrontingPort() + noiseParams := fake.NoiseParams{} + + probeCount := int(opts.NoiseProbeCount) + if probeCount <= 0 { + probeCount = 15 + } + + cacheTTL := opts.NoiseCacheTTL + + // Try loading from cache first. + if opts.NoiseCachePath != "" { + if cached, ok := fake.LoadCachedProbe(opts.NoiseCachePath, probeHost, probePort, cacheTTL); ok { + noiseParams = fake.NoiseParams(cached) + logger.Info(fmt.Sprintf("cert probe: loaded from cache, host=%s mean=%d jitter=%d", + probeHost, cached.Mean, cached.Jitter)) + } + } + + // If no cached result, probe live. + if noiseParams.Mean == 0 { + probeResult, probeErr := fake.ProbeCertSize(probeHost, probePort, probeCount) + if probeErr != nil { + logger.WarningError("cert probe failed, using default noise size", probeErr) + } else { + noiseParams = fake.NoiseParams(probeResult) + logger.Info(fmt.Sprintf("cert probe: host=%s mean=%d jitter=%d", + probeHost, probeResult.Mean, probeResult.Jitter)) + + if opts.NoiseCachePath != "" { + if saveErr := fake.SaveCachedProbe(opts.NoiseCachePath, probeHost, probePort, probeResult); saveErr != nil { + logger.WarningError("failed to save cert probe cache", saveErr) + } + } + } + } + proxy := &Proxy{ ctx: ctx, ctxCancel: cancel, + noiseParams: noiseParams, secret: opts.Secret, network: opts.Network, antiReplayCache: opts.AntiReplayCache, diff --git a/mtglib/proxy_opts.go b/mtglib/proxy_opts.go index cea9cad..70120e5 100644 --- a/mtglib/proxy_opts.go +++ b/mtglib/proxy_opts.go @@ -160,6 +160,25 @@ type ProxyOpts struct { // DoppelGangerDRS defines if TLS Dynamic Record Sizing is active. DoppelGangerDRS bool + + // NoiseProbeCount is the number of TLS connections to make when probing + // the fronting domain's cert chain size for noise calibration. + // Default is 15. + // + // This is an optional setting. + NoiseProbeCount uint + + // NoiseCacheTTL is how long a cached cert probe result is considered + // valid. Default is 24 hours. + // + // This is an optional setting. + NoiseCacheTTL time.Duration + + // NoiseCachePath is the file path for caching cert probe results + // between restarts. If empty, no caching is performed. + // + // This is an optional setting. + NoiseCachePath string } func (p ProxyOpts) valid() error { From 9dfd992c1dc2a2f22f36f0f5f14a352e83576fa9 Mon Sep 17 00:00:00 2001 From: Alexey Dolotov Date: Fri, 27 Mar 2026 16:30:50 +0300 Subject: [PATCH 06/22] Move cert noise calibration into doppelganger scout Instead of a separate cert_probe.go that duplicates the scout's TLS connection logic, measure the cert chain size directly from the same HTTPS connections the scout already makes. Changes: - Extend ScoutConnResult with payloadLen field - Add Write interception to ScoutConn for handshake boundary detection - Scout.learn() now computes cert size (sum of ApplicationData between CCS and first client Write) alongside inter-record durations - Ganger aggregates cert sizes across raids and exposes NoiseParams() via atomic pointer for lock-free reads from proxy goroutines - Proxy reads NoiseParams from Ganger on each handshake instead of probing at startup - Remove cert_probe.go, disk cache, and related config options (noise-cache-path, noise-cache-ttl, noise-probe-count) Falls back to legacy 2500-4700 range until the first scout raid completes (typically within 1-2 seconds of startup). --- internal/cli/run_proxy.go | 4 - internal/config/config.go | 3 - internal/config/parse.go | 3 - mtglib/internal/doppel/ganger.go | 96 ++++++- mtglib/internal/doppel/scout.go | 59 +++- mtglib/internal/doppel/scout_conn.go | 21 +- .../internal/doppel/scout_conn_collected.go | 17 +- .../doppel/scout_conn_collected_test.go | 8 +- mtglib/internal/doppel/scout_test.go | 4 +- mtglib/internal/tls/fake/cert_probe.go | 261 ------------------ mtglib/proxy.go | 46 +-- mtglib/proxy_opts.go | 18 -- 12 files changed, 177 insertions(+), 363 deletions(-) delete mode 100644 mtglib/internal/tls/fake/cert_probe.go diff --git a/internal/cli/run_proxy.go b/internal/cli/run_proxy.go index 1deac95..643f645 100644 --- a/internal/cli/run_proxy.go +++ b/internal/cli/run_proxy.go @@ -267,10 +267,6 @@ func runProxy(conf *config.Config, version string) error { //nolint: funlen DoppelGangerPerRaid: conf.Defense.Doppelganger.Repeats.Get(mtglib.DoppelGangerPerRaid), DoppelGangerEach: conf.Defense.Doppelganger.UpdateEach.Get(mtglib.DoppelGangerEach), DoppelGangerDRS: conf.Defense.Doppelganger.DRS.Get(false), - - NoiseProbeCount: conf.Defense.Doppelganger.NoiseProbeCount.Get(0), - NoiseCacheTTL: conf.Defense.Doppelganger.NoiseCacheTTL.Get(0), - NoiseCachePath: conf.Defense.Doppelganger.NoiseCachePath, } proxy, err := mtglib.NewProxy(opts) diff --git a/internal/config/config.go b/internal/config/config.go index e266d16..6bb0a6c 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -54,9 +54,6 @@ type Config struct { Repeats TypeConcurrency `json:"repeats_per_raid"` UpdateEach TypeDuration `json:"raid_each"` DRS TypeBool `json:"drs"` - NoiseProbeCount TypeConcurrency `json:"noise_probe_count"` - NoiseCacheTTL TypeDuration `json:"noise_cache_ttl"` - NoiseCachePath string `json:"noise_cache_path"` } `json:"doppelganger"` } `json:"defense"` Network struct { diff --git a/internal/config/parse.go b/internal/config/parse.go index 125e0a9..6cf824d 100644 --- a/internal/config/parse.go +++ b/internal/config/parse.go @@ -49,9 +49,6 @@ type tomlConfig struct { Repeats uint `toml:"repeats-per-raid" json:"repeats_per_raid,omitempty"` UpdateEach string `toml:"raid-each" json:"raid_each,omitempty"` DRS bool `toml:"drs" json:"drs,omitempty"` - NoiseProbeCount uint `toml:"noise-probe-count" json:"noise_probe_count,omitempty"` - NoiseCacheTTL string `toml:"noise-cache-ttl" json:"noise_cache_ttl,omitempty"` - NoiseCachePath string `toml:"noise-cache-path" json:"noise_cache_path,omitempty"` } `toml:"doppelganger" json:"doppelganger,omitempty"` } `toml:"defense" json:"defense,omitempty"` Network struct { diff --git a/mtglib/internal/doppel/ganger.go b/mtglib/internal/doppel/ganger.go index c8fbfbc..740acd7 100644 --- a/mtglib/internal/doppel/ganger.go +++ b/mtglib/internal/doppel/ganger.go @@ -2,7 +2,9 @@ package doppel import ( "context" + "fmt" "sync" + "sync/atomic" "time" "github.com/9seconds/mtg/v2/essentials" @@ -12,8 +14,22 @@ const ( DoppelGangerMaxDurations = 4096 DoppelGangerScoutRaidEach = 6 * time.Hour DoppelGangerScoutRepeats = 10 + + MinCertSizesToCalculate = 3 ) +// NoiseParams holds the measured cert chain size for FakeTLS noise calibration. +// If Mean is 0, the caller should use a legacy fallback. +type NoiseParams struct { + Mean int + Jitter int +} + +type scoutRaidResult struct { + durations []time.Duration + certSizes []int +} + type gangerConnRequest struct { ret chan<- Conn payload essentials.Conn @@ -33,6 +49,9 @@ type Ganger struct { stats *Stats durations []time.Duration + certSizes []int + + noiseParams atomic.Pointer[NoiseParams] connRequests chan gangerConnRequest } @@ -48,6 +67,16 @@ func (g *Ganger) Run() { }) } +// NoiseParams returns the current cert-size-based noise parameters. +// Returns zero-value NoiseParams if not yet measured (caller should use fallback). +func (g *Ganger) NoiseParams() NoiseParams { + if p := g.noiseParams.Load(); p != nil { + return *p + } + + return NoiseParams{} +} + func (g *Ganger) NewConn(conn essentials.Conn) (Conn, error) { rvChan := make(chan Conn) req := gangerConnRequest{ @@ -81,7 +110,7 @@ func (g *Ganger) run() { } }() - scoutCollectedChan := make(chan []time.Duration) + scoutCollectedChan := make(chan scoutRaidResult) currentScoutCollectedChan := scoutCollectedChan updatedStatsChan := make(chan *Stats) @@ -94,18 +123,29 @@ func (g *Ganger) run() { select { case <-g.ctx.Done(): return - case durations := <-currentScoutCollectedChan: - g.durations = append(g.durations, durations...) + case result := <-currentScoutCollectedChan: + g.durations = append(g.durations, result.durations...) if len(g.durations) > DoppelGangerMaxDurations { copy(g.durations, g.durations[len(g.durations)-DoppelGangerMaxDurations:]) g.durations = g.durations[:DoppelGangerMaxDurations] } + // Update cert sizes and recompute noise params. + g.certSizes = append(g.certSizes, result.certSizes...) + if len(g.certSizes) > DoppelGangerMaxDurations { + g.certSizes = g.certSizes[len(g.certSizes)-DoppelGangerMaxDurations:] + } + + if len(g.certSizes) >= MinCertSizesToCalculate { + g.updateNoiseParams() + } + if len(g.durations) < MinDurationsToCalculate { continue } + durations := g.durations currentScoutCollectedChan = nil g.wg.Go(func() { select { @@ -129,8 +169,45 @@ func (g *Ganger) run() { } } -func (g *Ganger) runScoutRaid(rvChan chan<- []time.Duration) { - durations := []time.Duration{} +func (g *Ganger) updateNoiseParams() { + if len(g.certSizes) == 0 { + return + } + + sum := 0 + for _, s := range g.certSizes { + sum += s + } + + mean := sum / len(g.certSizes) + + maxDev := 0 + for _, s := range g.certSizes { + d := s - mean + if d < 0 { + d = -d + } + + if d > maxDev { + maxDev = d + } + } + + if maxDev < 100 { + maxDev = 100 + } + + np := &NoiseParams{Mean: mean, Jitter: maxDev} + g.noiseParams.Store(np) + + g.logger.Info(fmt.Sprintf( + "updated noise params: mean=%d jitter=%d samples=%d", + mean, maxDev, len(g.certSizes), + )) +} + +func (g *Ganger) runScoutRaid(rvChan chan<- scoutRaidResult) { + var result scoutRaidResult for range g.scoutRaidRepeats { learned, err := g.scout.Learn(g.ctx) @@ -138,13 +215,18 @@ func (g *Ganger) runScoutRaid(rvChan chan<- []time.Duration) { g.logger.WarningError("cannot learn", err) continue } - durations = append(durations, learned...) + + result.durations = append(result.durations, learned.Durations...) + + if learned.CertSize > 0 { + result.certSizes = append(result.certSizes, learned.CertSize) + } } select { case <-g.ctx.Done(): return - case rvChan <- durations: + case rvChan <- result: } } diff --git a/mtglib/internal/doppel/scout.go b/mtglib/internal/doppel/scout.go index e6650c8..4b58e32 100644 --- a/mtglib/internal/doppel/scout.go +++ b/mtglib/internal/doppel/scout.go @@ -12,36 +12,46 @@ import ( "github.com/9seconds/mtg/v2/mtglib/internal/tls" ) +// ScoutResult holds measurements from a single scout HTTP request. +type ScoutResult struct { + Durations []time.Duration + CertSize int // total ApplicationData bytes during TLS handshake; 0 if unknown +} + type Scout struct { network Network urls []string } -func (s Scout) Learn(ctx context.Context) ([]time.Duration, error) { - var durations []time.Duration +func (s Scout) Learn(ctx context.Context) (ScoutResult, error) { + var combined ScoutResult for _, url := range s.urls { learned, err := s.learn(ctx, url) if err != nil { - return nil, err + return ScoutResult{}, err } - durations = append(durations, learned...) + combined.Durations = append(combined.Durations, learned.Durations...) + + if learned.CertSize > 0 && combined.CertSize == 0 { + combined.CertSize = learned.CertSize + } } - return durations, nil + return combined, nil } -func (s Scout) learn(ctx context.Context, url string) ([]time.Duration, error) { +func (s Scout) learn(ctx context.Context, url string) (ScoutResult, error) { client, results := s.makeClient() if !strings.HasPrefix(url, "https://") { - return nil, fmt.Errorf("url %s must be https", url) + return ScoutResult{}, fmt.Errorf("url %s must be https", url) } req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) if err != nil { - return nil, err + return ScoutResult{}, err } resp, err := client.Do(req) @@ -52,10 +62,12 @@ func (s Scout) learn(ctx context.Context, url string) ([]time.Duration, error) { } if err != nil || len(results.data) == 0 { - return nil, err + return ScoutResult{}, err } - durations := []time.Duration{} + var result ScoutResult + + // Compute inter-record durations (existing logic). lastTimestamp := time.Time{} for i, v := range results.data { @@ -71,11 +83,34 @@ func (s Scout) learn(ctx context.Context, url string) ([]time.Duration, error) { } } - durations = append(durations, v.timestamp.Sub(lastTimestamp)) + result.Durations = append(result.Durations, v.timestamp.Sub(lastTimestamp)) lastTimestamp = v.timestamp } - return durations, nil + // Compute cert size: sum of ApplicationData payload between CCS and + // the first client Write (which marks the end of server handshake). + seenCCS := false + boundary := results.writeIndex + if boundary < 0 { + boundary = len(results.data) + } + + for i, v := range results.data { + if i >= boundary { + break + } + + if v.recordType == tls.TypeChangeCipherSpec { + seenCCS = true + continue + } + + if seenCCS && v.recordType == tls.TypeApplicationData { + result.CertSize += v.payloadLen + } + } + + return result, nil } func (s Scout) makeClient() (*http.Client, *ScoutConnCollected) { diff --git a/mtglib/internal/doppel/scout_conn.go b/mtglib/internal/doppel/scout_conn.go index 0aaa0b5..8bde199 100644 --- a/mtglib/internal/doppel/scout_conn.go +++ b/mtglib/internal/doppel/scout_conn.go @@ -14,9 +14,10 @@ type ScoutConn struct { results *ScoutConnCollected rawBuf *bytes.Buffer + seenCCS bool } -func (s ScoutConn) Read(p []byte) (int, error) { +func (s *ScoutConn) Read(p []byte) (int, error) { buf := &bytes.Buffer{} for { @@ -31,7 +32,11 @@ func (s ScoutConn) Read(p []byte) (int, error) { return 0, err } - s.results.Add(recordType) + if recordType == tls.TypeChangeCipherSpec { + s.seenCCS = true + } + + s.results.Add(recordType, int(length)) s.rawBuf.Write([]byte{recordType}) s.rawBuf.Write(tls.TLSVersion[:]) @@ -45,11 +50,19 @@ func (s ScoutConn) Read(p []byte) (int, error) { } } -func NewScoutConn(conn essentials.Conn, results *ScoutConnCollected) ScoutConn { +func (s *ScoutConn) Write(p []byte) (int, error) { + if s.seenCCS { + s.results.MarkWrite() + } + + return s.Conn.Write(p) +} + +func NewScoutConn(conn essentials.Conn, results *ScoutConnCollected) *ScoutConn { rawBuf := &bytes.Buffer{} rawBuf.Grow(tls.MaxRecordSize) - return ScoutConn{ + return &ScoutConn{ Conn: tls.New(conn, false, false), results: results, rawBuf: rawBuf, diff --git a/mtglib/internal/doppel/scout_conn_collected.go b/mtglib/internal/doppel/scout_conn_collected.go index daf98cb..0fe4e4a 100644 --- a/mtglib/internal/doppel/scout_conn_collected.go +++ b/mtglib/internal/doppel/scout_conn_collected.go @@ -9,21 +9,32 @@ const ( type ScoutConnResult struct { timestamp time.Time recordType byte + payloadLen int } type ScoutConnCollected struct { - data []ScoutConnResult + data []ScoutConnResult + writeIndex int // index at which client first wrote post-handshake data; -1 if not set } -func (s *ScoutConnCollected) Add(record byte) { +func (s *ScoutConnCollected) Add(record byte, payloadLen int) { s.data = append(s.data, ScoutConnResult{ timestamp: time.Now(), recordType: record, + payloadLen: payloadLen, }) } +// MarkWrite records the current data length as the handshake boundary. +func (s *ScoutConnCollected) MarkWrite() { + if s.writeIndex < 0 { + s.writeIndex = len(s.data) + } +} + func NewScoutConnCollected() *ScoutConnCollected { return &ScoutConnCollected{ - data: make([]ScoutConnResult, 0, ScoutConnCollectedPreallocSize), + data: make([]ScoutConnResult, 0, ScoutConnCollectedPreallocSize), + writeIndex: -1, } } diff --git a/mtglib/internal/doppel/scout_conn_collected_test.go b/mtglib/internal/doppel/scout_conn_collected_test.go index df4dbdd..dcf8a41 100644 --- a/mtglib/internal/doppel/scout_conn_collected_test.go +++ b/mtglib/internal/doppel/scout_conn_collected_test.go @@ -14,7 +14,7 @@ type ScoutConnCollectedTestSuite struct { func (suite *ScoutConnCollectedTestSuite) TestAddSingle() { collected := NewScoutConnCollected() - collected.Add(tls.TypeApplicationData) + collected.Add(tls.TypeApplicationData, 100) suite.Len(collected.data, 1) suite.Equal(byte(tls.TypeApplicationData), collected.data[0].recordType) @@ -23,13 +23,13 @@ func (suite *ScoutConnCollectedTestSuite) TestAddSingle() { func (suite *ScoutConnCollectedTestSuite) TestAddTimestampsAreMonotonic() { collected := NewScoutConnCollected() - collected.Add(tls.TypeApplicationData) + collected.Add(tls.TypeApplicationData, 100) time.Sleep(time.Microsecond) - collected.Add(tls.TypeApplicationData) + collected.Add(tls.TypeApplicationData, 100) time.Sleep(time.Microsecond) - collected.Add(tls.TypeApplicationData) + collected.Add(tls.TypeApplicationData, 100) for i := 1; i < len(collected.data); i++ { suite.True(collected.data[i].timestamp.After(collected.data[i-1].timestamp)) diff --git a/mtglib/internal/doppel/scout_test.go b/mtglib/internal/doppel/scout_test.go index d9fe850..4fbd01c 100644 --- a/mtglib/internal/doppel/scout_test.go +++ b/mtglib/internal/doppel/scout_test.go @@ -22,9 +22,9 @@ func (suite *ScoutTestSuite) SetupSuite() { } func (suite *ScoutTestSuite) TestCollectResults() { - durations, err := suite.scout.Learn(suite.ctx) + result, err := suite.scout.Learn(suite.ctx) suite.NoError(err) - suite.Less(3, len(durations)) + suite.Less(3, len(result.Durations)) } func (suite *ScoutTestSuite) TestCollectNothing() { diff --git a/mtglib/internal/tls/fake/cert_probe.go b/mtglib/internal/tls/fake/cert_probe.go deleted file mode 100644 index 4c92968..0000000 --- a/mtglib/internal/tls/fake/cert_probe.go +++ /dev/null @@ -1,261 +0,0 @@ -package fake - -import ( - "crypto/tls" - "encoding/binary" - "encoding/json" - "fmt" - "net" - "os" - "sync" - "time" -) - -const ( - probeDialTimeout = 10 * time.Second - probeHandshakeTimeout = 10 * time.Second - defaultProbeCount = 15 - defaultCacheTTL = 24 * time.Hour - - tlsTypeChangeCipherSpec = 0x14 - tlsTypeApplicationData = 0x17 -) - -// CertProbeResult holds the measured encrypted handshake size. -type CertProbeResult struct { - Mean int `json:"mean"` - Jitter int `json:"jitter"` -} - -// CertProbeCache is the on-disk format for cached probe results. -type CertProbeCache struct { - Hostname string `json:"hostname"` - Port int `json:"port"` - Mean int `json:"mean"` - Jitter int `json:"jitter"` - ProbedAt time.Time `json:"probed_at"` -} - -// LoadCachedProbe reads a cached probe result from path. Returns the result -// and true if the cache exists, matches hostname:port, and is younger than ttl. -// Otherwise returns zero value and false. -func LoadCachedProbe(path, hostname string, port int, ttl time.Duration) (CertProbeResult, bool) { - if ttl <= 0 { - ttl = defaultCacheTTL - } - - data, err := os.ReadFile(path) - if err != nil { - return CertProbeResult{}, false - } - - var cache CertProbeCache - if err := json.Unmarshal(data, &cache); err != nil { - return CertProbeResult{}, false - } - - if cache.Hostname != hostname || cache.Port != port { - return CertProbeResult{}, false - } - - if time.Since(cache.ProbedAt) > ttl { - return CertProbeResult{}, false - } - - if cache.Mean <= 0 { - return CertProbeResult{}, false - } - - return CertProbeResult{Mean: cache.Mean, Jitter: cache.Jitter}, true -} - -// SaveCachedProbe writes a probe result to path as JSON. -func SaveCachedProbe(path, hostname string, port int, result CertProbeResult) error { - cache := CertProbeCache{ - Hostname: hostname, - Port: port, - Mean: result.Mean, - Jitter: result.Jitter, - ProbedAt: time.Now(), - } - - data, err := json.MarshalIndent(cache, "", " ") - if err != nil { - return err - } - - return os.WriteFile(path, data, 0o644) //nolint: gosec -} - -// ProbeCertSize connects to hostname:port via TLS multiple times and measures -// the total ApplicationData payload bytes sent by the server during the -// handshake (between ChangeCipherSpec and the first application-level data). -// This corresponds to EncryptedExtensions + Certificate + CertificateVerify + -// Finished in TLS 1.3, which is what the FakeTLS noise must mimic. -func ProbeCertSize(hostname string, port int, count int) (CertProbeResult, error) { - if count <= 0 { - count = defaultProbeCount - } - - addr := net.JoinHostPort(hostname, fmt.Sprintf("%d", port)) - sizes := make([]int, 0, count) - - for i := 0; i < count; i++ { - size, err := probeSingle(addr, hostname) - if err != nil { - if len(sizes) > 0 { - break // use what we have - } - - return CertProbeResult{}, fmt.Errorf("probe %d failed: %w", i, err) - } - - sizes = append(sizes, size) - } - - if len(sizes) == 0 { - return CertProbeResult{}, fmt.Errorf("no successful probes") - } - - // Calculate mean and jitter (max deviation from mean). - sum := 0 - for _, s := range sizes { - sum += s - } - - mean := sum / len(sizes) - - maxDev := 0 - for _, s := range sizes { - d := s - mean - if d < 0 { - d = -d - } - - if d > maxDev { - maxDev = d - } - } - - // Ensure minimum jitter of 100 bytes for variability. - if maxDev < 100 { - maxDev = 100 - } - - return CertProbeResult{Mean: mean, Jitter: maxDev}, nil -} - -// probeSingle does one TLS handshake and measures ApplicationData bytes -// received during the handshake. -func probeSingle(addr, hostname string) (int, error) { - rawConn, err := net.DialTimeout("tcp", addr, probeDialTimeout) - if err != nil { - return 0, err - } - defer rawConn.Close() //nolint: errcheck - - capture := &recordCapture{conn: rawConn} - - tlsConn := tls.Client(capture, &tls.Config{ - ServerName: hostname, - MinVersion: tls.VersionTLS12, - }) - tlsConn.SetDeadline(time.Now().Add(probeHandshakeTimeout)) //nolint: errcheck - - if err := tlsConn.Handshake(); err != nil { - return 0, err - } - - tlsConn.Close() //nolint: errcheck - - return capture.appDataBytes, nil -} - -// recordCapture wraps a net.Conn and parses the raw TLS record stream to -// measure ApplicationData payload sizes sent by the server during handshake. -// It tracks record boundaries by maintaining a state machine over Read calls. -type recordCapture struct { - conn net.Conn - mu sync.Mutex - appDataBytes int - seenCCS bool - done bool - - // Record boundary tracking for the read side. - readRemaining int // bytes left in current record payload - readHeaderBuf [5]byte - readHeaderPos int -} - -func (rc *recordCapture) Read(p []byte) (int, error) { - n, err := rc.conn.Read(p) - if n > 0 && !rc.done { - rc.mu.Lock() - rc.parseReadBytes(p[:n]) - rc.mu.Unlock() - } - - return n, err -} - -func (rc *recordCapture) parseReadBytes(data []byte) { - for len(data) > 0 { - if rc.readRemaining > 0 { - // Consuming payload of current record. - consume := rc.readRemaining - if consume > len(data) { - consume = len(data) - } - - rc.readRemaining -= consume - data = data[consume:] - - continue - } - - // Accumulate header bytes (5 bytes per record). - need := 5 - rc.readHeaderPos - if need > len(data) { - need = len(data) - } - - copy(rc.readHeaderBuf[rc.readHeaderPos:], data[:need]) - rc.readHeaderPos += need - data = data[need:] - - if rc.readHeaderPos < 5 { - return // incomplete header - } - - // Full header available. - recordType := rc.readHeaderBuf[0] - payloadLen := int(binary.BigEndian.Uint16(rc.readHeaderBuf[3:5])) - rc.readHeaderPos = 0 - rc.readRemaining = payloadLen - - if recordType == tlsTypeChangeCipherSpec { - rc.seenCCS = true - } else if recordType == tlsTypeApplicationData && rc.seenCCS { - rc.appDataBytes += payloadLen - } - } -} - -func (rc *recordCapture) Write(p []byte) (int, error) { - // After client writes post-CCS data, server handshake records are done. - if rc.seenCCS && rc.appDataBytes > 0 { - rc.done = true - } - - return rc.conn.Write(p) -} - -func (rc *recordCapture) Close() error { return rc.conn.Close() } -func (rc *recordCapture) LocalAddr() net.Addr { return rc.conn.LocalAddr() } -func (rc *recordCapture) RemoteAddr() net.Addr { return rc.conn.RemoteAddr() } -func (rc *recordCapture) SetDeadline(t time.Time) error { return rc.conn.SetDeadline(t) } -func (rc *recordCapture) SetReadDeadline(t time.Time) error { return rc.conn.SetReadDeadline(t) } -func (rc *recordCapture) SetWriteDeadline(t time.Time) error { return rc.conn.SetWriteDeadline(t) } - -// Ensure recordCapture implements net.Conn. -var _ net.Conn = (*recordCapture)(nil) diff --git a/mtglib/proxy.go b/mtglib/proxy.go index 5784f19..be05194 100644 --- a/mtglib/proxy.go +++ b/mtglib/proxy.go @@ -36,7 +36,6 @@ type Proxy struct { doppelGanger *doppel.Ganger clientObfuscatror obfuscation.Obfuscator - noiseParams fake.NoiseParams secret Secret network Network antiReplayCache AntiReplayCache @@ -193,7 +192,10 @@ func (p *Proxy) doFakeTLSHandshake(ctx *streamContext) bool { return false } - if err := fake.SendServerHello(ctx.clientConn, p.secret.Key[:], clientHello, p.noiseParams); err != nil { + gangerNoise := p.doppelGanger.NoiseParams() + noiseParams := fake.NoiseParams{Mean: gangerNoise.Mean, Jitter: gangerNoise.Jitter} + + if err := fake.SendServerHello(ctx.clientConn, p.secret.Key[:], clientHello, noiseParams); err != nil { p.logger.InfoError("cannot send welcome packet", err) return false } @@ -324,49 +326,9 @@ func NewProxy(opts ProxyOpts) (*Proxy, error) { logger := opts.getLogger("proxy") updatersLogger := logger.Named("telegram-updaters") - // Probe the fronting domain's cert chain size for noise calibration. - probeHost := opts.Secret.Host - probePort := opts.getDomainFrontingPort() - noiseParams := fake.NoiseParams{} - - probeCount := int(opts.NoiseProbeCount) - if probeCount <= 0 { - probeCount = 15 - } - - cacheTTL := opts.NoiseCacheTTL - - // Try loading from cache first. - if opts.NoiseCachePath != "" { - if cached, ok := fake.LoadCachedProbe(opts.NoiseCachePath, probeHost, probePort, cacheTTL); ok { - noiseParams = fake.NoiseParams(cached) - logger.Info(fmt.Sprintf("cert probe: loaded from cache, host=%s mean=%d jitter=%d", - probeHost, cached.Mean, cached.Jitter)) - } - } - - // If no cached result, probe live. - if noiseParams.Mean == 0 { - probeResult, probeErr := fake.ProbeCertSize(probeHost, probePort, probeCount) - if probeErr != nil { - logger.WarningError("cert probe failed, using default noise size", probeErr) - } else { - noiseParams = fake.NoiseParams(probeResult) - logger.Info(fmt.Sprintf("cert probe: host=%s mean=%d jitter=%d", - probeHost, probeResult.Mean, probeResult.Jitter)) - - if opts.NoiseCachePath != "" { - if saveErr := fake.SaveCachedProbe(opts.NoiseCachePath, probeHost, probePort, probeResult); saveErr != nil { - logger.WarningError("failed to save cert probe cache", saveErr) - } - } - } - } - proxy := &Proxy{ ctx: ctx, ctxCancel: cancel, - noiseParams: noiseParams, secret: opts.Secret, network: opts.Network, antiReplayCache: opts.AntiReplayCache, diff --git a/mtglib/proxy_opts.go b/mtglib/proxy_opts.go index 70120e5..3d07fbd 100644 --- a/mtglib/proxy_opts.go +++ b/mtglib/proxy_opts.go @@ -161,24 +161,6 @@ type ProxyOpts struct { // DoppelGangerDRS defines if TLS Dynamic Record Sizing is active. DoppelGangerDRS bool - // NoiseProbeCount is the number of TLS connections to make when probing - // the fronting domain's cert chain size for noise calibration. - // Default is 15. - // - // This is an optional setting. - NoiseProbeCount uint - - // NoiseCacheTTL is how long a cached cert probe result is considered - // valid. Default is 24 hours. - // - // This is an optional setting. - NoiseCacheTTL time.Duration - - // NoiseCachePath is the file path for caching cert probe results - // between restarts. If empty, no caching is performed. - // - // This is an optional setting. - NoiseCachePath string } func (p ProxyOpts) valid() error { From 026ec74dfd48f70f1c7181969a46c16fc2a9d3a2 Mon Sep 17 00:00:00 2001 From: Alexey Dolotov Date: Sat, 28 Mar 2026 13:24:39 +0300 Subject: [PATCH 07/22] Reduce per-connection memory overhead - Use sync.Pool for relay buffers instead of stack-allocated arrays. A [16379]byte on the goroutine stack forces Go to grow it to 32KB (next power of two). Pooled buffers keep goroutine stacks small. - Same fix for doppelganger write buffer ([16384]byte in conn.start). - Replace idle goroutines with context.AfterFunc in proxy.ServeConn and relay.Relay. These goroutines existed only to wait on ctx.Done() and close connections. AfterFunc achieves the same without allocating a goroutine until the context is actually cancelled. Net effect: at 3000 concurrent connections on a 1-vCPU/961MB VPS, the unmodified binary drops 246 connections and falls to 10 MB/s. With these changes: zero failures, 63 MB/s, 31% lower RSS. Closes #412 --- mtglib/internal/doppel/conn.go | 13 +++++++++++-- mtglib/internal/relay/relay.go | 19 ++++++++++++++----- mtglib/proxy.go | 6 +++--- 3 files changed, 28 insertions(+), 10 deletions(-) diff --git a/mtglib/internal/doppel/conn.go b/mtglib/internal/doppel/conn.go index cc81d91..e4342ba 100644 --- a/mtglib/internal/doppel/conn.go +++ b/mtglib/internal/doppel/conn.go @@ -9,6 +9,13 @@ import ( "github.com/9seconds/mtg/v2/mtglib/internal/tls" ) +var doppelBufPool = sync.Pool{ + New: func() any { + b := make([]byte, tls.MaxRecordSize) + return &b + }, +} + type Conn struct { essentials.Conn @@ -46,7 +53,9 @@ func (c Conn) Start() { } func (c Conn) start() { - buf := [tls.MaxRecordSize]byte{} + bp := doppelBufPool.Get().(*[]byte) + buf := *bp + defer doppelBufPool.Put(bp) for { select { @@ -68,7 +77,7 @@ func (c Conn) start() { continue } - if err := tls.WriteRecordInPlace(c.Conn, buf[:], n); err != nil { + if err := tls.WriteRecordInPlace(c.Conn, buf, n); err != nil { c.p.ctxCancel(err) return } diff --git a/mtglib/internal/relay/relay.go b/mtglib/internal/relay/relay.go index 986b3e2..70ee140 100644 --- a/mtglib/internal/relay/relay.go +++ b/mtglib/internal/relay/relay.go @@ -4,11 +4,19 @@ import ( "context" "errors" "io" + "sync" "github.com/9seconds/mtg/v2/essentials" "github.com/9seconds/mtg/v2/mtglib/internal/tls" ) +var bufPool = sync.Pool{ + New: func() any { + b := make([]byte, tls.MaxRecordPayloadSize) + return &b + }, +} + func Relay(ctx context.Context, log Logger, telegramConn, clientConn essentials.Conn) { defer telegramConn.Close() //nolint: errcheck defer clientConn.Close() //nolint: errcheck @@ -16,11 +24,11 @@ func Relay(ctx context.Context, log Logger, telegramConn, clientConn essentials. ctx, cancel := context.WithCancel(ctx) defer cancel() - go func() { - <-ctx.Done() + stop := context.AfterFunc(ctx, func() { telegramConn.Close() //nolint: errcheck clientConn.Close() //nolint: errcheck - }() + }) + defer stop() closeChan := make(chan struct{}) @@ -36,12 +44,13 @@ func Relay(ctx context.Context, log Logger, telegramConn, clientConn essentials. } func pump(log Logger, src, dst essentials.Conn, direction string) { - var buf [tls.MaxRecordPayloadSize]byte + bp := bufPool.Get().(*[]byte) + defer bufPool.Put(bp) defer src.CloseRead() //nolint: errcheck defer dst.CloseWrite() //nolint: errcheck - n, err := io.CopyBuffer(src, dst, buf[:]) + n, err := io.CopyBuffer(src, dst, *bp) switch { case err == nil: diff --git a/mtglib/proxy.go b/mtglib/proxy.go index be05194..44d426f 100644 --- a/mtglib/proxy.go +++ b/mtglib/proxy.go @@ -65,10 +65,10 @@ func (p *Proxy) ServeConn(conn essentials.Conn) { ctx := newStreamContext(p.ctx, p.logger, conn) defer ctx.Close() - go func() { - <-ctx.Done() + stop := context.AfterFunc(ctx, func() { ctx.Close() - }() + }) + defer stop() p.eventStream.Send(ctx, NewEventStart(ctx.streamID, ctx.ClientIP())) ctx.logger.Info("Stream has been started") From 01402bdba2977a3cc2f86e92d460e47e207e5219 Mon Sep 17 00:00:00 2001 From: Alexey Dolotov Date: Sat, 28 Mar 2026 22:31:47 +0300 Subject: [PATCH 08/22] fix: prevent index out of range panic on 32-bit platforms On 32-bit architectures (e.g. ARM7), int is 32 bits wide. Casting a uint32 hash value to int can overflow, producing a negative number. Go's modulo operator preserves the sign, so the channel index can become -1, causing a panic. Perform the modulo in uint32 space before indexing to ensure the result is always non-negative. Fixes #413 --- events/event_stream.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/events/event_stream.go b/events/event_stream.go index b71db4c..516f77e 100644 --- a/events/event_stream.go +++ b/events/event_stream.go @@ -38,7 +38,7 @@ func (e EventStream) Send(ctx context.Context, evt mtglib.Event) { select { case <-ctx.Done(): case <-e.ctx.Done(): - case e.chans[int(chanNo)%len(e.chans)] <- evt: + case e.chans[chanNo%uint32(len(e.chans))] <- evt: } } From 836090ebdf743da4c11882cc7a86809e56497bbb Mon Sep 17 00:00:00 2001 From: Alexey Dolotov Date: Sat, 28 Mar 2026 22:47:39 +0300 Subject: [PATCH 09/22] fix: apply idle timeout to domain fronting relay connections Domain fronting relay (for non-Telegram traffic) had no idle timeout, causing worker pool exhaustion under traffic spikes. The ProxyOpts.IdleTimeout field existed but was never wired into the proxy. Now domain fronting connections are wrapped with per-read/write deadlines reset to the configured idle timeout (default 1m), so stale or slowloris-style connections are reaped promptly. Fixes #378 --- internal/cli/run_proxy.go | 1 + mtglib/conns.go | 19 +++++++++++++++++++ mtglib/proxy.go | 6 ++++-- mtglib/proxy_opts.go | 8 ++++++++ 4 files changed, 32 insertions(+), 2 deletions(-) diff --git a/internal/cli/run_proxy.go b/internal/cli/run_proxy.go index 643f645..8eabc3d 100644 --- a/internal/cli/run_proxy.go +++ b/internal/cli/run_proxy.go @@ -262,6 +262,7 @@ func runProxy(conf *config.Config, version string) error { //nolint: funlen AllowFallbackOnUnknownDC: conf.AllowFallbackOnUnknownDC.Get(false), TolerateTimeSkewness: conf.TolerateTimeSkewness.Value, + IdleTimeout: conf.Network.Timeout.Idle.Get(mtglib.DefaultIdleTimeout), DoppelGangerURLs: doppelGangerURLs, DoppelGangerPerRaid: conf.Defense.Doppelganger.Repeats.Get(mtglib.DoppelGangerPerRaid), diff --git a/mtglib/conns.go b/mtglib/conns.go index 8219b75..e3b60e8 100644 --- a/mtglib/conns.go +++ b/mtglib/conns.go @@ -6,6 +6,7 @@ import ( "fmt" "io" "net" + "time" "github.com/9seconds/mtg/v2/essentials" "github.com/pires/go-proxyproto" @@ -95,3 +96,21 @@ func newConnProxyProtocol(source, target essentials.Conn) *connProxyProtocol { sourceAddr: source.RemoteAddr(), } } + +type connIdleTimeout struct { + essentials.Conn + + timeout time.Duration +} + +func (c connIdleTimeout) Read(b []byte) (int, error) { + c.Conn.SetReadDeadline(time.Now().Add(c.timeout)) //nolint: errcheck + + return c.Conn.Read(b) //nolint: wrapcheck +} + +func (c connIdleTimeout) Write(b []byte) (int, error) { + c.Conn.SetWriteDeadline(time.Now().Add(c.timeout)) //nolint: errcheck + + return c.Conn.Write(b) //nolint: wrapcheck +} diff --git a/mtglib/proxy.go b/mtglib/proxy.go index be05194..65f52ad 100644 --- a/mtglib/proxy.go +++ b/mtglib/proxy.go @@ -27,6 +27,7 @@ type Proxy struct { allowFallbackOnUnknownDC bool tolerateTimeSkewness time.Duration + idleTimeout time.Duration domainFrontingPort int domainFrontingIP string domainFrontingProxyProtocol bool @@ -306,8 +307,8 @@ func (p *Proxy) doDomainFronting(ctx *streamContext, conn *connRewind) { relay.Relay( ctx, ctx.logger.Named("domain-fronting"), - frontConn, - conn, + connIdleTimeout{Conn: frontConn, timeout: p.idleTimeout}, + connIdleTimeout{Conn: conn, timeout: p.idleTimeout}, ) } @@ -339,6 +340,7 @@ func NewProxy(opts ProxyOpts) (*Proxy, error) { domainFrontingPort: opts.getDomainFrontingPort(), domainFrontingIP: opts.DomainFrontingIP, tolerateTimeSkewness: opts.getTolerateTimeSkewness(), + idleTimeout: opts.getIdleTimeout(), allowFallbackOnUnknownDC: opts.AllowFallbackOnUnknownDC, telegram: tg, doppelGanger: doppel.NewGanger( diff --git a/mtglib/proxy_opts.go b/mtglib/proxy_opts.go index 3d07fbd..beea1c6 100644 --- a/mtglib/proxy_opts.go +++ b/mtglib/proxy_opts.go @@ -216,6 +216,14 @@ func (p ProxyOpts) getPreferIP() string { return p.PreferIP } +func (p ProxyOpts) getIdleTimeout() time.Duration { + if p.IdleTimeout == 0 { + return DefaultIdleTimeout + } + + return p.IdleTimeout +} + func (p ProxyOpts) getLogger(name string) Logger { return p.Logger.Named(name) } From 289bb283b1d479e93512d6a795791fb62de1e8a1 Mon Sep 17 00:00:00 2001 From: Alexey Dolotov Date: Sat, 28 Mar 2026 22:52:39 +0300 Subject: [PATCH 10/22] fix: close connection on worker pool overflow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When the worker pool rejected a connection (ErrPoolOverload), the accepted net.Conn was never closed — leaking a file descriptor and TCP socket per rejected connection. Under sustained traffic spikes this compounds the problem: leaked descriptors reduce the capacity for new dials (including to the fronting domain), accelerating the failure cascade described in #378. --- mtglib/proxy.go | 1 + 1 file changed, 1 insertion(+) diff --git a/mtglib/proxy.go b/mtglib/proxy.go index 65f52ad..cb252ed 100644 --- a/mtglib/proxy.go +++ b/mtglib/proxy.go @@ -152,6 +152,7 @@ func (p *Proxy) Serve(listener net.Listener) error { case errors.Is(err, ants.ErrPoolClosed): return nil case errors.Is(err, ants.ErrPoolOverload): + conn.Close() //nolint: errcheck logger.Info("connection was concurrency limited") p.eventStream.Send(p.ctx, NewEventConcurrencyLimited()) } From f355512aa698df5334a253a3b1d8376dc0555c92 Mon Sep 17 00:00:00 2001 From: Alexey Dolotov Date: Sat, 28 Mar 2026 22:59:32 +0300 Subject: [PATCH 11/22] fix: address staticcheck lint issues - avoid deprecated DefaultIdleTimeout, use time.Minute directly - simplify embedded field selectors (QF1008) --- internal/cli/run_proxy.go | 3 ++- mtglib/conns.go | 4 ++-- mtglib/proxy_opts.go | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/internal/cli/run_proxy.go b/internal/cli/run_proxy.go index 8eabc3d..c57374b 100644 --- a/internal/cli/run_proxy.go +++ b/internal/cli/run_proxy.go @@ -5,6 +5,7 @@ import ( "fmt" "net" "os" + "time" "github.com/9seconds/mtg/v2/antireplay" "github.com/9seconds/mtg/v2/events" @@ -262,7 +263,7 @@ func runProxy(conf *config.Config, version string) error { //nolint: funlen AllowFallbackOnUnknownDC: conf.AllowFallbackOnUnknownDC.Get(false), TolerateTimeSkewness: conf.TolerateTimeSkewness.Value, - IdleTimeout: conf.Network.Timeout.Idle.Get(mtglib.DefaultIdleTimeout), + IdleTimeout: conf.Network.Timeout.Idle.Get(time.Minute), DoppelGangerURLs: doppelGangerURLs, DoppelGangerPerRaid: conf.Defense.Doppelganger.Repeats.Get(mtglib.DoppelGangerPerRaid), diff --git a/mtglib/conns.go b/mtglib/conns.go index e3b60e8..12f19d1 100644 --- a/mtglib/conns.go +++ b/mtglib/conns.go @@ -104,13 +104,13 @@ type connIdleTimeout struct { } func (c connIdleTimeout) Read(b []byte) (int, error) { - c.Conn.SetReadDeadline(time.Now().Add(c.timeout)) //nolint: errcheck + c.SetReadDeadline(time.Now().Add(c.timeout)) //nolint: errcheck return c.Conn.Read(b) //nolint: wrapcheck } func (c connIdleTimeout) Write(b []byte) (int, error) { - c.Conn.SetWriteDeadline(time.Now().Add(c.timeout)) //nolint: errcheck + c.SetWriteDeadline(time.Now().Add(c.timeout)) //nolint: errcheck return c.Conn.Write(b) //nolint: wrapcheck } diff --git a/mtglib/proxy_opts.go b/mtglib/proxy_opts.go index beea1c6..102b279 100644 --- a/mtglib/proxy_opts.go +++ b/mtglib/proxy_opts.go @@ -218,7 +218,7 @@ func (p ProxyOpts) getPreferIP() string { func (p ProxyOpts) getIdleTimeout() time.Duration { if p.IdleTimeout == 0 { - return DefaultIdleTimeout + return time.Minute } return p.IdleTimeout From 46c33f15322e2f40c8b6a1df6bfbad926e104678 Mon Sep 17 00:00:00 2001 From: Alexey Dolotov Date: Sat, 28 Mar 2026 23:04:33 +0300 Subject: [PATCH 12/22] ci: retrigger (flaky antireplay test) From 450381ee1635319d95c50e905fc67a0691c46b8a Mon Sep 17 00:00:00 2001 From: Alexey Dolotov Date: Sat, 28 Mar 2026 23:13:35 +0300 Subject: [PATCH 13/22] ci: retrigger (flaky antireplay test) From 2b07c0037ec9bdf694d14b39a6cd9f09bb6c9b5e Mon Sep 17 00:00:00 2001 From: Alexey Dolotov Date: Sun, 29 Mar 2026 00:39:10 +0300 Subject: [PATCH 14/22] Add public-ipv4/public-ipv6 config options for manual IP override On some servers ifconfig.co is unreachable (e.g. Hetzner, AdGuard DNS blocklists), causing 'mtg doctor' SNI-DNS check and 'mtg access' link generation to fail. New config options allow specifying public IPs manually, with automatic detection as fallback. Fixes #405 --- example.config.toml | 7 +++++ internal/cli/access.go | 6 +++++ internal/cli/doctor.go | 13 +++++++--- internal/config/config.go | 2 ++ internal/config/config_test.go | 26 +++++++++++++++++++ internal/config/parse.go | 2 ++ internal/config/testdata/public_ip.toml | 4 +++ .../config/testdata/public_ip_invalid.toml | 3 +++ .../config/testdata/public_ip_v4_only.toml | 3 +++ 9 files changed, 63 insertions(+), 3 deletions(-) create mode 100644 internal/config/testdata/public_ip.toml create mode 100644 internal/config/testdata/public_ip_invalid.toml create mode 100644 internal/config/testdata/public_ip_v4_only.toml diff --git a/example.config.toml b/example.config.toml index c0ca32c..abc0bff 100644 --- a/example.config.toml +++ b/example.config.toml @@ -48,6 +48,13 @@ concurrency = 8192 # Only ipv4 connectivity is used prefer-ip = "prefer-ipv6" +# Public IP addresses of this server. Used by 'mtg access' to generate +# proxy links and by 'mtg doctor' to validate SNI-DNS match. +# If not set, mtg tries to detect them automatically via ifconfig.co. +# Set these if ifconfig.co is unreachable from your server. +# public-ipv4 = "1.2.3.4" +# public-ipv6 = "2001:db8::1" + # If this setting is set, then mtg will try to get proxy updates from Telegram # Usually this is completely fine to have it disabled, because mtg has a list # of some core proxies hardcoded. diff --git a/internal/cli/access.go b/internal/cli/access.go index ec266e9..c93c97b 100644 --- a/internal/cli/access.go +++ b/internal/cli/access.go @@ -58,6 +58,9 @@ func (a *Access) Run(cli *CLI, version string) error { wg.Go(func() { ip := a.PublicIPv4 + if ip == nil { + ip = conf.PublicIPv4.Get(nil) + } if ip == nil { ip = getIP(ntw, "tcp4") } @@ -70,6 +73,9 @@ func (a *Access) Run(cli *CLI, version string) error { }) wg.Go(func() { ip := a.PublicIPv6 + if ip == nil { + ip = conf.PublicIPv6.Get(nil) + } if ip == nil { ip = getIP(ntw, "tcp6") } diff --git a/internal/cli/doctor.go b/internal/cli/doctor.go index 21ea0e1..b00c3e0 100644 --- a/internal/cli/doctor.go +++ b/internal/cli/doctor.go @@ -332,13 +332,20 @@ func (d *Doctor) checkSecretHost(resolver *net.Resolver, ntw mtglib.Network) boo return false } - ourIP4 := getIP(ntw, "tcp4") - ourIP6 := getIP(ntw, "tcp6") + ourIP4 := d.conf.PublicIPv4.Get(nil) + if ourIP4 == nil { + ourIP4 = getIP(ntw, "tcp4") + } + + ourIP6 := d.conf.PublicIPv6.Get(nil) + if ourIP6 == nil { + ourIP6 = getIP(ntw, "tcp6") + } if ourIP4 == nil && ourIP6 == nil { tplError.Execute(os.Stdout, map[string]any{ //nolint: errcheck "description": "cannot detect public IP address", - "error": errors.New("ifconfig.co is unreachable for both IPv4 and IPv6"), + "error": errors.New("cannot detect automatically and public-ipv4/public-ipv6 are not set in config"), }) return false } diff --git a/internal/config/config.go b/internal/config/config.go index 6bb0a6c..4ca566c 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -35,6 +35,8 @@ type Config struct { DomainFrontingProxyProtocol TypeBool `json:"domainFrontingProxyProtocol"` TolerateTimeSkewness TypeDuration `json:"tolerateTimeSkewness"` Concurrency TypeConcurrency `json:"concurrency"` + PublicIPv4 TypeIP `json:"publicIpv4"` + PublicIPv6 TypeIP `json:"publicIpv6"` DomainFronting struct { IP TypeIP `json:"ip"` Port TypePort `json:"port"` diff --git a/internal/config/config_test.go b/internal/config/config_test.go index e6b19e0..c291c29 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -42,6 +42,32 @@ func (suite *ConfigTestSuite) TestParseMinimalConfig() { suite.Equal("0.0.0.0:3128", conf.BindTo.String()) } +func (suite *ConfigTestSuite) TestParsePublicIP() { + conf, err := config.Parse(suite.ReadConfig("public_ip.toml")) + suite.NoError(err) + suite.Equal("203.0.113.1", conf.PublicIPv4.Get(nil).String()) + suite.Equal("2001:db8::1", conf.PublicIPv6.Get(nil).String()) +} + +func (suite *ConfigTestSuite) TestParsePublicIPv4Only() { + conf, err := config.Parse(suite.ReadConfig("public_ip_v4_only.toml")) + suite.NoError(err) + suite.Equal("203.0.113.1", conf.PublicIPv4.Get(nil).String()) + suite.Nil(conf.PublicIPv6.Get(nil)) +} + +func (suite *ConfigTestSuite) TestParsePublicIPInvalid() { + _, err := config.Parse(suite.ReadConfig("public_ip_invalid.toml")) + suite.Error(err) +} + +func (suite *ConfigTestSuite) TestParsePublicIPNotSet() { + conf, err := config.Parse(suite.ReadConfig("minimal.toml")) + suite.NoError(err) + suite.Nil(conf.PublicIPv4.Get(nil)) + suite.Nil(conf.PublicIPv6.Get(nil)) +} + func (suite *ConfigTestSuite) TestString() { conf, err := config.Parse(suite.ReadConfig("minimal.toml")) suite.NoError(err) diff --git a/internal/config/parse.go b/internal/config/parse.go index 6cf824d..fd60708 100644 --- a/internal/config/parse.go +++ b/internal/config/parse.go @@ -21,6 +21,8 @@ type tomlConfig struct { DomainFrontingProxyProtocol bool `toml:"domain-fronting-proxy-protocol" json:"domainFrontingProxyProtocol,omitempty"` TolerateTimeSkewness string `toml:"tolerate-time-skewness" json:"tolerateTimeSkewness,omitempty"` Concurrency uint `toml:"concurrency" json:"concurrency,omitempty"` + PublicIPv4 string `toml:"public-ipv4" json:"publicIpv4,omitempty"` + PublicIPv6 string `toml:"public-ipv6" json:"publicIpv6,omitempty"` DomainFronting struct { IP string `toml:"ip" json:"ip,omitempty"` Port uint `toml:"port" json:"port,omitempty"` diff --git a/internal/config/testdata/public_ip.toml b/internal/config/testdata/public_ip.toml new file mode 100644 index 0000000..6e4673c --- /dev/null +++ b/internal/config/testdata/public_ip.toml @@ -0,0 +1,4 @@ +secret = "7oe1GqLy6TBc38CV3jx7q09nb29nbGUuY29t" +bind-to = "0.0.0.0:3128" +public-ipv4 = "203.0.113.1" +public-ipv6 = "2001:db8::1" diff --git a/internal/config/testdata/public_ip_invalid.toml b/internal/config/testdata/public_ip_invalid.toml new file mode 100644 index 0000000..0c97776 --- /dev/null +++ b/internal/config/testdata/public_ip_invalid.toml @@ -0,0 +1,3 @@ +secret = "7oe1GqLy6TBc38CV3jx7q09nb29nbGUuY29t" +bind-to = "0.0.0.0:3128" +public-ipv4 = "not-an-ip" diff --git a/internal/config/testdata/public_ip_v4_only.toml b/internal/config/testdata/public_ip_v4_only.toml new file mode 100644 index 0000000..f0c63c8 --- /dev/null +++ b/internal/config/testdata/public_ip_v4_only.toml @@ -0,0 +1,3 @@ +secret = "7oe1GqLy6TBc38CV3jx7q09nb29nbGUuY29t" +bind-to = "0.0.0.0:3128" +public-ipv4 = "203.0.113.1" From 7b62e06e364774bebd3f9459f806746c97721bf9 Mon Sep 17 00:00:00 2001 From: Alexey Dolotov Date: Sun, 29 Mar 2026 00:53:20 +0300 Subject: [PATCH 15/22] Retry CI: flaky antireplay bloom filter test From 0c030646f9f2866640557f536722124d86c7d108 Mon Sep 17 00:00:00 2001 From: 9seconds Date: Sun, 29 Mar 2026 15:39:33 +0200 Subject: [PATCH 16/22] Remove clock goroutine This is a followup for https://github.com/9seconds/mtg/issues/412 it makes sense to manage timers inplace instead of creating for new goroutines: saves memory --- mtglib/internal/doppel/clock.go | 35 ------------ mtglib/internal/doppel/clock_test.go | 80 ---------------------------- mtglib/internal/doppel/conn.go | 27 ++++------ mtglib/internal/doppel/conn_test.go | 4 +- mtglib/internal/doppel/ganger.go | 6 +-- mtglib/internal/doppel/stats.go | 4 +- 6 files changed, 17 insertions(+), 139 deletions(-) delete mode 100644 mtglib/internal/doppel/clock.go delete mode 100644 mtglib/internal/doppel/clock_test.go diff --git a/mtglib/internal/doppel/clock.go b/mtglib/internal/doppel/clock.go deleted file mode 100644 index 8b29078..0000000 --- a/mtglib/internal/doppel/clock.go +++ /dev/null @@ -1,35 +0,0 @@ -package doppel - -import ( - "context" - "time" -) - -type Clock struct { - stats *Stats - tick chan struct{} -} - -func (c Clock) Start(ctx context.Context) { - tickTock := time.NewTimer(c.stats.Delay()) - defer func() { - tickTock.Stop() - select { - case <-tickTock.C: - default: - } - }() - - for { - select { - case <-ctx.Done(): - return - case <-tickTock.C: - select { - case <-ctx.Done(): - case c.tick <- struct{}{}: - } - tickTock.Reset(c.stats.Delay()) - } - } -} diff --git a/mtglib/internal/doppel/clock_test.go b/mtglib/internal/doppel/clock_test.go deleted file mode 100644 index 37fbb62..0000000 --- a/mtglib/internal/doppel/clock_test.go +++ /dev/null @@ -1,80 +0,0 @@ -package doppel - -import ( - "context" - "sync" - "testing" - "time" - - "github.com/stretchr/testify/suite" -) - -type ClockTestSuite struct { - suite.Suite - - clock Clock - wg sync.WaitGroup - ctx context.Context - ctxCancel context.CancelFunc -} - -func (suite *ClockTestSuite) SetupTest() { - ctx, cancel := context.WithCancel(context.Background()) - - suite.ctx = ctx - suite.ctxCancel = cancel - suite.clock = Clock{ - stats: &Stats{ - k: StatsDefaultK, - lambda: StatsDefaultLambda, - }, - tick: make(chan struct{}), - } - - suite.wg.Go(func() { - suite.clock.Start(suite.ctx) - }) -} - -func (suite *ClockTestSuite) TearDownTest() { - suite.ctxCancel() - suite.wg.Wait() -} - -func (suite *ClockTestSuite) TestTicks() { - received := 0 - - for range 3 { - select { - case <-suite.clock.tick: - received++ - case <-time.After(2 * time.Second): - suite.Fail("timed out waiting for tick") - } - } - - suite.Equal(3, received) -} - -func (suite *ClockTestSuite) TestStopsOnCancel() { - select { - case <-suite.clock.tick: - case <-time.After(2 * time.Second): - suite.Fail("timed out waiting for first tick") - } - - suite.ctxCancel() - - time.Sleep(50 * time.Millisecond) - - select { - case <-suite.clock.tick: - suite.Fail("received tick after cancel") - default: - } -} - -func TestClock(t *testing.T) { - t.Parallel() - suite.Run(t, &ClockTestSuite{}) -} diff --git a/mtglib/internal/doppel/conn.go b/mtglib/internal/doppel/conn.go index e4342ba..910cbfc 100644 --- a/mtglib/internal/doppel/conn.go +++ b/mtglib/internal/doppel/conn.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "sync" + "time" "github.com/9seconds/mtg/v2/essentials" "github.com/9seconds/mtg/v2/mtglib/internal/tls" @@ -25,7 +26,7 @@ type Conn struct { type connPayload struct { ctx context.Context ctxCancel context.CancelCauseFunc - clock Clock + stats Stats wg sync.WaitGroup writeStream bytes.Buffer writtenCond sync.Cond @@ -46,25 +47,23 @@ func (c Conn) Write(p []byte) (int, error) { return len(p), context.Cause(c.p.ctx) } -func (c Conn) Start() { - c.p.wg.Go(func() { - c.start() - }) -} - func (c Conn) start() { bp := doppelBufPool.Get().(*[]byte) buf := *bp defer doppelBufPool.Put(bp) + timer := time.NewTimer(c.p.stats.Delay()) + defer timer.Stop() + for { select { case <-c.p.ctx.Done(): return - case <-c.p.clock.tick: + case <-timer.C: + timer.Reset(c.p.stats.Delay()) } - size := c.p.clock.stats.Size() + size := c.p.stats.Size() c.p.writtenCond.L.Lock() for c.p.writeStream.Len() == 0 && !c.p.done { @@ -95,28 +94,22 @@ func (c Conn) Stop() { c.p.wg.Wait() } -func NewConn(ctx context.Context, conn essentials.Conn, stats *Stats) Conn { +func NewConn(ctx context.Context, conn essentials.Conn, stats Stats) Conn { ctx, cancel := context.WithCancelCause(ctx) rv := Conn{ Conn: conn, p: &connPayload{ ctx: ctx, ctxCancel: cancel, + stats: stats, writtenCond: sync.Cond{ L: &sync.Mutex{}, }, - clock: Clock{ - stats: stats, - tick: make(chan struct{}), - }, }, } rv.p.writeStream.Grow(tls.DefaultBufferSize) - rv.p.wg.Go(func() { - rv.p.clock.Start(ctx) - }) rv.p.wg.Go(func() { rv.start() }) diff --git a/mtglib/internal/doppel/conn_test.go b/mtglib/internal/doppel/conn_test.go index 8501469..4827c83 100644 --- a/mtglib/internal/doppel/conn_test.go +++ b/mtglib/internal/doppel/conn_test.go @@ -63,7 +63,7 @@ func (suite *ConnTestSuite) TearDownTest() { } func (suite *ConnTestSuite) makeConn() Conn { - return NewConn(suite.ctx, suite.connMock, &Stats{ + return NewConn(suite.ctx, suite.connMock, Stats{ k: 2.0, lambda: 0.01, }) @@ -152,7 +152,7 @@ func (suite *ConnTestSuite) TestStopDoesNotDeadlockWhenStartIsWaiting() { ctx, cancel := context.WithCancel(suite.ctx) defer cancel() - c := NewConn(ctx, suite.connMock, &Stats{ + c := NewConn(ctx, suite.connMock, Stats{ k: 2.0, lambda: 0.01, }) diff --git a/mtglib/internal/doppel/ganger.go b/mtglib/internal/doppel/ganger.go index 740acd7..f9d2ad7 100644 --- a/mtglib/internal/doppel/ganger.go +++ b/mtglib/internal/doppel/ganger.go @@ -47,7 +47,7 @@ type Ganger struct { drs bool - stats *Stats + stats Stats durations []time.Duration certSizes []int @@ -113,7 +113,7 @@ func (g *Ganger) run() { scoutCollectedChan := make(chan scoutRaidResult) currentScoutCollectedChan := scoutCollectedChan - updatedStatsChan := make(chan *Stats) + updatedStatsChan := make(chan Stats) g.wg.Go(func() { g.runScoutRaid(scoutCollectedChan) @@ -256,7 +256,7 @@ func NewGanger( scoutRaidEach: scoutEach, scoutRaidRepeats: scoutRepeats, drs: drs, - stats: &Stats{ + stats: Stats{ k: StatsDefaultK, lambda: StatsDefaultLambda, drs: drs, diff --git a/mtglib/internal/doppel/stats.go b/mtglib/internal/doppel/stats.go index 18371b6..b68eebd 100644 --- a/mtglib/internal/doppel/stats.go +++ b/mtglib/internal/doppel/stats.go @@ -112,7 +112,7 @@ func (d *Stats) Size() int { return TLSRecordSizeMax } -func NewStats(durations []time.Duration, drs bool) *Stats { +func NewStats(durations []time.Duration, drs bool) Stats { n := float64(len(durations)) // in milliseconds @@ -162,7 +162,7 @@ func NewStats(durations []time.Duration, drs bool) *Stats { // λ = (Σxᵢᵏ / n)^(1/k) lambda := math.Pow(sumXK/n, 1.0/k) - return &Stats{ + return Stats{ k: k, lambda: lambda, drs: drs, From ec271baab04db816c646ba1382de288c16295b64 Mon Sep 17 00:00:00 2001 From: Alexey Dolotov Date: Sun, 29 Mar 2026 16:59:30 +0300 Subject: [PATCH 17/22] fix: apply idle timeout to Telegram relay Wrap both sides of the Telegram relay in connIdleTimeout, same as already done for domain fronting in #416. Without this, if a client disappears (network drop, battery dies), the TCP connection stays formally alive and the goroutine in the worker pool blocks on io.CopyBuffer indefinitely. Under mass client disconnects this accumulates zombie goroutines. Fixes #417 --- mtglib/proxy.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mtglib/proxy.go b/mtglib/proxy.go index fe5eb8d..8a904ee 100644 --- a/mtglib/proxy.go +++ b/mtglib/proxy.go @@ -105,8 +105,8 @@ func (p *Proxy) ServeConn(conn essentials.Conn) { relay.Relay( ctx, ctx.logger.Named("relay"), - ctx.telegramConn, - ctx.clientConn, + connIdleTimeout{Conn: ctx.telegramConn, timeout: p.idleTimeout}, + connIdleTimeout{Conn: ctx.clientConn, timeout: p.idleTimeout}, ) } From 87988326ab24a85237ea49cc3e8b0e9553d063b9 Mon Sep 17 00:00:00 2001 From: Alexey Dolotov Date: Sun, 29 Mar 2026 17:02:53 +0300 Subject: [PATCH 18/22] retry CI From d3a090d6b4f35f93f7c2220e2239147140e0571a Mon Sep 17 00:00:00 2001 From: 9seconds Date: Sun, 29 Mar 2026 21:42:26 +0200 Subject: [PATCH 19/22] Decrease a relay buffer size for MIPS devices --- mtglib/internal/relay/pool_settings_mips.go | 13 +++++++++++++ mtglib/internal/relay/pool_settings_other.go | 9 +++++++++ mtglib/internal/relay/pools.go | 18 ++++++++++++++++++ mtglib/internal/relay/relay.go | 15 +++------------ 4 files changed, 43 insertions(+), 12 deletions(-) create mode 100644 mtglib/internal/relay/pool_settings_mips.go create mode 100644 mtglib/internal/relay/pool_settings_other.go create mode 100644 mtglib/internal/relay/pools.go diff --git a/mtglib/internal/relay/pool_settings_mips.go b/mtglib/internal/relay/pool_settings_mips.go new file mode 100644 index 0000000..21d34a9 --- /dev/null +++ b/mtglib/internal/relay/pool_settings_mips.go @@ -0,0 +1,13 @@ +//go:build mips || mipsle + +package relay + +import "github.com/9seconds/mtg/v2/mtglib/internal/tls" + +const ( + // MIPS is quite short in resources, and usually it means that it will run + // on Microtiks, OpenWRT-based routers or similar hardware. I think it worth + // to sacrifice a number of read syscalls (read, CPU load) to shrink + // limited RAM resources. + bufPoolSize = tls.MaxRecordPayloadSize / 2 +) diff --git a/mtglib/internal/relay/pool_settings_other.go b/mtglib/internal/relay/pool_settings_other.go new file mode 100644 index 0000000..a5f1c14 --- /dev/null +++ b/mtglib/internal/relay/pool_settings_other.go @@ -0,0 +1,9 @@ +//go:build !mips && !mipsle + +package relay + +import "github.com/9seconds/mtg/v2/mtglib/internal/tls" + +const ( + bufPoolSize = tls.MaxRecordPayloadSize +) diff --git a/mtglib/internal/relay/pools.go b/mtglib/internal/relay/pools.go new file mode 100644 index 0000000..8fb1230 --- /dev/null +++ b/mtglib/internal/relay/pools.go @@ -0,0 +1,18 @@ +package relay + +import "sync" + +var bufPool = sync.Pool{ + New: func() any { + b := make([]byte, bufPoolSize) + return &b + }, +} + +func acquireBuffer() *[]byte { + return bufPool.Get().(*[]byte) +} + +func releaseBuffer(p *[]byte) { + bufPool.Put(p) +} diff --git a/mtglib/internal/relay/relay.go b/mtglib/internal/relay/relay.go index 70ee140..e734b71 100644 --- a/mtglib/internal/relay/relay.go +++ b/mtglib/internal/relay/relay.go @@ -4,19 +4,10 @@ import ( "context" "errors" "io" - "sync" "github.com/9seconds/mtg/v2/essentials" - "github.com/9seconds/mtg/v2/mtglib/internal/tls" ) -var bufPool = sync.Pool{ - New: func() any { - b := make([]byte, tls.MaxRecordPayloadSize) - return &b - }, -} - func Relay(ctx context.Context, log Logger, telegramConn, clientConn essentials.Conn) { defer telegramConn.Close() //nolint: errcheck defer clientConn.Close() //nolint: errcheck @@ -44,13 +35,13 @@ func Relay(ctx context.Context, log Logger, telegramConn, clientConn essentials. } func pump(log Logger, src, dst essentials.Conn, direction string) { - bp := bufPool.Get().(*[]byte) - defer bufPool.Put(bp) + buf := acquireBuffer() + defer releaseBuffer(buf) defer src.CloseRead() //nolint: errcheck defer dst.CloseWrite() //nolint: errcheck - n, err := io.CopyBuffer(src, dst, *bp) + n, err := io.CopyBuffer(src, dst, *buf) switch { case err == nil: From 018bd2fdc1faa36a3bf7dd719a6f7becf46c462d Mon Sep 17 00:00:00 2001 From: 9seconds Date: Sun, 29 Mar 2026 22:11:39 +0200 Subject: [PATCH 20/22] Run release build --- .github/workflows/ci.yaml | 33 +++++++++++++++++++ ...s_mips.go => pool_settings_constrained.go} | 0 2 files changed, 33 insertions(+) rename mtglib/internal/relay/{pool_settings_mips.go => pool_settings_constrained.go} (100%) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 0161e01..a71e3a2 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -119,6 +119,39 @@ jobs: - name: Run linter run: mise tasks run lint + artifacts: + name: Build release artifacts + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + submodules: recursive + + - uses: jdx/mise-action@v3 + name: Install mise + + - name: Cache Go modules + uses: actions/cache@v5 + with: + path: ~/go/pkg/mod + key: ${{ runner.os }}-gomod-${{ hashFiles('go.sum') }} + restore-keys: | + ${{ runner.os }}-gomod- + + - name: Cache cross-compilation build + uses: actions/cache@v5 + with: + path: ~/.cache/go-build + key: ${{ runner.os }}-goreleaser-${{ hashFiles('go.sum') }}-${{ hashFiles('**/*.go') }} + restore-keys: | + ${{ runner.os }}-goreleaser-${{ hashFiles('go.sum') }}- + ${{ runner.os }}-goreleaser- + + - name: Run release + run: mise tasks run release + docker: name: Docker runs-on: ubuntu-latest diff --git a/mtglib/internal/relay/pool_settings_mips.go b/mtglib/internal/relay/pool_settings_constrained.go similarity index 100% rename from mtglib/internal/relay/pool_settings_mips.go rename to mtglib/internal/relay/pool_settings_constrained.go From de48e177b18f0c6604c81f068cf8c2b890924940 Mon Sep 17 00:00:00 2001 From: 9seconds Date: Sun, 29 Mar 2026 23:16:06 +0200 Subject: [PATCH 21/22] Update depndencies --- go.mod | 4 ++-- go.sum | 17 ++++------------- 2 files changed, 6 insertions(+), 15 deletions(-) diff --git a/go.mod b/go.mod index c0ce65b..5334e45 100644 --- a/go.mod +++ b/go.mod @@ -15,7 +15,7 @@ require ( github.com/prometheus/client_golang v1.23.2 github.com/prometheus/common v0.67.5 // indirect github.com/prometheus/procfs v0.20.1 // indirect - github.com/rs/zerolog v1.34.0 + github.com/rs/zerolog v1.35.0 github.com/smira/go-statsd v1.3.4 github.com/stretchr/objx v0.5.2 // indirect github.com/stretchr/testify v1.11.1 @@ -29,7 +29,7 @@ require ( require ( github.com/beevik/ntp v1.5.0 github.com/ncruces/go-dns v1.3.2 - github.com/pelletier/go-toml/v2 v2.2.4 + github.com/pelletier/go-toml/v2 v2.3.0 github.com/pires/go-proxyproto v0.11.0 github.com/things-go/go-socks5 v0.1.0 github.com/txthinking/socks5 v0.0.0-20251011041537-5c31f201a10e diff --git a/go.sum b/go.sum index 51f27e3..8161acc 100644 --- a/go.sum +++ b/go.sum @@ -18,14 +18,12 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/d4l3k/messagediff v1.2.1 h1:ZcAIMYsUg0EAp9X+tt8/enBE/Q8Yd5kzPynLyKptt9U= github.com/d4l3k/messagediff v1.2.1/go.mod h1:Oozbb1TVXFac9FtSIxHBMnBCq2qeH/2KkEQxENCrlLo= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= @@ -40,11 +38,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= -github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mccutchen/go-httpbin v1.1.1 h1:aEws49HEJEyXHLDnshQVswfUlCVoS8g6h9YaDyaW7RE= @@ -59,11 +54,10 @@ github.com/panjf2000/ants/v2 v2.12.0 h1:u9JhESo83i/GkZnhfTNuFMMWcNt7mnV1bGJ6FT4w github.com/panjf2000/ants/v2 v2.12.0/go.mod h1:tSQuaNQ6r6NRhPt+IZVUevvDyFMTs+eS4ztZc52uJTY= github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= -github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= -github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= +github.com/pelletier/go-toml/v2 v2.3.0 h1:k59bC/lIZREW0/iVaQR8nDHxVq8OVlIzYCOJf421CaM= +github.com/pelletier/go-toml/v2 v2.3.0/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/pires/go-proxyproto v0.11.0 h1:gUQpS85X/VJMdUsYyEgyn59uLJvGqPhJV5YvG68wXH4= github.com/pires/go-proxyproto v0.11.0/go.mod h1:ZKAAyp3cgy5Y5Mo4n9AlScrkCZwUy0g3Jf+slqQVcuU= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= @@ -76,9 +70,8 @@ github.com/prometheus/procfs v0.20.1 h1:XwbrGOIplXW/AU3YhIhLODXMJYyC1isLFfYCsTEy github.com/prometheus/procfs v0.20.1/go.mod h1:o9EMBZGRyvDrSPH1RqdxhojkuXstoe4UlK79eF5TGGo= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= -github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= -github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY= -github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ= +github.com/rs/zerolog v1.35.0 h1:VD0ykx7HMiMJytqINBsKcbLS+BJ4WYjz+05us+LRTdI= +github.com/rs/zerolog v1.35.0/go.mod h1:EjML9kdfa/RMA7h/6z6pYmq1ykOuA8/mjWaEvGI+jcw= github.com/smira/go-statsd v1.3.4 h1:kBYWcLSGT+qC6JVbvfz48kX7mQys32fjDOPrfmsSx2c= github.com/smira/go-statsd v1.3.4/go.mod h1:RjdsESPgDODtg1VpVVf9MJrEW2Hw0wtRNbmB1CAhu6A= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -133,10 +126,8 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo= golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= From 0840c7e3e5ad200b4a6379953d8a2c8aee09535d Mon Sep 17 00:00:00 2001 From: 9seconds Date: Sun, 29 Mar 2026 23:31:41 +0200 Subject: [PATCH 22/22] Update PGO --- default.pgo | Bin 39105 -> 42756 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/default.pgo b/default.pgo index d7bf481caf3a57734ffbe8cfda436bb0dc0f04d7..b4fa92aff0f810f5449a3c4edb13160c749d8c19 100644 GIT binary patch literal 42756 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

  • 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 literal 39105 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