Update MTPROXY_CHECK_TIMEOUT to 45s in docker-compose and documentation; adjust default timeout handling in code for improved performance under slow network conditions.
Publish mtproxy_checker Docker image / test (push) Successful in 13s
Publish mtproxy_checker Docker image / build-and-push (push) Successful in 52s

This commit is contained in:
Denozordec
2026-04-11 13:39:52 +07:00
parent 6a6183a994
commit 6f2dbcbca3
5 changed files with 25 additions and 6 deletions
+2 -1
View File
@@ -58,7 +58,8 @@ func loadConfig() (*config, error) {
} }
timeoutStr := strings.TrimSpace(os.Getenv("MTPROXY_CHECK_TIMEOUT")) timeoutStr := strings.TrimSpace(os.Getenv("MTPROXY_CHECK_TIMEOUT"))
if timeoutStr == "" { if timeoutStr == "" {
timeoutStr = "15s" // Fake-TLS + drain + req_pq/resPQ к DC часто >15s на медленных линиях и при многих прокси подряд.
timeoutStr = "45s"
} }
checkTimeout, err := time.ParseDuration(timeoutStr) checkTimeout, err := time.ParseDuration(timeoutStr)
if err != nil || checkTimeout <= 0 { if err != nil || checkTimeout <= 0 {
+1 -1
View File
@@ -12,7 +12,7 @@ services:
MTPROXY_LIST_FILE: /data/proxies.txt MTPROXY_LIST_FILE: /data/proxies.txt
MTPROXY_CHECK_INTERVAL: 5m MTPROXY_CHECK_INTERVAL: 5m
MTPROXY_HTTP_ADDR: ":8080" MTPROXY_HTTP_ADDR: ":8080"
MTPROXY_CHECK_TIMEOUT: 15s MTPROXY_CHECK_TIMEOUT: 45s
MTPROXY_DC_ID: "2" MTPROXY_DC_ID: "2"
# Опционально: только перечисленные IP/CIDR (иначе не задавайте переменную) # Опционально: только перечисленные IP/CIDR (иначе не задавайте переменную)
# MTPROXY_ALLOWED_IPS: "172.17.0.0/16,127.0.0.1" # MTPROXY_ALLOWED_IPS: "172.17.0.0/16,127.0.0.1"
+1 -1
View File
@@ -49,7 +49,7 @@ docker run -d --name mtproxy-api --restart unless-stopped -p 8080:8080 `
| `MTPROXY_LIST_FILE` | `/data/proxies.txt` | Путь к файлу: одна `tg://` ссылка на строку; пустые строки и строки с `#` в начале пропускаются | | `MTPROXY_LIST_FILE` | `/data/proxies.txt` | Путь к файлу: одна `tg://` ссылка на строку; пустые строки и строки с `#` в начале пропускаются |
| `MTPROXY_CHECK_INTERVAL` | `5m` | Интервал между циклами (`time.ParseDuration`, например `5m`, `1h`) | | `MTPROXY_CHECK_INTERVAL` | `5m` | Интервал между циклами (`time.ParseDuration`, например `5m`, `1h`) |
| `MTPROXY_HTTP_ADDR` | `:8080` | Адрес прослушивания HTTP | | `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_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` **не** используется | | `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` **не** используется |
+5
View File
@@ -3,6 +3,7 @@ package checkresult
import ( import (
"context" "context"
"errors" "errors"
"net"
"mtproxy_checker/internal/checker" "mtproxy_checker/internal/checker"
) )
@@ -18,5 +19,9 @@ func Classify(err error) (exitCode int, message string) {
if errors.Is(err, context.DeadlineExceeded) { if errors.Is(err, context.DeadlineExceeded) {
return 4, "timeout" return 4, "timeout"
} }
var ne net.Error
if errors.As(err, &ne) && ne.Timeout() {
return 4, "timeout"
}
return 1, err.Error() return 1, err.Error()
} }
+16 -3
View File
@@ -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. // 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 { func DrainPostInitEE(ctx context.Context, br *bufio.Reader, conn net.Conn, dec cipher.Stream, maxWait time.Duration) error {
end := time.Now().Add(maxWait) end := time.Now().Add(maxWait)
if d, ok := ctx.Deadline(); ok && d.Before(end) {
end = d
}
var acc []byte var acc []byte
const maxDrain = 256 << 10 const maxDrain = 256 << 10
drained := 0 drained := 0
@@ -122,10 +125,15 @@ func DrainPostInitEE(ctx context.Context, br *bufio.Reader, conn net.Conn, dec c
return ctx.Err() return ctx.Err()
default: default:
} }
d := 200 * time.Millisecond d := 500 * time.Millisecond
if rem := time.Until(end); rem < d { if rem := time.Until(end); rem < d {
d = rem d = rem
} }
if cd, ok := ctx.Deadline(); ok {
if rem := time.Until(cd); rem < d {
d = rem
}
}
if d <= 0 { if d <= 0 {
return nil return nil
} }
@@ -185,22 +193,27 @@ func VerifyResPQ(ctx context.Context, conn net.Conn, enc, dec cipher.Stream, tls
deadline, ok := ctx.Deadline() deadline, ok := ctx.Deadline()
if !ok { if !ok {
deadline = time.Now().Add(15 * time.Second) deadline = time.Now().Add(60 * time.Second)
} }
var acc []byte var acc []byte
const maxAcc = 256 << 10 const maxAcc = 256 << 10
// Один read ждёт ответ DC; 3s мало при нагрузке/гео — держим до 30s, но не дольше ctx.
const perReadSlack = 30 * time.Second
for time.Now().Before(deadline) { for time.Now().Before(deadline) {
select { select {
case <-ctx.Done(): case <-ctx.Done():
return ctx.Err() return ctx.Err()
default: default:
} }
chunkDeadline := time.Now().Add(3 * time.Second) chunkDeadline := time.Now().Add(perReadSlack)
if chunkDeadline.After(deadline) { if chunkDeadline.After(deadline) {
chunkDeadline = 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) _ = conn.SetReadDeadline(chunkDeadline)
var chunk []byte var chunk []byte
var rerr error var rerr error