Improve TCP keepalive and idle timeout for mobile clients#441
Conversation
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
|
Та же проблема присутствует и в оригинальном MTProxy. |
|
Вы совершенно правы, большое спасибо. Если честно, я был прямо на 100% уверен, что SetKeepAlivePeriod включает и сам keep alive. Полез проверять - а и правда, кажется так оно и есть |
|
Да, по поводу вопросов:
|
Sync upstream history (no-op merge after cherry-pick of 9seconds#441)
|
Скажите, кто-нибудь проверял что это реально работает? Я так и не смог отсеять keepalive пакеты tcpdump'ом на системе с mtg: Обычно прокси часто подсыпает пакетами, но когда телефон засыпает, это резко прекращается, хотя соединение похоже не рвется, т.к. при входящих событиях пакеты появляются. Так вот, если эта фича работает, я ожидаю видеть как минимум 1 keepalive пакет раз в 30 секунд, если других пакетов нет. Я кстати не знаю как сказать tcpdump'у показывать только keepalive пакеты, поэтому смотрю всё вместе, но всё равно - такое ощущение что keepalive просто не работает. Было бы полезно если кто-то ещё проведёт этот эксперимент, спасибо. |
Это скорее повод для обсуждения — хотелось бы услышать мнение, имеют ли эти изменения смысл и насколько адекватны выбранные дефолты.
Проблема
Мобильные клиенты (особенно на сотовых сетях) теряют 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 минут:Вопросы