net: «неожиданный» фрагмент сбрасывает передачу, а не весь пакет
Source.NET в ReadSubChannelData при фрагменте вне диапазона или нулевой длине делает data.Return() и выходит — это штатная ситуация для опоздавшего/дублирующего фрагмента. Наш код возвращал NetError, из-за чего терялись все остальные сообщения пакета, а с ними и signon-блок.
This commit is contained in:
parent
459fe6b0bc
commit
fb48358208
2 changed files with 67 additions and 8 deletions
|
|
@ -380,10 +380,17 @@ impl NetChan {
|
|||
}
|
||||
|
||||
if start_fragment + num_fragments > stream.num_fragments {
|
||||
return Err(NetError::Protocol(format!(
|
||||
"фрагмент вне диапазона: {start_fragment}+{num_fragments}>{}",
|
||||
stream.num_fragments
|
||||
)));
|
||||
// Source.NET (ReadSubChannelData) просто возвращает управление:
|
||||
// это штатная ситуация для опоздавшего/дублирующего фрагмента.
|
||||
// Ошибкой считать нельзя — иначе один старый фрагмент уронит
|
||||
// разбор всех остальных сообщений пакета.
|
||||
tracing::trace!(
|
||||
start_fragment,
|
||||
num_fragments,
|
||||
num_fragments_total = stream.num_fragments,
|
||||
"фрагмент вне диапазона, ждём повтор"
|
||||
);
|
||||
return Ok(());
|
||||
}
|
||||
if start_fragment + num_fragments == stream.num_fragments {
|
||||
// Последний фрагмент — укорачиваем до реального размера.
|
||||
|
|
@ -393,10 +400,17 @@ impl NetChan {
|
|||
}
|
||||
}
|
||||
if length == 0 || offset + length > stream.bytes {
|
||||
return Err(NetError::Protocol(format!(
|
||||
"некорректный фрагмент: offset={offset} length={length} bytes={}",
|
||||
stream.bytes
|
||||
)));
|
||||
// Source.NET делает data.Return() и уходит: передача отбрасывается
|
||||
// целиком, ждём её с начала. Ошибка здесь означала бы потерю всех
|
||||
// прочих сообщений пакета.
|
||||
tracing::trace!(
|
||||
offset,
|
||||
length,
|
||||
bytes = stream.bytes,
|
||||
"некорректный фрагмент, передача отброшена"
|
||||
);
|
||||
stream.reset();
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
for i in 0..length {
|
||||
|
|
|
|||
|
|
@ -709,3 +709,48 @@ public enum PacketFlag {
|
|||
`MAX_FRAGMENTS_PER_PACKET_BITS = 5`, `NETMSG_TYPE_BITS = 6`,
|
||||
`IN_RELIABLE_STATE_BITS = 16`, `SubChannel.MAX_BITS = 4` (совпало с дизасмом),
|
||||
порядок `net.SetConVar` → `net.SignonState(CONNECTED, -1)`.
|
||||
|
||||
### 13.13. Полнота эталонного захвата: send tables в нём отсутствуют
|
||||
|
||||
Проверка непрерывности `seq` в `real_client.pcap`:
|
||||
|
||||
| сторона | netchan-пакетов | пропусков | потеряно |
|
||||
|---|---|---|---|
|
||||
| сервер | 2718 | 164 | **2576** |
|
||||
| клиент | 3204 | 82 | 201 |
|
||||
|
||||
Основная потеря — в начале сессии (пакеты #63…#130, по 30+ подряд), то есть
|
||||
именно там, где сервер шлёт send tables, baselines и string tables.
|
||||
|
||||
**Вывод:** отсутствие send tables в эталоне — дефект захвата (трафик шёл через
|
||||
SOCKS-прокси), а не дефект парсера. Проверено: собранные 582 блока разбираются
|
||||
до конца, первый блок (`#34`, 305 байт) содержит `SV_Print` (баннер ClassicBox)
|
||||
и `SV_ServerInfo` (proto=24, count=2, ded=1, maxcl=48, tick=0.030303,
|
||||
`dir=garrysmod`, `map=gm_bigcity_improved_rp`) — ровно тот блок, что лежит в
|
||||
фикстуре `tests/fixtures/classicbox_signon_block.bin`.
|
||||
|
||||
Многофрагментных (multi-fragment) передач в читаемом виде в захвате нет: все
|
||||
собранные блоки меньше `FRAGMENT_SIZE`. Значит дальнейшую верификацию этой ветки
|
||||
проводить не на чем — но её раскладка теперь известна из исходника и совпадает с
|
||||
нашей реализацией.
|
||||
|
||||
### 13.14. Устойчивость разбора фрагментов
|
||||
|
||||
`ReadSubChannelData` в Source.NET при «неожиданном» фрагменте (за пределами
|
||||
диапазона, нулевой длине, выходе за `bytes`) **не считает это ошибкой**: делает
|
||||
`data.Return()` и выходит, передача будет прислана заново. Наш код возвращал
|
||||
`NetError`, что сбрасывало разбор всех остальных сообщений пакета — достаточно
|
||||
одного опоздавшего фрагмента, чтобы потерять весь signon-блок. Исправлено на
|
||||
отбрасывание передачи (`stream.reset()`) с `trace`-логом.
|
||||
|
||||
### 13.15. Распределение флагов в эталоне
|
||||
|
||||
| флаги | пакетов | состав |
|
||||
|---|---|---|
|
||||
| 0x20 / 0xa0 / 0x60 | 2187 | Challenge (+ биты добивки 0x40/0x80) |
|
||||
| 0x21 / 0x61 / 0xa1 | 527 | Reliable\|Challenge (+ добивка) |
|
||||
| 0xb0, 0xa7, 0xbf, 0xd3 | 4 | единичные Split/Choked/Compressed/Encrypted |
|
||||
|
||||
То есть правка `CHOKED = 1<<4` затрагивала 3 пакета из 2718 — редко, но каждый
|
||||
из них раньше давал рассинхрон на 8 бит. Биты 6–7 (0x40/0x80) — добивка, их
|
||||
значения игнорируются.
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue