net: индекс подканала — 3 бита (исправлен silently-баг reliable)

Главная причина отсутствия server info: SUBCHANNEL_BITS был 4, из-за чего
сервер читал subchannel=1 и 0 фрагментов, молча отбрасывая reliable-поток
(net_SignonState не обрабатывался → m_bSendServerInfo не выставлялся).

Подтверждено pcap-разбором и дизасмом engine_srv.so (CNetChan::ProcessPacket:
lea 0x3(%esi) — ровно 3 бита; and $0x1 — reliable-флаг 0x01).
This commit is contained in:
Niko Marmeladkov 2026-10-12 11:44:49 +03:00
parent 9cc4daf6b0
commit 50bb36304e
2 changed files with 57 additions and 3 deletions

View file

@ -31,10 +31,14 @@ pub const FRAGMENT_OFFSET_BITS: usize = 18;
pub const MAX_FRAGMENTS_PER_PACKET_BITS: usize = 5;
/// Биты максимального размера файла.
pub const MAX_FILE_SIZE_BITS: usize = 30;
/// Биты номера подканала (в GMod — 4: 16 подканалов).
pub const SUBCHANNEL_BITS: usize = 4;
/// Биты номера подканала: 3 (8 подканалов, как в Source 2013 / HL2).
///
/// Проверено по эталонному разбору пакета: при 4 битах сервер читает
/// subchannel=1 и «0 фрагментов», молча теряя reliable-данные (из-за этого
/// не обрабатывался net_SignonState и не приходил server info).
pub const SUBCHANNEL_BITS: usize = 3;
/// Ширина индекса подканала: GMod — 4 (16 подканалов), Source 2013 — 3.
/// Ширина индекса подканала.
/// Переопределяется переменной окружения для экспериментов.
pub fn subchannel_bits() -> usize {
std::env::var("SOURCE_RS_SUBCHANNEL_BITS")

View file

@ -344,3 +344,53 @@ SendServerInfo() ; cl->vfunc(0xd0)
- альтернатива для отладки: поднять локальный srcds на незалоченном порту
(сейчас мешает сломанный резолвер в системе — `systemd-resolved` не
запущен, см. §13).
### 13.3. НАЙДЕНА ПРИЧИНА: ширина индекса подканала (3 бита, не 4)
Гипотеза §13.2 оказалась **неверной**: дело не в Steam-авторизации. Настоящая
причина — в заголовке reliable-фрагментов.
**Как обнаружено.** Захвачен трафик живого подключения (`SOURCE_RS_LOG=net=trace`).
Сервер подтверждает наши netchan-пакеты (поле ack растёт 0→1→2→…), но в его
ответах поле `InReliableState` **всегда равно 0**. Значит сервер принимает
заголовок, но **не читает наш reliable-поток**.
Побитовый разбор нашего hello-пакета:
```
seq=1 ack=0 flags=0x21 csum=0x58df rel=0x0001 chal=0x63896b24
payload[191]: 10 bc 01 c5 82 ...
```
| SUBCHANNEL_BITS | прочитанный subchannel | биты потоков | итог |
|---|---|---|---|
| **4** (было) | **1** | `[0, 0, 0]` | **0 фрагментов → reliable-данные молча отброшены** |
| **3** (стало) | **0** | `[1, 0, 0]` | корректный single-block фрагмент |
При 4 битах сервер читал индекс 1 (несуществующий подканал) и ноль фрагментов
→ `net_SignonState` не обрабатывался → `m_bSendServerInfo` не выставлялся →
`SendPendingServerInfo` пропускал клиента → **серверный блок не уходил никогда**.
**Подтверждено дизассемблированием GMod-сервера** (`engine_srv.so`,
`CNetChan::ProcessPacket`):
```
189b6f: and $0x1,%esi ; flags & Reliable (0x01) — наш флаг верен
189b93: lea 0x3(%esi),%edx ; читаем РОВНО 3 бита индекса подканала
```
То есть `SUBCHANNEL_BITS = 3` (8 подканалов, как в Source 2013/HL2),
а не 4. Прежнее значение 4 было ошибочным предположением; из-за него
наши надёжные сообщения молча терялись.
Побочно подтверждено:
- reliable-флаг пакета = `0x01` (`and $0x1`) — совпадает с нашим;
- флаг challenge = `0x20` — совпадает;
- `IN_RELIABLE_STATE_BITS = 16` — совпадает с Source.NET.
Ограничение `SendPendingServerInfo` (для публичного IP требуется
`m_bFullyAuthenticated`) реально, но не блокировало: сервер готов слать данные
сразу после разбора нашего reliable-потока. Новый прогон подключения упирается
уже в `#GameUI_ServerRejectSteam` — это лимит Steam на частые подключения с
одного аккаунта (`k_EBeginAuthSessionResultDuplicateRequest`), а не ошибка
клиента.