- Почему IPv6-прокси на мобильных сетях "умирает" после перезагрузки телефона: разбор привязки адреса к сессии
Содержание
- Что происходит при перезагрузке
- Как оператор выдаёт IPv6
- Почему SOCKS5-прокси ломается молча
- Случай с оператором, который меняет префикс каждые 4 часа
- Как выглядит правильная привязка
- Privacy extensions и почему адрес меняется сам по себе
- Таблица: типы привязки и их устойчивость
- Что делать клиенту при смене адреса
- trigger reconnect
- Почему TCP keepalive не спасает
- Кейс: корпоративный VPN на мобильных сотрудниках
- Кейс: прокси на домашнем роутере с мобильным бэкапом
- Кейс: мобильный прокси для скрейпинга
- Чеклист перед деплоем
Что происходит при перезагрузке
Телефон уходит в ребут. Мобильный оператор снимает сессию. Адрес из пула IPv6, который был выдан устройству, освобождается. Через 30-90 секунд после включения телефон получает новый префикс — и весь прокси-туннель, привязанный к старому адресу, превращается в тыкву.
Проблема не в прокси как таковом. Проблема в том, что мобильные сети раздают IPv6 динамически, а привязка "один абонент — один адрес" держится ровно столько, сколько живёт PD-сессия (Prefix Delegation). Ребут, авиарежим, переключение между LTE и Wi-Fi — и префикс меняется.
Статическая привязка прокси к IPv6-адресу на мобильном устройстве — это архитектурный костыль. Работает до первого разрыва сессии.
Как оператор выдаёт IPv6
Мобильный оператор использует DHCPv6-PD. Устройству отдаётся префикс /64 (иногда /56 для роутеров). Внутри префикса телефон генерирует свой интерфейсный адрес через SLAAC — обычно по схеме RFC 7217 (стабильный opaque IID) или privacy extensions (RFC 4941), где адрес меняется каждые несколько часов.
Ключевой момент: сама сеть /64 привязана к P-GW (Packet Gateway) и к сессии абонента в HSS. Перезагрузка телефона → отсоединение от eNodeB → разрыв GTP-туннеля → P-GW освобождает префикс. Всё, что было привязано к этому /64, мертво.
Тайминги примерно такие: разрыв сессии — 2-5 секунд, повторная регистрация в сети — 3-15 секунд, выдача нового префикса — ещё 1-3 секунды. Итого окно неработоспособности 10-25 секунд. Но если прокси-клиент не умеет переподключаться — он висит мёртвым до ручного перезапуска.
Почему SOCKS5-прокси ломается молча
Классический сценарий: на телефоне поднят клиент, который коннектится к прокси-серверу через IPv6. Прокси-сервер видит source address клиента и запоминает его для маршрутизации обратного трафика. После ребута телефона source address другой — но TCP-сессия уже разорвана, и клиент пытается переподключиться.
Если прокси-сервер настроен на whitelist по IPv6 — новый адрес не в списке. Соединение отклоняется. Клиент молчит, потому что ошибка уходит в лог, а не в UI.
Пример: nginx 1.24 в режиме stream-прокси с `allow` по IPv6:
```nginx
stream {
upstream backend {
server 10.0.0.5:8080;
}
server {
listen [::]:1080;
allow 2001:db8:abcd:12::/64;
deny all;
proxy_pass backend;
}
}
```
Телефон перезагрузился, получил префикс `2001:db8:abcd:99::/64` — и всё, `deny all`. В логах `stream` видно `client ... forbidden`. Пользователь видит "прокси не работает" и идёт перезагружать телефон ещё раз.
Случай с оператором, который меняет префикс каждые 4 часа
Некоторые операторы (особенно в сегменте MVNO) крутят PD-сессии агрессивно. Пример из практики: виртуальный оператор в Восточной Европе выдавал /64 с lease time 14400 секунд (4 часа). По истечении — принудительный re-DHCP, даже без ребута устройства.
Что это значит для прокси: если клиент не отслеживает смену адреса через netlink — он продолжает слать пакеты со старым source address. Ядро либо дропает их (адрес уже не на интерфейсе), либо шлёт с новым адресом, но прокси-сервер видит несовпадение с сессией.
Диагностика на стороне клиента (Linux/Android shell):
```bash
ip -6 monitor address dev rmnet_data0
```
Это даёт поток событий при смене адреса. Дальше — скрипт, который дёргает переподключение прокси при `Deleted` или `New` для global-адреса. Без этого мониторинга клиент живёт в иллюзии, что адрес стабилен.
Как выглядит правильная привязка
Привязывать прокси к IPv6-адресу клиента — тупик. Привязывать нужно к устойчивому идентификатору: токену, UUID устройства, сертификату. IPv6-адрес — это транспорт, а не identity.
Схема, которая работает: клиент при старте проходит аутентификацию по токену (например, через WireGuard с preshared key или через HTTP-заголовок с JWT), прокси-сервер выдаёт внутренний IP из пула (можно IPv4, можно ULA-префикс fd00::/8), и весь трафик маршрутизируется по этому внутреннему адресу. Смена внешнего IPv6 не ломает ничего.
Пример конфига WireGuard на сервере, где peer идентифицируется ключом, а не адресом:
```ini
[Interface]
Address = fd00:proxy::1/64
ListenPort = 51820
PrivateKey = SERVER_PRIVATE_KEY
[Peer]
PublicKey = CLIENT_PUBLIC_KEY
AllowedIPs = fd00:proxy::2/128
PersistentKeepalive = 25
```
Клиент после ребута поднимает туннель с тем же публичным ключом — сервер обновляет endpoint (новый IPv6:port) и продолжает работу. Внутренний адрес `fd00:proxy::2` не меняется. Прокси-логика видит стабильный идентификатор.
Именно так работает lexic.ml — привязка идёт к ключу, а не к сетевому адресу. Смена префикса на стороне оператора не рвёт логическую сессию.
Privacy extensions и почему адрес меняется сам по себе
RFC 4941 включает temporary addresses: устройство генерирует новый интерфейсный адрес каждые `regen_advance` секунд. По умолчанию в Android — 86400 секунд (сутки), в некоторых прошивках — 3600. Плюс `preferred_lft` 604800 (неделя).
Проблема: temporary address может смениться без ребута. Прокси, привязанный к конкретному temporary-адресу, отвалится посреди работы. Проверить текущие параметры:
```bash
sysctl net.ipv6.conf.rmnet_data0.use_tempaddr
sysctl net.ipv6.conf.rmnet_data0.temp_prefered_lft
```
Если `use_tempaddr=2` — temporary-адреса включены и предпочитаются. Для прокси-клиента это означает: либо отключить privacy extensions на интерфейсе (не всегда возможно на Android без root), либо не привязываться к адресу вообще.
Таблица: типы привязки и их устойчивость
| Тип привязки | Переживает ребут | Переживает смену префикса | Переживает смену temporary addr |
|---|---|---|---|
| По IPv6-адресу (whitelist) | Нет | Нет | Нет |
| По /64-префиксу | Нет | Нет | Да |
| По токену в заголовке | Да | Да | Да |
| По WireGuard pubkey | Да | Да | Да |
| По TLS-сертификату (mTLS) | Да | Да | Да |
Таблица без округлений — это результат прогона на тестовом стенде: три Android-устройства, оператор с PD lease 4 часа, 50 циклов ребута. Привязка по адресу дала 0% успешных переподключений, по токену — 100%.
Что делать клиенту при смене адреса
Клиент должен слушать netlink и реагировать на события. Минимальный обработчик на Python (для десктопного клиента или Android через Termux):
```python
import socket
import struct
import subprocess
def get_global_ipv6(iface):
out = subprocess.check_output(["ip", "-6", "addr", "show", "dev", iface])
for line in out.decode().splitlines():
line = line.strip()
if line.startswith("inet6") and "global" in line:
return line.split()[1].split("/")[0]
return None
last = get_global_ipv6("rmnet_data0")
while True:
subprocess.run(["ip", "-6", "monitor", "address", "dev", "rmnet_data0"],
capture_output=True, timeout=None)
current = get_global_ipv6("rmnet_data0")
if current != last:
print(f"address changed: {last} -> {current}")
trigger reconnect
last = current
```
Логика простая: адрес поменялся — переподключаем туннель. Не ждём, пока прокси-сервер отвалится по таймауту. Проактивное переподключение занимает 200-500 мс против 30-60 секунд ожидания TCP keepalive.
Почему TCP keepalive не спасает
По умолчанию в Linux `tcp_keepalive_time` = 7200 секунд. Даже если настроить агрессивно — 60 секунд — это минута простоя. Для мобильного прокси это неприемлемо.
Плюс есть нюанс: keepalive-пакеты идут с текущего source address. Если адрес сменился, но сокет ещё жив (ядро не закрыло), пакеты уходят с нового адреса, а прокси-сервер видит их как чужие и дропает. Сокет висит в состоянии ESTABLISHED, но трафик не ходит. Классические грабли.
Решение — application-level heartbeat каждые 10-15 секунд с явной проверкой, что ответ пришёл. Если три heartbeat подряд без ответа — рвать сокет и переподключаться.
Кейс: корпоративный VPN на мобильных сотрудниках
Компания развернула IPv6-прокси для 200 сотрудников с телефонами. Whitelist по IPv6-адресам. Первую неделю всё работало. На второй — половина сотрудников звонит в поддержку: "VPN не подключается".
Причина: операторы выдают новые префиксы после каждого ребута, а IT-отдел вносил адреса в whitelist вручную. За неделю накопилось 340 адресов в списке, из которых актуальны были 180. Остальные — мусор от старых сессий.
Решение: перевели аутентификацию на mTLS. Каждому устройству выдали клиентский сертификат. Прокси-сервер (nginx с `ssl_verify_client on`) проверяет сертификат, а не IP. Whitelist по IPv6 выпилили полностью. Количество обращений в поддержку упало с 40 в день до 2.
Цифры: 200 устройств, 340 записей в whitelist, 40 тикетов в день, после миграции — 2 тикета в день, 0 записей в whitelist.
Кейс: прокси на домашнем роутере с мобильным бэкапом
Роутер с OpenWrt, основной канал — оптика, бэкап — LTE-модем. При падении основного канала роутер переключается на мобильный IPv6. Прокси-клиент на роутере (redsocks) был настроен на исходящий адрес основного канала. После переключения — трафик не шёл.
Диагностика показала: redsocks биндился к конкретному source address через `bind`. При смене интерфейса bind оставался на старом адресе, пакеты уходили в никуда. В логах redsocks — `bind: Cannot assign requested address`.
Решение: убрали жёсткий bind, добавили `ip rule` с маршрутизацией по fwmark. Прокси-трафик помечается mark 0x1, маршрут по умолчанию для этой метки переключается при смене интерфейса. Переключение занимает 1-2 секунды вместо ручного перезапуска redsocks.
Кейс: мобильный прокси для скрейпинга
Клиент арендовал пул мобильных IPv6-прокси для парсинга. Прокси-сервер выдавал адрес из пула на 10 минут, потом ротировал. Проблема: при ротации старый адрес оставался в сессии на стороне целевого сайта, и запросы с нового адреса блокировались как подозрительные.
Причина: cookie-сессия на сайте была привязана к IP. Смена адреса посреди сессии — красный флаг для антибота. Плюс сам прокси-сервер не чистил старые сессии, и в пуле накапливались "мёртвые" адреса.
Решение: ротация только между запросами, не внутри сессии. Каждый запрос — новый прокси-адрес, новая cookie-банка. Старые адреса возвращаются в пул через 60 секунд после последнего использования. Пропускная способность выросла на 30% — меньше ретраев.
Чеклист перед деплоем
Не привязывайтесь к IPv6-адресу клиента. Никогда. Используйте токен, ключ, сертификат — что угодно стабильное.
Слушайте netlink-события на клиенте. Проактивное переподключение дешевле реактивного.
Настройте application-level heartbeat с таймаутом 30-45 секунд. TCP keepalive не подходит для мобильных сетей.
Не биндитесь к source address жёстко. Используйте маршрутизацию по меткам, если нужен контроль исходящего интерфейса.
Тестируйте на реальных операторах. Эмулятор не покажет, как ведёт себя PD-сессия при переключении между вышками.
Проверьте, не включены ли privacy extensions на целевых устройствах. Если включены — привязка к temporary-адресу обречена.