90 lines
2.8 KiB
Go
90 lines
2.8 KiB
Go
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
|
||
}
|