Files
mtproxy_checker/internal/checker/checker.go
T
Denozordec e3049028cd
Publish mtproxy_checker Docker image / test (push) Successful in 6s
Publish mtproxy_checker Docker image / build-and-push (push) Successful in 51s
Update README and Docker documentation to clarify exit codes and error handling. Introduce new error for no data received after MTProxy header in checker logic.
2026-04-11 01:35:37 +07:00

110 lines
3.3 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package checker
import (
"context"
"errors"
"fmt"
"io"
"net"
"time"
"mtproxy_checker/internal/faketls"
"mtproxy_checker/internal/mtproxy"
"mtproxy_checker/internal/secret"
)
// ErrProxyClosed indicates the MTProxy dropped the TCP connection right after the probe (Telethon #1134 style).
var ErrProxyClosed = errors.New("mtproxy closed connection after initial payload")
// ErrNoDataAfterHeader indicates the proxy accepted the probe but sent no application data back within the wait window.
// Treating silence as success matched some servers but often disagrees with Telegram client "unavailable".
var ErrNoDataAfterHeader = errors.New("no data from proxy after mtproxy header (timeout)")
// Check runs the handshake probe for the given parsed secret.
func Check(ctx context.Context, host string, port int, parsed *secret.Parsed, dcID int16) error {
conn, err := dialTCP(ctx, host, port)
if err != nil {
return fmt.Errorf("tcp dial: %w", err)
}
defer conn.Close()
switch parsed.Kind {
case secret.KindEE:
return checkEE(conn, parsed, dcID)
case secret.KindDD:
return checkDD(conn, parsed, dcID)
default:
return fmt.Errorf("unknown secret kind")
}
}
func checkEE(conn net.Conn, p *secret.Parsed, dcID int16) error {
// ee-секрет хранит домен как «сырой» хвост (часто 0xd0 + ASCII hostname). В TLS SNI нужен только hostname,
// как в официальном клиенте Telegram — иначе прокси сбрасывает соединение до ServerHello.
sni := faketls.SNIDomain(p.Domain)
if len(sni) == 0 {
sni = p.Domain
}
ch, err := faketls.BuildTdesktopClientHello(p.Key, sni)
if err != nil {
return fmt.Errorf("fake-tls client hello: %w", err)
}
if _, err := conn.Write(ch.Record); err != nil {
return fmt.Errorf("write client hello: %w", err)
}
resp, err := faketls.ReadServerHello(conn)
if err != nil {
return fmt.Errorf("read server hello: %w", err)
}
if err := faketls.VerifyServerHelloTdesktop(resp, p.Key, ch.RandomField); err != nil {
return fmt.Errorf("verify server hello: %w", err)
}
hdr, _, _, err := mtproxy.InitHeader(p.Key, dcID)
if err != nil {
return fmt.Errorf("mtproxy header: %w", err)
}
if err := faketls.WriteTLSApplicationData(conn, hdr); err != nil {
return fmt.Errorf("write mtproxy header: %w", err)
}
return waitPostPayload(conn)
}
func checkDD(conn net.Conn, p *secret.Parsed, dcID int16) error {
hdr, _, _, err := mtproxy.InitHeader(p.Key, dcID)
if err != nil {
return fmt.Errorf("mtproxy header: %w", err)
}
if _, err := conn.Write(hdr); err != nil {
return fmt.Errorf("write mtproxy header: %w", err)
}
return waitPostPayload(conn)
}
func waitPostPayload(conn 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)
if n > 0 {
_ = conn.SetReadDeadline(time.Time{})
return nil
}
if err != nil {
if errors.Is(err, io.EOF) {
_ = conn.SetReadDeadline(time.Time{})
return ErrProxyClosed
}
var ne net.Error
if errors.As(err, &ne) && ne.Timeout() {
continue
}
_ = conn.SetReadDeadline(time.Time{})
return err
}
}
_ = conn.SetReadDeadline(time.Time{})
return ErrNoDataAfterHeader
}