Skip to content

Improve TCP keepalive and idle timeout for mobile clients#441

Merged
9seconds merged 1 commit into
9seconds:masterfrom
appolimp:tcp-keepalive-idle-timeout
Apr 6, 2026
Merged

Improve TCP keepalive and idle timeout for mobile clients#441
9seconds merged 1 commit into
9seconds:masterfrom
appolimp:tcp-keepalive-idle-timeout

Conversation

@appolimp
Copy link
Copy Markdown
Contributor

@appolimp appolimp commented Apr 4, 2026

Это скорее повод для обсуждения — хотелось бы услышать мнение, имеют ли эти изменения смысл и насколько адекватны выбранные дефолты.

Проблема

Мобильные клиенты (особенно на сотовых сетях) теряют TCP-соединения когда телефон засыпает. NAT оператора дропает маппинг через 5–30 минут, но ни клиент, ни сервер об этом не узнают. После пробуждения Telegram зависает на "Connecting to proxy" пока не переключишь прокси off/on. Ref: #132

Изменения

1. Включить TCP keepalive на принятых соединениях

SetKeepAlivePeriod вызывался, но SetKeepAlive(true) — нет. Проверил в исходниках Go 1.26: SetKeepAlivePeriod вызывает только setsockopt(TCP_KEEPIDLE), но не включает SO_KEEPALIVE. Без этого ядро не отправляет probe-пакеты на принятых соединениях.

2. Использовать net.KeepAliveConfig для per-socket настройки

Заменил SetKeepAlive + SetKeepAlivePeriod на KeepAliveConfig (Go 1.24+) с явным контролем: Idle (30s), Interval (10s), Count (3). Мёртвые соединения обнаруживаются за ~60с вместо системных дефолтов (~11 мин).

3. Увеличить дефолтный idle timeout с 1m до 5m

MTProto клиенты шлют ping_delay_disconnect каждые ~60с, что сбрасывает idle timer. С idle timeout в 1 минуту возникает race condition — если ping опаздывает на 1–2с, relay закрывается. 5 минут:

  • Убирает race между ping и timeout
  • Переживает типичные засыпания телефона (2–5 мин), пока NAT ещё жив
  • Совпадает с тем, что использует Xray для похожих сценариев (300s)

Вопросы

  • Параметры keepalive (30s/10s/3) — не слишком агрессивные? Может стоит что-то консервативнее?
  • 5 минут — адекватный idle timeout, или лучше меньше (например 90s)?
  • Стоит ли сделать keepalive конфигурируемым через TOML, или хардкод нормально?

TCP keepalive was configured (SetKeepAlivePeriod) but never actually
enabled (SO_KEEPALIVE) on accepted client connections. Go 1.26's
SetKeepAlivePeriod only sets TCP_KEEPIDLE — it does not call
setsockopt(SO_KEEPALIVE, 1). Without SO_KEEPALIVE the kernel never
sends probe packets, so dead connections from sleeping mobile clients
linger until the idle timeout fires.

Replace SetKeepAlive + SetKeepAlivePeriod with net.KeepAliveConfig
(available since Go 1.24) for explicit per-socket control:

  Idle:     30s   (time before first probe)
  Interval: 10s   (between probes)
  Count:    3     (failed probes to declare dead)

This detects dead connections in ~60s instead of relying on system
defaults (tcp_keepalive_intvl=75s, probes=9 → up to 11 minutes).

Increase the default idle timeout from 1 minute to 5 minutes.
MTProto clients send ping_delay_disconnect every ~60s, which resets
the idle timer. The previous 1-minute default created a race: if a
ping arrived even 1–2 seconds late the relay was killed. A 5-minute
window also survives typical mobile sleep periods (phone idle 2–5 min)
where the NAT mapping is still alive and the connection can resume
without reconnection.

Ref: 9seconds#132
@Samesheet
Copy link
Copy Markdown

Та же проблема присутствует и в оригинальном MTProxy.
Из-за нее не держу прокси всегда включенным, т.к. прерванное соединение не восстанавливается. Считаю это багом уровня critical.

@9seconds
Copy link
Copy Markdown
Owner

9seconds commented Apr 6, 2026

Вы совершенно правы, большое спасибо. Если честно, я был прямо на 100% уверен, что SetKeepAlivePeriod включает и сам keep alive. Полез проверять - а и правда, кажется так оно и есть

@9seconds 9seconds merged commit 45f958e into 9seconds:master Apr 6, 2026
6 checks passed
@9seconds
Copy link
Copy Markdown
Owner

9seconds commented Apr 6, 2026

Да, по поводу вопросов:

  1. Мне кажется, это отличные дефолтные значения. В любом случае, эти пробы делаются настолько редко, что можно даже просто игнорировать какой-то оверхед
  2. По умолчанию - вполне. Так-то настройка есть, можно и другое число поставить
  3. Думаю, пока что можно оставить хардкод. Если что, всегда можно и прокинуть параметры

dolonet added a commit to dolonet/mtg-multi that referenced this pull request Apr 6, 2026
dolonet added a commit to dolonet/mtg-multi that referenced this pull request Apr 6, 2026
Sync upstream history (no-op merge after cherry-pick of 9seconds#441)
@appolimp appolimp deleted the tcp-keepalive-idle-timeout branch April 7, 2026 03:40
@appolimp appolimp restored the tcp-keepalive-idle-timeout branch April 7, 2026 04:17
@bam80
Copy link
Copy Markdown
Contributor

bam80 commented May 23, 2026

Скажите, кто-нибудь проверял что это реально работает?

Я так и не смог отсеять keepalive пакеты tcpdump'ом на системе с mtg:
tcpdump -vv -i phy0-sta0 "dst host <client public IP>

Обычно прокси часто подсыпает пакетами, но когда телефон засыпает, это резко прекращается, хотя соединение похоже не рвется, т.к. при входящих событиях пакеты появляются.

Так вот, если эта фича работает, я ожидаю видеть как минимум 1 keepalive пакет раз в 30 секунд, если других пакетов нет.
Но этого похоже не происходит - если наблюдать за трафиком, периодически возникает "тишина", которая может достигать нескольких минут и никак не связана с 30 секундами.
Обычно она нарушается очередным блоком пакетов, а не единичным keepalive.

Я кстати не знаю как сказать tcpdump'у показывать только keepalive пакеты, поэтому смотрю всё вместе, но всё равно - такое ощущение что keepalive просто не работает.

Было бы полезно если кто-то ещё проведёт этот эксперимент, спасибо.

@bam80
Copy link
Copy Markdown
Contributor

bam80 commented May 23, 2026

  • Параметры keepalive (30s/10s/3) — не слишком агрессивные? Может стоит что-то консервативнее?

Слишком:
#526

  • Стоит ли сделать keepalive конфигурируемым через TOML, или хардкод нормально?

Стоит:
#516

Но разобраться с keepalive выше тоже определенно стоит.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants