Ver código fonte

doctor: deepen DC verification with MTProto handshake probe

Closes #494.

After a successful TCP connect, run an unauthenticated req_pq_multi ->
resPQ exchange via mtglib/dcprobe. This rejects generic listeners that
happen to bind 443 but cannot speak MTProto.

Output now shows "(rpc <rtt>)" on success; on failure the wrapped error
distinguishes "tcp connect to ...: ..." from "rpc handshake to ...: ...".
The probe runs by default — an opt-in flag would defeat the purpose,
since the existing TCP-only check is what motivated the issue.
pull/496/head
Alexey Dolotov 1 semana atrás
pai
commit
408e2c7150
1 arquivos alterados com 26 adições e 16 exclusões
  1. 26
    16
      internal/cli/doctor.go

+ 26
- 16
internal/cli/doctor.go Ver arquivo

18
 	"github.com/9seconds/mtg/v2/internal/config"
18
 	"github.com/9seconds/mtg/v2/internal/config"
19
 	"github.com/9seconds/mtg/v2/internal/utils"
19
 	"github.com/9seconds/mtg/v2/internal/utils"
20
 	"github.com/9seconds/mtg/v2/mtglib"
20
 	"github.com/9seconds/mtg/v2/mtglib"
21
+	"github.com/9seconds/mtg/v2/mtglib/dcprobe"
21
 	"github.com/9seconds/mtg/v2/network/v2"
22
 	"github.com/9seconds/mtg/v2/network/v2"
22
 	"github.com/beevik/ntp"
23
 	"github.com/beevik/ntp"
23
 )
24
 )
46
 	)
47
 	)
47
 
48
 
48
 	tplODCConnect = template.Must(
49
 	tplODCConnect = template.Must(
49
-		template.New("").Parse("  ✅ DC {{ .dc }}\n"),
50
+		template.New("").Parse("  ✅ DC {{ .dc }} (rpc {{ .rtt }})\n"),
50
 	)
51
 	)
51
 	tplEDCConnect = template.Must(
52
 	tplEDCConnect = template.Must(
52
 		template.New("").Parse("  ❌ DC {{ .dc }}: {{ .error }}\n"),
53
 		template.New("").Parse("  ❌ DC {{ .dc }}: {{ .error }}\n"),
237
 	dcs := slices.Collect(maps.Keys(essentials.TelegramCoreAddresses))
238
 	dcs := slices.Collect(maps.Keys(essentials.TelegramCoreAddresses))
238
 	slices.Sort(dcs)
239
 	slices.Sort(dcs)
239
 
240
 
240
-	errs := make([]error, len(dcs))
241
+	type dcResult struct {
242
+		rtt time.Duration
243
+		err error
244
+	}
245
+	results := make([]dcResult, len(dcs))
241
 
246
 
242
 	var wg sync.WaitGroup
247
 	var wg sync.WaitGroup
243
 	for i, dc := range dcs {
248
 	for i, dc := range dcs {
244
 		wg.Go(func() {
249
 		wg.Go(func() {
245
 			defer func() {
250
 			defer func() {
246
 				if r := recover(); r != nil {
251
 				if r := recover(); r != nil {
247
-					errs[i] = fmt.Errorf("panic: %v", r)
252
+					results[i].err = fmt.Errorf("panic: %v", r)
248
 				}
253
 				}
249
 			}()
254
 			}()
250
-			errs[i] = d.checkNetworkAddresses(ntw, essentials.TelegramCoreAddresses[dc])
255
+			results[i].rtt, results[i].err = d.checkNetworkAddresses(ntw, dc, essentials.TelegramCoreAddresses[dc])
251
 		})
256
 		})
252
 	}
257
 	}
253
 	wg.Wait()
258
 	wg.Wait()
255
 	ok := true
260
 	ok := true
256
 
261
 
257
 	for i, dc := range dcs {
262
 	for i, dc := range dcs {
258
-		if errs[i] == nil {
263
+		if results[i].err == nil {
259
 			tplODCConnect.Execute(os.Stdout, map[string]any{ //nolint: errcheck
264
 			tplODCConnect.Execute(os.Stdout, map[string]any{ //nolint: errcheck
260
-				"dc": dc,
265
+				"dc":  dc,
266
+				"rtt": results[i].rtt.Round(time.Microsecond),
261
 			})
267
 			})
262
 		} else {
268
 		} else {
263
 			tplEDCConnect.Execute(os.Stdout, map[string]any{ //nolint: errcheck
269
 			tplEDCConnect.Execute(os.Stdout, map[string]any{ //nolint: errcheck
264
 				"dc":    dc,
270
 				"dc":    dc,
265
-				"error": errs[i],
271
+				"error": results[i].err,
266
 			})
272
 			})
267
 			ok = false
273
 			ok = false
268
 		}
274
 		}
271
 	return ok
277
 	return ok
272
 }
278
 }
273
 
279
 
274
-func (d *Doctor) checkNetworkAddresses(ntw mtglib.Network, addresses []string) error {
280
+func (d *Doctor) checkNetworkAddresses(ntw mtglib.Network, dc int, addresses []string) (time.Duration, error) {
275
 	checkAddresses := []string{}
281
 	checkAddresses := []string{}
276
 
282
 
277
 	switch d.conf.PreferIP.Get("prefer-ip4") {
283
 	switch d.conf.PreferIP.Get("prefer-ip4") {
302
 	}
308
 	}
303
 
309
 
304
 	if len(checkAddresses) == 0 {
310
 	if len(checkAddresses) == 0 {
305
-		return fmt.Errorf("no suitable addresses after IP version filtering")
311
+		return 0, fmt.Errorf("no suitable addresses after IP version filtering")
306
 	}
312
 	}
307
 
313
 
308
 	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
314
 	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
309
 	defer cancel()
315
 	defer cancel()
310
 
316
 
311
-	var (
312
-		conn net.Conn
313
-		err  error
314
-	)
317
+	var lastErr error
315
 
318
 
316
 	for _, addr := range checkAddresses {
319
 	for _, addr := range checkAddresses {
317
-		conn, err = ntw.DialContext(ctx, "tcp", addr)
320
+		conn, err := ntw.DialContext(ctx, "tcp", addr)
318
 		if err != nil {
321
 		if err != nil {
322
+			lastErr = fmt.Errorf("tcp connect to %s: %w", addr, err)
319
 			continue
323
 			continue
320
 		}
324
 		}
321
 
325
 
326
+		rtt, err := dcprobe.Probe(ctx, conn, dc)
322
 		conn.Close() //nolint: errcheck
327
 		conn.Close() //nolint: errcheck
323
 
328
 
324
-		return nil
329
+		if err != nil {
330
+			lastErr = fmt.Errorf("rpc handshake to %s: %w", addr, err)
331
+			continue
332
+		}
333
+
334
+		return rtt, nil
325
 	}
335
 	}
326
 
336
 
327
-	return err
337
+	return 0, lastErr
328
 }
338
 }
329
 
339
 
330
 func (d *Doctor) checkFrontingDomain(ntw mtglib.Network) bool {
340
 func (d *Doctor) checkFrontingDomain(ntw mtglib.Network) bool {

Carregando…
Cancelar
Salvar