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
+36 -6
View File
@@ -1,4 +1,4 @@
// Package tgquick performs a minimal MTProto step (req_pq → resPQ) through an established MTProxy tunnel,
// Package tgquick performs a minimal MTProto step (req_pq → ответ DC) through an established MTProxy tunnel,
// matching Telethon's RandomizedIntermediate + MTProxyIO framing.
package tgquick
@@ -24,9 +24,12 @@ const (
maxRI = 1 << 20
)
// ErrNoResPQ means no valid resPQ was received from Telegram through the tunnel.
// ErrNoResPQ means strict deep check did not see resPQ#05162463 from DC.
var ErrNoResPQ = errors.New("no resPQ from telegram through proxy (tunnel may be broken)")
// ErrNoDCReply means relaxed deep check got no parsable unencrypted MTProto reply from DC before deadline.
var ErrNoDCReply = errors.New("no reply from telegram DC through proxy (timeout)")
// ErrPeerClosed is returned when the remote side closes TCP during the MTProto probe.
var ErrPeerClosed = errors.New("connection closed by peer during mtproto probe")
@@ -109,6 +112,22 @@ func isResPQ(mtInner []byte) bool {
return binary.LittleEndian.Uint32(body[0:4]) == tlResPQ
}
// isUnencryptedDCEnvelope reports a valid MTProto unencrypted container (auth_key_id 0) with non-empty body.
// After req_pq the DC normally sends resPQ, but any well-formed unencrypted reply proves the tunnel carried DC traffic.
func isUnencryptedDCEnvelope(mtInner []byte) bool {
if len(mtInner) < 24 {
return false
}
if binary.LittleEndian.Uint64(mtInner[0:8]) != 0 {
return false
}
ml := int(binary.LittleEndian.Uint32(mtInner[16:20]))
if ml < 4 || ml > maxRI || 20+ml > len(mtInner) {
return false
}
return true
}
// DrainPostInitEE reads inbound fake-TLS records after MTProxy init and before req_pq (Telethon TcpMTProxy waits for data ~2s).
// Consumes 0x17 payloads with dec so the CTR stream stays aligned with the server; discards MTProto frames until idle.
func DrainPostInitEE(ctx context.Context, br *bufio.Reader, conn net.Conn, dec cipher.Stream, maxWait time.Duration) error {
@@ -172,9 +191,10 @@ func DrainPostInitEE(ctx context.Context, br *bufio.Reader, conn net.Conn, dec c
return nil
}
// VerifyResPQ sends req_pq and waits for resPQ on the MTProxy byte stream (decrypt with dec, encrypt with enc).
// VerifyResPQ sends req_pq and waits for a DC reply on the MTProxy byte stream (decrypt with dec, encrypt with enc).
// If strictResPQ is true, the first matching RI frame must be resPQ; otherwise any valid unencrypted MTProto message is enough.
// For ee (fake-TLS), pass the same bufio.Reader used after init (and DrainPostInitEE); for dd pass tlsBR=nil.
func VerifyResPQ(ctx context.Context, conn net.Conn, enc, dec cipher.Stream, tlsBR *bufio.Reader) error {
func VerifyResPQ(ctx context.Context, conn net.Conn, enc, dec cipher.Stream, tlsBR *bufio.Reader, strictResPQ bool) error {
plain, err := buildReqPQ()
if err != nil {
return fmt.Errorf("build req_pq: %w", err)
@@ -263,14 +283,24 @@ func VerifyResPQ(ctx context.Context, conn net.Conn, enc, dec cipher.Stream, tls
if !ok {
break
}
if isResPQ(frame) {
if strictResPQ {
if isResPQ(frame) {
_ = conn.SetReadDeadline(time.Time{})
return nil
}
continue
}
if isUnencryptedDCEnvelope(frame) {
_ = conn.SetReadDeadline(time.Time{})
return nil
}
}
}
_ = conn.SetReadDeadline(time.Time{})
return ErrNoResPQ
if strictResPQ {
return ErrNoResPQ
}
return ErrNoDCReply
}
// readNextTLS17Payload reads TLS 1.2-style records from the wire (plaintext record headers).