diff --git a/cmd/mtproxy_checkerd/main.go b/cmd/mtproxy_checkerd/main.go index 8bc0a44..9d5901f 100644 --- a/cmd/mtproxy_checkerd/main.go +++ b/cmd/mtproxy_checkerd/main.go @@ -58,7 +58,8 @@ func loadConfig() (*config, error) { } timeoutStr := strings.TrimSpace(os.Getenv("MTPROXY_CHECK_TIMEOUT")) if timeoutStr == "" { - timeoutStr = "15s" + // Fake-TLS + drain + req_pq/resPQ к DC часто >15s на медленных линиях и при многих прокси подряд. + timeoutStr = "45s" } checkTimeout, err := time.ParseDuration(timeoutStr) if err != nil || checkTimeout <= 0 { diff --git a/docker-compose.api.yaml b/docker-compose.api.yaml index c3cefac..051775c 100644 --- a/docker-compose.api.yaml +++ b/docker-compose.api.yaml @@ -12,7 +12,7 @@ services: MTPROXY_LIST_FILE: /data/proxies.txt MTPROXY_CHECK_INTERVAL: 5m MTPROXY_HTTP_ADDR: ":8080" - MTPROXY_CHECK_TIMEOUT: 15s + MTPROXY_CHECK_TIMEOUT: 45s MTPROXY_DC_ID: "2" # Опционально: только перечисленные IP/CIDR (иначе не задавайте переменную) # MTPROXY_ALLOWED_IPS: "172.17.0.0/16,127.0.0.1" diff --git a/docs/docker.ru.md b/docs/docker.ru.md index 56919e0..6cbd3d7 100644 --- a/docs/docker.ru.md +++ b/docs/docker.ru.md @@ -49,7 +49,7 @@ docker run -d --name mtproxy-api --restart unless-stopped -p 8080:8080 ` | `MTPROXY_LIST_FILE` | `/data/proxies.txt` | Путь к файлу: одна `tg://` ссылка на строку; пустые строки и строки с `#` в начале пропускаются | | `MTPROXY_CHECK_INTERVAL` | `5m` | Интервал между циклами (`time.ParseDuration`, например `5m`, `1h`) | | `MTPROXY_HTTP_ADDR` | `:8080` | Адрес прослушивания HTTP | -| `MTPROXY_CHECK_TIMEOUT` | `15s` | Таймаут одной проверки (аналог `-timeout` CLI) | +| `MTPROXY_CHECK_TIMEOUT` | `45s` (в демоне по умолчанию; CLI по-прежнему `15s` если не задано) | Таймаут **всей** одной проверки: TCP + Fake-TLS + MTProxy init + drain + `req_pq`/`resPQ` до DC; при медленной сети увеличьте | | `MTPROXY_DC_ID` | `2` | DC id (аналог `-dc-id` CLI) | | `MTPROXY_ALLOWED_IPS` | *(не задана)* | Если задана непустая строка — доступ к **всем** маршрутам только с перечисленных IP/CIDR; остальные получают **403** и JSON `{"error":"forbidden"}`. Формат: через запятую, пробелы допускаются: `192.168.1.10`, `10.0.0.0/8`, IPv6 и CIDR вида `2001:db8::/32`. Учитывается только **`RemoteAddr`** TCP-соединения; заголовок `X-Forwarded-For` **не** используется | diff --git a/internal/checkresult/checkresult.go b/internal/checkresult/checkresult.go index 90773dc..9435527 100644 --- a/internal/checkresult/checkresult.go +++ b/internal/checkresult/checkresult.go @@ -3,6 +3,7 @@ package checkresult import ( "context" "errors" + "net" "mtproxy_checker/internal/checker" ) @@ -18,5 +19,9 @@ func Classify(err error) (exitCode int, message string) { if errors.Is(err, context.DeadlineExceeded) { return 4, "timeout" } + var ne net.Error + if errors.As(err, &ne) && ne.Timeout() { + return 4, "timeout" + } return 1, err.Error() } diff --git a/internal/tgquick/probe.go b/internal/tgquick/probe.go index f31176b..873b21d 100644 --- a/internal/tgquick/probe.go +++ b/internal/tgquick/probe.go @@ -113,6 +113,9 @@ func isResPQ(mtInner []byte) bool { // Consumes 0x17 payloads with dec so the CTR stream stays aligned with the server; discards MTProto frames until idle. func DrainPostInitEE(ctx context.Context, br *bufio.Reader, conn net.Conn, dec cipher.Stream, maxWait time.Duration) error { end := time.Now().Add(maxWait) + if d, ok := ctx.Deadline(); ok && d.Before(end) { + end = d + } var acc []byte const maxDrain = 256 << 10 drained := 0 @@ -122,10 +125,15 @@ func DrainPostInitEE(ctx context.Context, br *bufio.Reader, conn net.Conn, dec c return ctx.Err() default: } - d := 200 * time.Millisecond + d := 500 * time.Millisecond if rem := time.Until(end); rem < d { d = rem } + if cd, ok := ctx.Deadline(); ok { + if rem := time.Until(cd); rem < d { + d = rem + } + } if d <= 0 { return nil } @@ -185,22 +193,27 @@ func VerifyResPQ(ctx context.Context, conn net.Conn, enc, dec cipher.Stream, tls deadline, ok := ctx.Deadline() if !ok { - deadline = time.Now().Add(15 * time.Second) + deadline = time.Now().Add(60 * time.Second) } var acc []byte const maxAcc = 256 << 10 + // Один read ждёт ответ DC; 3s мало при нагрузке/гео — держим до 30s, но не дольше ctx. + const perReadSlack = 30 * time.Second for time.Now().Before(deadline) { select { case <-ctx.Done(): return ctx.Err() default: } - chunkDeadline := time.Now().Add(3 * time.Second) + chunkDeadline := time.Now().Add(perReadSlack) if chunkDeadline.After(deadline) { chunkDeadline = deadline } + if !chunkDeadline.After(time.Now().Add(50 * time.Millisecond)) { + return fmt.Errorf("%w: not enough time left waiting for resPQ", context.DeadlineExceeded) + } _ = conn.SetReadDeadline(chunkDeadline) var chunk []byte var rerr error