Files
mtproxy_checker/internal/checker/checker.go
T

134 lines
4.0 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 (
"bufio"
"context"
"errors"
"fmt"
"io"
"net"
"time"
"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")
// ErrNoDataAfterHeader is returned in ProbeFast when the proxy sends nothing after the init payload within the wait window.
var ErrNoDataAfterHeader = errors.New("no data from proxy after mtproxy header (timeout)")
// Check runs Fake-TLS/dd handshake and MTProxy init; further steps depend on opts.Probe (see Options). opts nil => ProbeFast.
func Check(ctx context.Context, host string, port int, parsed *secret.Parsed, dcID int16, opts *Options) error {
conn, err := dialTCP(ctx, host, port)
if err != nil {
return fmt.Errorf("tcp dial: %w", err)
}
defer conn.Close()
o := effectiveOpts(opts)
switch parsed.Kind {
case secret.KindEE:
return checkEE(ctx, conn, parsed, dcID, o)
case secret.KindDD:
return checkDD(ctx, conn, parsed, dcID, o)
default:
return fmt.Errorf("unknown secret kind")
}
}
func checkEE(ctx context.Context, conn net.Conn, p *secret.Parsed, dcID int16, o *Options) 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 o.Probe == ProbeFast {
return waitPostPayload(conn)
}
br := bufio.NewReader(conn)
if err := tgquick.DrainPostInitEE(ctx, br, conn, dec, 2*time.Second); err != nil {
return err
}
if err := tgquick.VerifyResPQ(ctx, conn, enc, dec, br); 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, o *Options) 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 o.Probe == ProbeFast {
return waitPostPayload(conn)
}
if err := tgquick.VerifyResPQ(ctx, conn, enc, dec, nil); err != nil {
if errors.Is(err, tgquick.ErrPeerClosed) {
return fmt.Errorf("%w", ErrProxyClosed)
}
return err
}
return nil
}
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
}