Фикс залипания мобильных клиентов через TCP keepalive. telemt ставит SO_KEEPALIVE на сокеты, поэтому достаточно подкрутить sysctl — ядро само быстро пробивает тихие коннекты и шлёт RST. Клиент после выхода из фона не висит на дохлом сокете, а сразу переподключается.
Мобильный клиент сворачивается → ОС усыпляет приложение, но сокет закрывается не чисто (FIN/RST не уходит, процесс заморожен). Сервер продолжает держать established-коннект открытым — по умолчанию чуть ли не сутками. Юзер разворачивает Telegram → клиент пытается ожить на старом сокете, а путь (NAT) его состояние уже выкинул → пакеты в никуда → висяк.
| Этап | Что происходит |
|---|---|
| фон | приложение заморожено, сокет не закрыт |
| сервер | держит мёртвый established без таймаута |
| возврат | клиент залипает на протухшем коннекте |
SO_KEEPALIVE, поэтому ядро при правильном sysctl само быстро обнаружит тихий коннект, отправит пробы, и через несколько неотвеченных — порвёт его RST-ом. Клиент на возврате сразу видит «мёртв» и делает чистый реконнект.Проверки ниже на Python — он есть в любой Ubuntu из коробки. На всякий случай убедись:
python3 --version # если не найден: apt install -y python3
Скрипт сам достаёт порты инстансов из /etc/telemt/*.toml, парсит ss (он выдаёт по две строки на соединение — питон их корректно склеивает) и считает таймеры на клиентских коннектах. Ничего подставлять руками не нужно.
python3 << 'PYEOF' import subprocess, re, glob from collections import Counter # порты инстансов из конфигов ports = set() for f in glob.glob("/etc/telemt/*.toml"): for line in open(f): m = re.match(r'\s*port\s*=\s*(\d+)', line) if m: ports.add(m.group(1)) # ss: established с таймерами; склеиваем двустрочные записи out = subprocess.run(["ss","-tinoH","state","established"], capture_output=True, text=True).stdout records, buf = [], "" for ln in out.split("\n"): if not ln.strip(): continue if ln[0].isspace(): buf += " " + ln.strip() else: if buf: records.append(buf) buf = ln.strip() if buf: records.append(buf) # считаем таймеры на соединениях с локальным портом из наших timers, total = Counter(), 0 for r in records: f = r.split() local = f[2] if len(f) > 2 else "" if not any(local.endswith(":"+p) for p in ports): continue total += 1 m = re.search(r'timer:\((\w+)', r) timers[m.group(1) if m else "—"] += 1 # красивый вывод W = 44 print("╭" + "─"*W + "╮") print("│ " + "KEEPALIVE CHECK".ljust(W-1) + "│") print("├" + "─"*W + "┤") print("│ " + f"Порты telemt: {', '.join(sorted(ports)) or '—'}".ljust(W-1) + "│") print("│ " + f"Соединений: {total}".ljust(W-1) + "│") print("├" + "─"*W + "┤") for name, cnt in timers.most_common(): bar = "█" * min(cnt, 24) print("│ " + f"{name:<10} {cnt:>4} {bar}".ljust(W-1) + "│") print("╰" + "─"*W + "╯") PYEOF
keepalive — значит keepalive армирован, метод подходит. Несколько on — это retransmit-таймер активных передач, норма, к делу не относятся. Если соединений 0 — просто сейчас никто не подключён, проверь когда будет трафик.Задаём агрессивные тайминги keepalive глобально. Дохлый коннект будет рваться примерно за 60 + 15×3 ≈ 105с после ухода в тишину.
cat > /etc/sysctl.d/99-tg-keepalive.conf << 'EOF' # после 60с тишины — первая keepalive-проба net.ipv4.tcp_keepalive_time = 60 # повтор пробы каждые 15с net.ipv4.tcp_keepalive_intvl = 15 # 3 неотвеченных пробы → RST net.ipv4.tcp_keepalive_probes = 3 EOF sysctl --system
Сразу убедимся, что значения прописались в ядро:
sysctl net.ipv4.tcp_keepalive_time \
net.ipv4.tcp_keepalive_intvl \
net.ipv4.tcp_keepalive_probes
Это источник правды: применился ли sysctl к живым сокетам. Тот же подход, но вытаскиваем сами значения обратного отсчёта keepalive-таймера — видно, сползают ли они к заданным 60с.
python3 << 'PYEOF' import subprocess, re, glob from collections import Counter ports = set() for f in glob.glob("/etc/telemt/*.toml"): for line in open(f): m = re.match(r'\s*port\s*=\s*(\d+)', line) if m: ports.add(m.group(1)) out = subprocess.run(["ss","-tinoH","state","established"], capture_output=True, text=True).stdout records, buf = [], "" for ln in out.split("\n"): if not ln.strip(): continue if ln[0].isspace(): buf += " " + ln.strip() else: if buf: records.append(buf) buf = ln.strip() if buf: records.append(buf) # собираем значения обратного отсчёта keepalive-таймера countdown = Counter() for r in records: f = r.split() local = f[2] if len(f) > 2 else "" if not any(local.endswith(":"+p) for p in ports): continue m = re.search(r'timer:\(keepalive,([^,)]+)', r) if m: countdown[m.group(1)] += 1 # сортируем по реальной длительности (ms < sec < min) def secs(v): n = float(re.findall(r'[0-9.]+', v)[0]) if 'ms' in v: return n/1000 if 'min' in v: return n*60 return n W = 44 print("╭" + "─"*W + "╮") print("│ " + "KEEPALIVE COUNTDOWN".ljust(W-1) + "│") print("├" + "─"*W + "┤") if not countdown: print("│ " + "нет активных соединений сейчас".ljust(W-1) + "│") else: for val, cnt in sorted(countdown.items(), key=lambda x: secs(x[0])): bar = "█" * min(cnt, 24) print("│ " + f"{val:<10} {cnt:>3} {bar}".ljust(W-1) + "│") print("╰" + "─"*W + "╯") PYEOF
90min и не уменьшаются → telemt задал тайминги жёстко на сокете, глобальный sysctl их не перебивает. Тогда тайминг правится в конфиге telemt.У telemt есть своя секция таймаутов [timeouts] (client_handshake, client_keepalive, tg_connect, client_ack). Возникает соблазн «ускорить» отлов мёртвых соединений ещё и через неё. Не надо. Для этой задачи правильный слой — ядро (шаги 3–4), а не конфиг telemt.
[timeouts] в конфиге быть не должно. Если её нет — telemt берёт разумные дефолты (client_handshake = 15, client_keepalive = 60, tg_connect = 10, client_ack = 300). Этого достаточно. Если секция есть и ты её правил — лучше удалить целиком и вернуться к дефолтам.Почему именно ядро, а не тайминги telemt:
tcp_keepalive детектит физически мёртвый сокет (клиент пропал из сети, провод оборвался) — ниже уровнем и надёжнее. Тайминги telemt — про логику сессии (успеть хендшейк, ACK), это не про обрыв канала.client_handshake = 10 рвёт медленных мобильных/iOS-клиентов, которые не успевают рукопожатие за 10с. client_keepalive = 15 на рваном канале даёт ложные обрывы живых сессий. Дефолты терпимее.# если что-то вывелось — секция есть, лучше убрать её и перезапустить telemt grep -n '\[timeouts\]' /etc/telemt/*.toml
use_middle_proxy = false: при прямой маршрутизации (direct relay) вся группа ME-таймаутов (me_keepalive_*, me_reconnect_*) вообще не применяется. Работают только клиентские тайминги и ядерный keepalive — ещё одна причина держать всё на ядре, а конфиг telemt не усложнять.Метод лечит симптом — залипание клиента на мёртвом сокете после фона. Границы:
Если нужно вернуть систему в исходное состояние — удаляем файл sysctl и сбрасываем значения ядра к дефолтам. Одной командой:
# удаляем наш конфиг rm -f /etc/sysctl.d/99-tg-keepalive.conf # возвращаем дефолтные значения ядра Linux sysctl -w net.ipv4.tcp_keepalive_time=7200 sysctl -w net.ipv4.tcp_keepalive_intvl=75 sysctl -w net.ipv4.tcp_keepalive_probes=9 # перечитываем оставшиеся sysctl-файлы sysctl --system
Проверяем, что вернулись дефолты (7200 / 75 / 9):
sysctl net.ipv4.tcp_keepalive_time \
net.ipv4.tcp_keepalive_intvl \
net.ipv4.tcp_keepalive_probes
sysctl -w применяет дефолты к ядру сразу, новые соединения подхватят их немедленно. Уже открытые сокеты дойдут до старого таймаута один раз, дальше — на дефолтных. Полностью чисто будет после reboot, но он не обязателен.