Files
mtproxy_checker/internal/checker/checker.go
T
2026-04-11 01:44:53 +07:00

90 lines
2.8 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"
"net"
"mtproxy_checker/internal/faketls"
"mtproxy_checker/internal/mtproxy"
"mtproxy_checker/internal/secret"
"mtproxy_checker/internal/tgquick"
)
// ErrProxyClosed indicates the peer closed the TCP connection during the check (Telethon #1134 style).
var ErrProxyClosed = errors.New("mtproxy closed connection after initial payload")
// Check runs Fake-TLS/dd handshake, MTProxy init, then a minimal MTProto req_pq and expects resPQ from Telegram DC (same idea as the mobile client path).
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(ctx, conn, parsed, dcID)
case secret.KindDD:
return checkDD(ctx, conn, parsed, dcID)
default:
return fmt.Errorf("unknown secret kind")
}
}
func checkEE(ctx context.Context, 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, enc, dec, 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)
}
if err := tgquick.VerifyResPQ(ctx, conn, enc, dec, true); err != nil {
if errors.Is(err, tgquick.ErrPeerClosed) {
return fmt.Errorf("%w", ErrProxyClosed)
}
return err
}
return nil
}
func checkDD(ctx context.Context, conn net.Conn, p *secret.Parsed, dcID int16) error {
hdr, enc, dec, 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)
}
if err := tgquick.VerifyResPQ(ctx, conn, enc, dec, false); err != nil {
if errors.Is(err, tgquick.ErrPeerClosed) {
return fmt.Errorf("%w", ErrProxyClosed)
}
return err
}
return nil
}