Enhance MTProxy checker by introducing a new -probe mode deep-strict for stricter response validation. Update README and Docker documentation to clarify probing modes, exit codes, and the new MTPROXY_TDLIB_HELPER and MTPROXY_TDLIB_TIMEOUT environment variables for external helper integration. Refactor connection handling and error reporting to improve robustness and clarity in response verification.
Publish mtproxy_checker Docker image / test (push) Successful in 7s
Publish mtproxy_checker Docker image / build-and-push (push) Successful in 53s

This commit is contained in:
Denozordec
2026-04-11 16:12:45 +07:00
parent 24a7315a16
commit 388784eb5e
17 changed files with 902 additions and 24 deletions
+53 -2
View File
@@ -7,6 +7,7 @@ import (
"fmt"
"io"
"net"
"syscall"
"time"
"mtproxy_checker/internal/faketls"
@@ -75,7 +76,8 @@ func checkEE(ctx context.Context, conn net.Conn, p *secret.Parsed, dcID int16, o
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 {
strict := o.Probe == ProbeDeepStrict
if err := tgquick.VerifyResPQ(ctx, conn, enc, dec, br, strict); err != nil {
if errors.Is(err, tgquick.ErrPeerClosed) {
return fmt.Errorf("%w", ErrProxyClosed)
}
@@ -95,7 +97,8 @@ func checkDD(ctx context.Context, conn net.Conn, p *secret.Parsed, dcID int16, o
if o.Probe == ProbeFast {
return waitPostPayload(conn, conn)
}
if err := tgquick.VerifyResPQ(ctx, conn, enc, dec, nil); err != nil {
strict := o.Probe == ProbeDeepStrict
if err := tgquick.VerifyResPQ(ctx, conn, enc, dec, nil, strict); err != nil {
if errors.Is(err, tgquick.ErrPeerClosed) {
return fmt.Errorf("%w", ErrProxyClosed)
}
@@ -104,9 +107,26 @@ func checkDD(ctx context.Context, conn net.Conn, p *secret.Parsed, dcID int16, o
return nil
}
// isPeerClosedReadErr reports RST/half-close style errors from Read (not timeouts).
func isPeerClosedReadErr(err error) bool {
if err == nil {
return false
}
var ne net.Error
if errors.As(err, &ne) && ne.Timeout() {
return false
}
return errors.Is(err, syscall.ECONNRESET) ||
errors.Is(err, syscall.ECONNABORTED) ||
errors.Is(err, syscall.EPIPE) ||
errors.Is(err, syscall.ENOTCONN)
}
// waitPostPayload после init повторяет логику Telethon TcpMTProxy._connect (#1134):
// ждём появления данных или таймаут, затем считаем успехом только если соединение не закрыто сразу после payload.
// Наличие входящих байтов не обязательно — часть прокси молчит до первого MTProto от клиента.
// После окна idle дополнительно читаем короткими интервалами: часть прокси шлёт FIN/RST сразу после init,
// но не в первых 200ms цикла — без этого fast давал бы ложный OK.
func waitPostPayload(r io.Reader, c net.Conn) error {
deadline := time.Now().Add(2 * time.Second)
buf := make([]byte, 4096)
@@ -122,6 +142,37 @@ func waitPostPayload(r io.Reader, c net.Conn) error {
_ = c.SetReadDeadline(time.Time{})
return ErrProxyClosed
}
if isPeerClosedReadErr(err) {
_ = c.SetReadDeadline(time.Time{})
return ErrProxyClosed
}
var ne net.Error
if errors.As(err, &ne) && ne.Timeout() {
continue
}
_ = c.SetReadDeadline(time.Time{})
return err
}
}
// Отложенное закрытие со стороны прокси (после того как «молчали» в основном окне).
const probeEach = 400 * time.Millisecond
const probeRounds = 4
for probes := 0; probes < probeRounds; probes++ {
_ = c.SetReadDeadline(time.Now().Add(probeEach))
n, err := r.Read(buf)
if n > 0 {
_ = c.SetReadDeadline(time.Time{})
return nil
}
if err != nil {
if errors.Is(err, io.EOF) {
_ = c.SetReadDeadline(time.Time{})
return ErrProxyClosed
}
if isPeerClosedReadErr(err) {
_ = c.SetReadDeadline(time.Time{})
return ErrProxyClosed
}
var ne net.Error
if errors.As(err, &ne) && ne.Timeout() {
continue
+6 -2
View File
@@ -8,8 +8,10 @@ type ProbeMode int
const (
// 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: MTProto req_pq then any valid unencrypted MTProto reply from DC (tunnel carries DC traffic; not only resPQ).
ProbeDeep
// ProbeDeepStrict: req_pq and response must be resPQ#05162463 (legacy strict check).
ProbeDeepStrict
)
// Options configures Check. Nil or zero value uses ProbeFast.
@@ -17,9 +19,11 @@ type Options struct {
Probe ProbeMode
}
// ParseProbe maps env/flag strings: "", "fast" -> ProbeFast; "deep" -> ProbeDeep.
// ParseProbe maps env/flag strings: "", "fast" -> ProbeFast; "deep" -> ProbeDeep; "deep-strict" -> ProbeDeepStrict.
func ParseProbe(s string) ProbeMode {
switch strings.ToLower(strings.TrimSpace(s)) {
case "deep-strict", "strict-deep", "respq-strict":
return ProbeDeepStrict
case "deep", "respq", "dc":
return ProbeDeep
default: