Вы подняли свой прокси-сервер, настроили навороченный sing-box на Android, всё летает, 4K видео грузится мгновенно. Но стоит положить телефон в карман на пять минут но все исчезает. Соединение залипает, SSH-сессии рвутся, а WhatsApp-звонки превращаются в тишину
Как только вы включаете экран сеть оживает. Казалось бы, типичный агрессивный энергосберегатор Android, но всё гораздо глубже. Я обнаружил там проблему на стыке рантайма Go, логики ядра Linux
Улика №1: conntrack и чистка сети
Первое, что бросается в глаза при анализе логов это странное поведение системы при событиях Pause и Wake. В Android-клиенте sing-box при выключении экрана срабатывает механизм приостановкиDEBUG inbound/hysteria2[hy2-in]: connection failed: timeout: no recent network activity
panic: runtime error: index out of range [0] with length 0
goroutine 615 [running]:
github.com/sagernet/sing/common/bufio.(*SyscallVectorisedWriter).WriteVectorised(...)
В современных сборках sing-box включен флаг with_conntrack. Когда Android сообщает приложению, что пора уходить в спячку (Pause), срабатывает метод ResetNetwork(). Внутри он вызывает conntrack.Close()
Разработчики хотели как лучше: очистить таблицу состояний, чтобы при смене сети (например, переход с Wi-Fi на LTE) не оставалось мертвых записей.
К чему это приводит? На мобилке это буквально рубит все активные TCP-сессии при каждом засыпании экрана. Если ваше приложение не умеет мгновенно переподнимать сессию, вы получаете обрыв
Улика №2: Проблема замершего времени
Почему WireGuard в официальном приложении работает стабильно, а в Go-клиентах (вроде sing-box или других форков) постоянно отваливается?
Всё дело в том, как Go считает время. По умолчанию рантайм Go для всех таймеров и time.Sleep использует системные часы CLOCK_MONOTONIC
В режиме глубокого сна на Android часы CLOCK_MONOTONIC останавливаются
Если вы настроили WireGuard на отправку keepalive каждые 20 секунд:
Телефон засыпает
Проходит 5 минут реального времени
Процессор просыпается
Go проверяет CLOCK_MONOTONIC и видит, что по его внутренним часам прошло... 0 секунд
Keepalive не отправляется. Сервер на той стороне считает, что клиент сдох, и закрывает сессию
Официальный клиент WireGuard решает это радикально, они используют патченный компилятор Go, который заменяет использование CLOCK_MONOTONIC на CLOCK_BOOTTIME (эти часы тикают даже во сне)// Пример того, что делает патч в рантайме Go для Android:
// Вместо стандартного вызова используется системный вызов к часам,
// которые учитывают время в режиме сна
- unsafe.Pointer(&monotonicClock)
+ unsafe.Pointer(&boottimeClock)
Как это лечится прямо сейчас?
Пока разработчики sing-box ищут способ внедрить поддержку CLOCK_BOOTTIME без пересборки всего рантайма, есть костыли, которые реально работают
Отключаем принудительный сброс (только для своих сборок) Если вы собираете клиент сами, можно закомментировать вызов паузы в BoxService.kt. Это предотвратит вызов ResetNetwork() и спасет ваши TCP-соединения
Трюк с Detour Если пустить трафик WireGuard через промежуточный SOCKS-прокси внутри самого sing-box, проблема со скоростью и засыпанием на 4G сетях становится менее выраженной
Пример куска конфига для теста:{
"outbounds": [
{
"type": "direct",
"tag": "direct-out",
"network_strategy": "default"
},
{
"type": "socks",
"tag": "direct-socks",
"server": "127.0.0.1",
"server_port": 9052
}
],
"endpoints": [
{
"type": "wireguard",
"detour": "direct-socks"
}
]
}
Добавление любого поля из Dial Fields (например, network_strategy) заставляет систему иначе аллоцировать ресурсы под сокет, что иногда не дает системе забыть про маршрут
Если ваш VPN отваливается
Проверьте, не слишком ли мал persistent_keepalive_interval (лучше ставить 20-25 сек).
Попробуйте поиграться с параметром strict_route в настройках TUN.
Попробуйте собрать клиент с патчем CLOCK_BOOTTIME.
