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.
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user