Explorar el Código

Add dynamic cert noise calibration for FakeTLS handshake

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
tags/v2.2.5^2^2
Alexey Dolotov hace 1 mes
padre
commit
80213ad35d

+ 4
- 0
internal/cli/run_proxy.go Ver fichero

@@ -267,6 +267,10 @@ func runProxy(conf *config.Config, version string) error { //nolint: funlen
267 267
 		DoppelGangerPerRaid: conf.Defense.Doppelganger.Repeats.Get(mtglib.DoppelGangerPerRaid),
268 268
 		DoppelGangerEach:    conf.Defense.Doppelganger.UpdateEach.Get(mtglib.DoppelGangerEach),
269 269
 		DoppelGangerDRS:     conf.Defense.Doppelganger.DRS.Get(false),
270
+
271
+		NoiseProbeCount: conf.Defense.Doppelganger.NoiseProbeCount.Get(0),
272
+		NoiseCacheTTL:   conf.Defense.Doppelganger.NoiseCacheTTL.Get(0),
273
+		NoiseCachePath:  conf.Defense.Doppelganger.NoiseCachePath,
270 274
 	}
271 275
 
272 276
 	proxy, err := mtglib.NewProxy(opts)

+ 7
- 4
internal/config/config.go Ver fichero

@@ -50,10 +50,13 @@ type Config struct {
50 50
 		Blocklist    ListConfig `json:"blocklist"`
51 51
 		Allowlist    ListConfig `json:"allowlist"`
52 52
 		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"`
53
+			URLs            []TypeHttpsURL  `json:"urls"`
54
+			Repeats         TypeConcurrency `json:"repeats_per_raid"`
55
+			UpdateEach      TypeDuration    `json:"raid_each"`
56
+			DRS             TypeBool        `json:"drs"`
57
+			NoiseProbeCount TypeConcurrency `json:"noise_probe_count"`
58
+			NoiseCacheTTL   TypeDuration    `json:"noise_cache_ttl"`
59
+			NoiseCachePath  string          `json:"noise_cache_path"`
57 60
 		} `json:"doppelganger"`
58 61
 	} `json:"defense"`
59 62
 	Network struct {

+ 7
- 4
internal/config/parse.go Ver fichero

@@ -45,10 +45,13 @@ type tomlConfig struct {
45 45
 			UpdateEach          string   `toml:"update-each" json:"updateEach,omitempty"`
46 46
 		} `toml:"allowlist" json:"allowlist,omitempty"`
47 47
 		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"`
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"`
52
+			NoiseProbeCount uint     `toml:"noise-probe-count" json:"noise_probe_count,omitempty"`
53
+			NoiseCacheTTL   string   `toml:"noise-cache-ttl" json:"noise_cache_ttl,omitempty"`
54
+			NoiseCachePath  string   `toml:"noise-cache-path" json:"noise_cache_path,omitempty"`
52 55
 		} `toml:"doppelganger" json:"doppelganger,omitempty"`
53 56
 	} `toml:"defense" json:"defense,omitempty"`
54 57
 	Network struct {

+ 261
- 0
mtglib/internal/tls/fake/cert_probe.go Ver fichero

@@ -0,0 +1,261 @@
1
+package fake
2
+
3
+import (
4
+	"crypto/tls"
5
+	"encoding/binary"
6
+	"encoding/json"
7
+	"fmt"
8
+	"net"
9
+	"os"
10
+	"sync"
11
+	"time"
12
+)
13
+
14
+const (
15
+	probeDialTimeout      = 10 * time.Second
16
+	probeHandshakeTimeout = 10 * time.Second
17
+	defaultProbeCount     = 15
18
+	defaultCacheTTL       = 24 * time.Hour
19
+
20
+	tlsTypeChangeCipherSpec = 0x14
21
+	tlsTypeApplicationData  = 0x17
22
+)
23
+
24
+// CertProbeResult holds the measured encrypted handshake size.
25
+type CertProbeResult struct {
26
+	Mean   int `json:"mean"`
27
+	Jitter int `json:"jitter"`
28
+}
29
+
30
+// CertProbeCache is the on-disk format for cached probe results.
31
+type CertProbeCache struct {
32
+	Hostname string    `json:"hostname"`
33
+	Port     int       `json:"port"`
34
+	Mean     int       `json:"mean"`
35
+	Jitter   int       `json:"jitter"`
36
+	ProbedAt time.Time `json:"probed_at"`
37
+}
38
+
39
+// LoadCachedProbe reads a cached probe result from path. Returns the result
40
+// and true if the cache exists, matches hostname:port, and is younger than ttl.
41
+// Otherwise returns zero value and false.
42
+func LoadCachedProbe(path, hostname string, port int, ttl time.Duration) (CertProbeResult, bool) {
43
+	if ttl <= 0 {
44
+		ttl = defaultCacheTTL
45
+	}
46
+
47
+	data, err := os.ReadFile(path)
48
+	if err != nil {
49
+		return CertProbeResult{}, false
50
+	}
51
+
52
+	var cache CertProbeCache
53
+	if err := json.Unmarshal(data, &cache); err != nil {
54
+		return CertProbeResult{}, false
55
+	}
56
+
57
+	if cache.Hostname != hostname || cache.Port != port {
58
+		return CertProbeResult{}, false
59
+	}
60
+
61
+	if time.Since(cache.ProbedAt) > ttl {
62
+		return CertProbeResult{}, false
63
+	}
64
+
65
+	if cache.Mean <= 0 {
66
+		return CertProbeResult{}, false
67
+	}
68
+
69
+	return CertProbeResult{Mean: cache.Mean, Jitter: cache.Jitter}, true
70
+}
71
+
72
+// SaveCachedProbe writes a probe result to path as JSON.
73
+func SaveCachedProbe(path, hostname string, port int, result CertProbeResult) error {
74
+	cache := CertProbeCache{
75
+		Hostname: hostname,
76
+		Port:     port,
77
+		Mean:     result.Mean,
78
+		Jitter:   result.Jitter,
79
+		ProbedAt: time.Now(),
80
+	}
81
+
82
+	data, err := json.MarshalIndent(cache, "", "  ")
83
+	if err != nil {
84
+		return err
85
+	}
86
+
87
+	return os.WriteFile(path, data, 0o644) //nolint: gosec
88
+}
89
+
90
+// ProbeCertSize connects to hostname:port via TLS multiple times and measures
91
+// the total ApplicationData payload bytes sent by the server during the
92
+// handshake (between ChangeCipherSpec and the first application-level data).
93
+// This corresponds to EncryptedExtensions + Certificate + CertificateVerify +
94
+// Finished in TLS 1.3, which is what the FakeTLS noise must mimic.
95
+func ProbeCertSize(hostname string, port int, count int) (CertProbeResult, error) {
96
+	if count <= 0 {
97
+		count = defaultProbeCount
98
+	}
99
+
100
+	addr := net.JoinHostPort(hostname, fmt.Sprintf("%d", port))
101
+	sizes := make([]int, 0, count)
102
+
103
+	for i := 0; i < count; i++ {
104
+		size, err := probeSingle(addr, hostname)
105
+		if err != nil {
106
+			if len(sizes) > 0 {
107
+				break // use what we have
108
+			}
109
+
110
+			return CertProbeResult{}, fmt.Errorf("probe %d failed: %w", i, err)
111
+		}
112
+
113
+		sizes = append(sizes, size)
114
+	}
115
+
116
+	if len(sizes) == 0 {
117
+		return CertProbeResult{}, fmt.Errorf("no successful probes")
118
+	}
119
+
120
+	// Calculate mean and jitter (max deviation from mean).
121
+	sum := 0
122
+	for _, s := range sizes {
123
+		sum += s
124
+	}
125
+
126
+	mean := sum / len(sizes)
127
+
128
+	maxDev := 0
129
+	for _, s := range sizes {
130
+		d := s - mean
131
+		if d < 0 {
132
+			d = -d
133
+		}
134
+
135
+		if d > maxDev {
136
+			maxDev = d
137
+		}
138
+	}
139
+
140
+	// Ensure minimum jitter of 100 bytes for variability.
141
+	if maxDev < 100 {
142
+		maxDev = 100
143
+	}
144
+
145
+	return CertProbeResult{Mean: mean, Jitter: maxDev}, nil
146
+}
147
+
148
+// probeSingle does one TLS handshake and measures ApplicationData bytes
149
+// received during the handshake.
150
+func probeSingle(addr, hostname string) (int, error) {
151
+	rawConn, err := net.DialTimeout("tcp", addr, probeDialTimeout)
152
+	if err != nil {
153
+		return 0, err
154
+	}
155
+	defer rawConn.Close() //nolint: errcheck
156
+
157
+	capture := &recordCapture{conn: rawConn}
158
+
159
+	tlsConn := tls.Client(capture, &tls.Config{
160
+		ServerName: hostname,
161
+		MinVersion: tls.VersionTLS12,
162
+	})
163
+	tlsConn.SetDeadline(time.Now().Add(probeHandshakeTimeout)) //nolint: errcheck
164
+
165
+	if err := tlsConn.Handshake(); err != nil {
166
+		return 0, err
167
+	}
168
+
169
+	tlsConn.Close() //nolint: errcheck
170
+
171
+	return capture.appDataBytes, nil
172
+}
173
+
174
+// recordCapture wraps a net.Conn and parses the raw TLS record stream to
175
+// measure ApplicationData payload sizes sent by the server during handshake.
176
+// It tracks record boundaries by maintaining a state machine over Read calls.
177
+type recordCapture struct {
178
+	conn         net.Conn
179
+	mu           sync.Mutex
180
+	appDataBytes int
181
+	seenCCS      bool
182
+	done         bool
183
+
184
+	// Record boundary tracking for the read side.
185
+	readRemaining int // bytes left in current record payload
186
+	readHeaderBuf [5]byte
187
+	readHeaderPos int
188
+}
189
+
190
+func (rc *recordCapture) Read(p []byte) (int, error) {
191
+	n, err := rc.conn.Read(p)
192
+	if n > 0 && !rc.done {
193
+		rc.mu.Lock()
194
+		rc.parseReadBytes(p[:n])
195
+		rc.mu.Unlock()
196
+	}
197
+
198
+	return n, err
199
+}
200
+
201
+func (rc *recordCapture) parseReadBytes(data []byte) {
202
+	for len(data) > 0 {
203
+		if rc.readRemaining > 0 {
204
+			// Consuming payload of current record.
205
+			consume := rc.readRemaining
206
+			if consume > len(data) {
207
+				consume = len(data)
208
+			}
209
+
210
+			rc.readRemaining -= consume
211
+			data = data[consume:]
212
+
213
+			continue
214
+		}
215
+
216
+		// Accumulate header bytes (5 bytes per record).
217
+		need := 5 - rc.readHeaderPos
218
+		if need > len(data) {
219
+			need = len(data)
220
+		}
221
+
222
+		copy(rc.readHeaderBuf[rc.readHeaderPos:], data[:need])
223
+		rc.readHeaderPos += need
224
+		data = data[need:]
225
+
226
+		if rc.readHeaderPos < 5 {
227
+			return // incomplete header
228
+		}
229
+
230
+		// Full header available.
231
+		recordType := rc.readHeaderBuf[0]
232
+		payloadLen := int(binary.BigEndian.Uint16(rc.readHeaderBuf[3:5]))
233
+		rc.readHeaderPos = 0
234
+		rc.readRemaining = payloadLen
235
+
236
+		if recordType == tlsTypeChangeCipherSpec {
237
+			rc.seenCCS = true
238
+		} else if recordType == tlsTypeApplicationData && rc.seenCCS {
239
+			rc.appDataBytes += payloadLen
240
+		}
241
+	}
242
+}
243
+
244
+func (rc *recordCapture) Write(p []byte) (int, error) {
245
+	// After client writes post-CCS data, server handshake records are done.
246
+	if rc.seenCCS && rc.appDataBytes > 0 {
247
+		rc.done = true
248
+	}
249
+
250
+	return rc.conn.Write(p)
251
+}
252
+
253
+func (rc *recordCapture) Close() error                       { return rc.conn.Close() }
254
+func (rc *recordCapture) LocalAddr() net.Addr                { return rc.conn.LocalAddr() }
255
+func (rc *recordCapture) RemoteAddr() net.Addr               { return rc.conn.RemoteAddr() }
256
+func (rc *recordCapture) SetDeadline(t time.Time) error      { return rc.conn.SetDeadline(t) }
257
+func (rc *recordCapture) SetReadDeadline(t time.Time) error  { return rc.conn.SetReadDeadline(t) }
258
+func (rc *recordCapture) SetWriteDeadline(t time.Time) error { return rc.conn.SetWriteDeadline(t) }
259
+
260
+// Ensure recordCapture implements net.Conn.
261
+var _ net.Conn = (*recordCapture)(nil)

+ 34
- 15
mtglib/internal/tls/fake/server_side.go Ver fichero

@@ -9,11 +9,18 @@ import (
9 9
 	"io"
10 10
 	rnd "math/rand/v2"
11 11
 
12
-	"github.com/9seconds/mtg/v2/mtglib/internal/doppel"
13 12
 	"github.com/9seconds/mtg/v2/mtglib/internal/tls"
14 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 24
 const (
18 25
 	TypeHandshakeServer = 0x02
19 26
 	ChangeCipherValue   = 0x01
@@ -33,13 +40,13 @@ var serverHelloSuffix = []byte{
33 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 44
 	buf := &bytes.Buffer{}
38 45
 	buf.Grow(tls.MaxRecordSize)
39 46
 
40 47
 	generateServerHello(buf, clientHello)
41 48
 	generateChangeCipherValue(buf)
42
-	generateNoise(buf)
49
+	generateNoise(buf, noise)
43 50
 
44 51
 	packet := buf.Bytes()
45 52
 	digest := hmac.New(sha256.New, secret)
@@ -125,19 +132,31 @@ func generateChangeCipherValue(buf *bytes.Buffer) {
125 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 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 Ver fichero

@@ -8,7 +8,6 @@ import (
8 8
 	"testing"
9 9
 
10 10
 	"github.com/9seconds/mtg/v2/mtglib"
11
-	"github.com/9seconds/mtg/v2/mtglib/internal/doppel"
12 11
 	"github.com/9seconds/mtg/v2/mtglib/internal/tls"
13 12
 	"github.com/9seconds/mtg/v2/mtglib/internal/tls/fake"
14 13
 	"github.com/stretchr/testify/suite"
@@ -39,7 +38,7 @@ func (suite *SendServerHelloTestSuite) SetupTest() {
39 38
 }
40 39
 
41 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 42
 	suite.NoError(err)
44 43
 
45 44
 	var rec bytes.Buffer
@@ -59,13 +58,13 @@ func (suite *SendServerHelloTestSuite) TestRecordStructure() {
59 58
 	recordType, length, err := tls.ReadRecord(suite.buf, &rec)
60 59
 	suite.NoError(err)
61 60
 	suite.Equal(byte(tls.TypeApplicationData), recordType)
62
-	suite.Greater(length, int64(doppel.TLSRecordSizeStart))
61
+	suite.Greater(length, int64(2500))
63 62
 
64 63
 	suite.Empty(suite.buf.Bytes())
65 64
 }
66 65
 
67 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 68
 	suite.NoError(err)
70 69
 
71 70
 	packet := make([]byte, suite.buf.Len())
@@ -83,7 +82,7 @@ func (suite *SendServerHelloTestSuite) TestHMAC() {
83 82
 }
84 83
 
85 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 86
 	suite.NoError(err)
88 87
 
89 88
 	packet := suite.buf.Bytes()
@@ -105,7 +104,7 @@ func (suite *SendServerHelloTestSuite) TestHandshakePayload() {
105 104
 }
106 105
 
107 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 108
 	suite.NoError(err)
110 109
 
111 110
 	// Skip first record
@@ -124,6 +123,33 @@ func (suite *SendServerHelloTestSuite) TestChangeCipherSpec() {
124 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 153
 func TestSendServerHello(t *testing.T) {
128 154
 	t.Parallel()
129 155
 	suite.Run(t, &SendServerHelloTestSuite{})

+ 42
- 1
mtglib/proxy.go Ver fichero

@@ -36,6 +36,7 @@ type Proxy struct {
36 36
 	doppelGanger                *doppel.Ganger
37 37
 	clientObfuscatror           obfuscation.Obfuscator
38 38
 
39
+	noiseParams     fake.NoiseParams
39 40
 	secret          Secret
40 41
 	network         Network
41 42
 	antiReplayCache AntiReplayCache
@@ -192,7 +193,7 @@ func (p *Proxy) doFakeTLSHandshake(ctx *streamContext) bool {
192 193
 		return false
193 194
 	}
194 195
 
195
-	if err := fake.SendServerHello(ctx.clientConn, p.secret.Key[:], clientHello); err != nil {
196
+	if err := fake.SendServerHello(ctx.clientConn, p.secret.Key[:], clientHello, p.noiseParams); err != nil {
196 197
 		p.logger.InfoError("cannot send welcome packet", err)
197 198
 		return false
198 199
 	}
@@ -323,9 +324,49 @@ func NewProxy(opts ProxyOpts) (*Proxy, error) {
323 324
 	logger := opts.getLogger("proxy")
324 325
 	updatersLogger := logger.Named("telegram-updaters")
325 326
 
327
+	// Probe the fronting domain's cert chain size for noise calibration.
328
+	probeHost := opts.Secret.Host
329
+	probePort := opts.getDomainFrontingPort()
330
+	noiseParams := fake.NoiseParams{}
331
+
332
+	probeCount := int(opts.NoiseProbeCount)
333
+	if probeCount <= 0 {
334
+		probeCount = 15
335
+	}
336
+
337
+	cacheTTL := opts.NoiseCacheTTL
338
+
339
+	// Try loading from cache first.
340
+	if opts.NoiseCachePath != "" {
341
+		if cached, ok := fake.LoadCachedProbe(opts.NoiseCachePath, probeHost, probePort, cacheTTL); ok {
342
+			noiseParams = fake.NoiseParams(cached)
343
+			logger.Info(fmt.Sprintf("cert probe: loaded from cache, host=%s mean=%d jitter=%d",
344
+				probeHost, cached.Mean, cached.Jitter))
345
+		}
346
+	}
347
+
348
+	// If no cached result, probe live.
349
+	if noiseParams.Mean == 0 {
350
+		probeResult, probeErr := fake.ProbeCertSize(probeHost, probePort, probeCount)
351
+		if probeErr != nil {
352
+			logger.WarningError("cert probe failed, using default noise size", probeErr)
353
+		} else {
354
+			noiseParams = fake.NoiseParams(probeResult)
355
+			logger.Info(fmt.Sprintf("cert probe: host=%s mean=%d jitter=%d",
356
+				probeHost, probeResult.Mean, probeResult.Jitter))
357
+
358
+			if opts.NoiseCachePath != "" {
359
+				if saveErr := fake.SaveCachedProbe(opts.NoiseCachePath, probeHost, probePort, probeResult); saveErr != nil {
360
+					logger.WarningError("failed to save cert probe cache", saveErr)
361
+				}
362
+			}
363
+		}
364
+	}
365
+
326 366
 	proxy := &Proxy{
327 367
 		ctx:                      ctx,
328 368
 		ctxCancel:                cancel,
369
+		noiseParams:              noiseParams,
329 370
 		secret:                   opts.Secret,
330 371
 		network:                  opts.Network,
331 372
 		antiReplayCache:          opts.AntiReplayCache,

+ 19
- 0
mtglib/proxy_opts.go Ver fichero

@@ -160,6 +160,25 @@ type ProxyOpts struct {
160 160
 
161 161
 	// DoppelGangerDRS defines if TLS Dynamic Record Sizing is active.
162 162
 	DoppelGangerDRS bool
163
+
164
+	// NoiseProbeCount is the number of TLS connections to make when probing
165
+	// the fronting domain's cert chain size for noise calibration.
166
+	// Default is 15.
167
+	//
168
+	// This is an optional setting.
169
+	NoiseProbeCount uint
170
+
171
+	// NoiseCacheTTL is how long a cached cert probe result is considered
172
+	// valid. Default is 24 hours.
173
+	//
174
+	// This is an optional setting.
175
+	NoiseCacheTTL time.Duration
176
+
177
+	// NoiseCachePath is the file path for caching cert probe results
178
+	// between restarts. If empty, no caching is performed.
179
+	//
180
+	// This is an optional setting.
181
+	NoiseCachePath string
163 182
 }
164 183
 
165 184
 func (p ProxyOpts) valid() error {

Loading…
Cancelar
Guardar