init
Publish mtproxy_checker Docker image / test (push) Successful in 19s
Publish mtproxy_checker Docker image / build-and-push (push) Successful in 48s

This commit is contained in:
Denozordec
2026-04-11 00:38:22 +07:00
commit 4905c06b9b
26 changed files with 1887 additions and 0 deletions
+105
View File
@@ -0,0 +1,105 @@
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")
// 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 nil
}
+19
View File
@@ -0,0 +1,19 @@
package checker
import (
"context"
"fmt"
"net"
"strconv"
)
// dialTCP подключается к host:port так же, как типичное приложение: один вызов Dial к имени хоста.
// Раньше использовался обход AAAA/A с приоритетом IPv4 — это могло выбирать другой бэкенд, чем у ОС/клиента Telegram.
func dialTCP(ctx context.Context, host string, port int) (net.Conn, error) {
var d net.Dialer
c, err := d.DialContext(ctx, "tcp", net.JoinHostPort(host, strconv.Itoa(port)))
if err != nil {
return nil, fmt.Errorf("tcp dial: %w", err)
}
return c, nil
}