Update MTProxy checker documentation and logic to clarify probing modes and improve error handling. Modify README and Docker documentation to reflect changes in the -probe flag behavior, including detailed descriptions of exit codes and timeout handling. Refactor connection handling to enhance robustness and align with Telethon's approach.
Publish mtproxy_checker Docker image / test (push) Successful in 7s
Publish mtproxy_checker Docker image / build-and-push (push) Successful in 52s

This commit is contained in:
Denozordec
2026-04-11 14:16:11 +07:00
parent 935401b1f8
commit a846ef5373
9 changed files with 45 additions and 31 deletions
+18 -16
View File
@@ -18,9 +18,6 @@ import (
// ErrProxyClosed indicates the peer closed the TCP connection during the check (Telethon #1134 style).
var ErrProxyClosed = errors.New("mtproxy closed connection after initial payload")
// ErrNoDataAfterHeader is returned in ProbeFast when the proxy sends nothing after the init payload within the wait window.
var ErrNoDataAfterHeader = errors.New("no data from proxy after mtproxy header (timeout)")
// Check runs Fake-TLS/dd handshake and MTProxy init; further steps depend on opts.Probe (see Options). opts nil => ProbeFast.
func Check(ctx context.Context, host string, port int, parsed *secret.Parsed, dcID int16, opts *Options) error {
conn, err := dialTCP(ctx, host, port)
@@ -54,6 +51,7 @@ func checkEE(ctx context.Context, conn net.Conn, p *secret.Parsed, dcID int16, o
if _, err := conn.Write(ch.Record); err != nil {
return fmt.Errorf("write client hello: %w", err)
}
// ServerHello читаем с сырого conn (ровно длина записи), как в tdesktop Read — без readahead.
resp, err := faketls.ReadServerHello(conn)
if err != nil {
return fmt.Errorf("read server hello: %w", err)
@@ -69,11 +67,12 @@ func checkEE(ctx context.Context, conn net.Conn, p *secret.Parsed, dcID int16, o
if err := faketls.WriteTLSApplicationData(conn, hdr); err != nil {
return fmt.Errorf("write mtproxy header: %w", err)
}
if o.Probe == ProbeFast {
return waitPostPayload(conn)
}
// После init один bufio на чтение TLS 0x17 (Telethon/telemt: не смешивать conn.Read и буфер).
br := bufio.NewReader(conn)
if err := tgquick.DrainPostInitEE(ctx, br, conn, dec, 2*time.Second); err != nil {
if o.Probe == ProbeFast {
return waitPostPayload(br, conn)
}
if err := tgquick.DrainPostInitEE(ctx, br, conn, dec, 5*time.Second); err != nil {
return err
}
if err := tgquick.VerifyResPQ(ctx, conn, enc, dec, br); err != nil {
@@ -94,7 +93,7 @@ func checkDD(ctx context.Context, conn net.Conn, p *secret.Parsed, dcID int16, o
return fmt.Errorf("write mtproxy header: %w", err)
}
if o.Probe == ProbeFast {
return waitPostPayload(conn)
return waitPostPayload(conn, conn)
}
if err := tgquick.VerifyResPQ(ctx, conn, enc, dec, nil); err != nil {
if errors.Is(err, tgquick.ErrPeerClosed) {
@@ -105,29 +104,32 @@ func checkDD(ctx context.Context, conn net.Conn, p *secret.Parsed, dcID int16, o
return nil
}
func waitPostPayload(conn net.Conn) error {
// waitPostPayload после init повторяет логику Telethon TcpMTProxy._connect (#1134):
// ждём появления данных или таймаут, затем считаем успехом только если соединение не закрыто сразу после payload.
// Наличие входящих байтов не обязательно — часть прокси молчит до первого MTProto от клиента.
func waitPostPayload(r io.Reader, c net.Conn) error {
deadline := time.Now().Add(2 * time.Second)
buf := make([]byte, 4096)
for time.Now().Before(deadline) {
_ = conn.SetReadDeadline(time.Now().Add(200 * time.Millisecond))
n, err := conn.Read(buf)
_ = c.SetReadDeadline(time.Now().Add(200 * time.Millisecond))
n, err := r.Read(buf)
if n > 0 {
_ = conn.SetReadDeadline(time.Time{})
_ = c.SetReadDeadline(time.Time{})
return nil
}
if err != nil {
if errors.Is(err, io.EOF) {
_ = conn.SetReadDeadline(time.Time{})
_ = c.SetReadDeadline(time.Time{})
return ErrProxyClosed
}
var ne net.Error
if errors.As(err, &ne) && ne.Timeout() {
continue
}
_ = conn.SetReadDeadline(time.Time{})
_ = c.SetReadDeadline(time.Time{})
return err
}
}
_ = conn.SetReadDeadline(time.Time{})
return ErrNoDataAfterHeader
_ = c.SetReadDeadline(time.Time{})
return nil
}
+1 -1
View File
@@ -6,7 +6,7 @@ import "strings"
type ProbeMode int
const (
// ProbeFast: handshake + init, then any inbound data within a short window (legacy behavior; fast).
// ProbeFast: handshake + init, then short wait like Telethon TcpMTProxy (#1134): OK if proxy does not close immediately.
ProbeFast ProbeMode = iota
// ProbeDeep: MTProto req_pq and expect resPQ from Telegram DC through the tunnel (stricter; slower).
ProbeDeep