Some checks failed
Published content check / validate (push) Failing after 3s
Обновить правила репозитория и подписи исходников в 99 заметках, не затрагивая пользовательские незакоммиченные файлы. Сделать Forgejo Actions содержательным: проверять опубликованный commit, а не пустое рабочее дерево после checkout.
358 lines
35 KiB
Markdown
358 lines
35 KiB
Markdown
---
|
||
date: 2026-07-17
|
||
tags:
|
||
- zapret
|
||
- zapret2
|
||
- nfqws2
|
||
- lua
|
||
- lua-desync
|
||
- desync
|
||
- dissect
|
||
- conntrack
|
||
- reference
|
||
link: https://github.com/bol-van/zapret2/blob/master/docs/manual.md
|
||
aliases:
|
||
- Структура таблицы desync
|
||
- Прототип desync-функции
|
||
- Диссект nfqws2
|
||
- desync API
|
||
- Структура track
|
||
- Поля desync
|
||
---
|
||
# 🧬 Прототип desync-функции и структура таблицы `desync`
|
||
|
||
> [!info] О чём заметка
|
||
> Подробный справочник по **данным**, с которыми работает Lua desync-функция в [[Zapret2/Zapret2|Zapret 2]]: как объявляется функция, что она получает в таблице `desync`, из чего состоит диссект пакета (`dis`), запись потока (`track`), как приходят собранные из нескольких пакетов пейлоады (reasm/decrypt), и в чём особенности ICMP и raw IP. Сам механизм вызова `--lua-desync` (как и зачем) описан в [[desync]]; где вызов стоит в общем конвейере — в [[схема обработки трафика]]; из чего состоит проект — в [[структура проекта]]. Эта заметка — про **устройство входных данных**, то есть то, что нужно, чтобы писать или читать desync-функции.
|
||
|
||
## TL;DR
|
||
|
||
- Прототип: `function desync_f(ctx, desync)`. `ctx` — ручка для вызова C-функций (отправка, cutoff), `desync` — таблица со всеми данными пакета.
|
||
- Функция возвращает **вердикт**: `VERDICT_PASS` (не трогать), `VERDICT_MODIFY` (отправить изменённый диссект), `VERDICT_DROP` (выбросить); ничего не вернуть = `PASS`. Отдельный бит `VERDICT_PRESERVE_NEXT` сохраняет поля «next protocol» в IPv6.
|
||
- Вердикты инстансов **агрегируются**: `MODIFY` перебивает `PASS`, `DROP` перебивает оба; `PRESERVE_NEXT` — если его вернул хоть один инстанс.
|
||
- Изучать содержимое `desync` удобнее всего готовым инстансом **`pktdebug`**.
|
||
- `desync.dis` — разобранный пакет (поля повторяют C-структуры из `netinet/*.h`; числа уже в machine byte order). `desync.track` — данные потока из conntrack (может отсутствовать — всегда проверяйте!).
|
||
- Многобайтовые числа переведены в порядок машины автоматически, **но sequence numbers 32-битные беззнаковые** — для арифметики используйте `u32add`/`bitand`, а не обычное сложение.
|
||
|
||
---
|
||
|
||
## Прототип Lua desync-функции
|
||
|
||
Любая desync-функция объявляется с двумя параметрами:
|
||
|
||
```lua
|
||
function desync_f(ctx, desync)
|
||
-- ... тело ...
|
||
end
|
||
```
|
||
|
||
- **`ctx`** — контекст для вызова некоторых C-функций. Сам по себе он не предназначен для чтения: его передают обратно в C (при отправке пакетов, при cutoff), чтобы ядро понимало, к какому пакету и очереди относится вызов.
|
||
- **`desync`** — таблица, содержащая **все** передаваемые в функцию значения: аргументы инстанса, диссект текущего пакета, данные потока и многое другое. Это главный вход функции.
|
||
|
||
> [!note] Проще говоря
|
||
> `desync` — это «вот пакет и всё, что мы про него знаем». `ctx` — «вот линия связи обратно в C-ядро, чтобы что-то отправить или отключиться». Первое читают, второе используют как ручку для команд.
|
||
|
||
### Вердикты — что функция возвращает
|
||
|
||
Функция возвращает **вердикт по текущему пакету**. Можно не возвращать ничего — тогда результат приравнивается к `VERDICT_PASS`.
|
||
|
||
| Вердикт | Что делает |
|
||
|:--------|:-----------|
|
||
| `VERDICT_PASS` | передать пакет как есть, **без учёта** изменений диссекта |
|
||
| `VERDICT_MODIFY` | выполнить реконструкцию и отправку **текущего (изменённого) диссекта** |
|
||
| `VERDICT_DROP` | дропнуть (выбросить) текущий пакет |
|
||
| `VERDICT_PRESERVE_NEXT` | **отдельный бит**, который прибавляется к основному вердикту |
|
||
|
||
**Про `VERDICT_PRESERVE_NEXT`.** Это не самостоятельный вердикт, а флаг, добавляемый к основному (например, `VERDICT_MODIFY + VERDICT_PRESERVE_NEXT`). Он велит использовать поля «next protocol» в IPv6-заголовке и IPv6 extension headers **как есть**. Без него эти поля генерируются автоматически по содержимому диссекта. Нужен, когда вы вручную выстраиваете цепочку заголовков и не хотите, чтобы ядро её пересчитало.
|
||
|
||
> [!important] Агрегация вердиктов по цепочке инстансов
|
||
> Результат всех `--lua-desync`-инстансов профиля объединяется по приоритету: `VERDICT_MODIFY` замещает `VERDICT_PASS`, а `VERDICT_DROP` замещает их обоих. `VERDICT_PRESERVE_NEXT` применяется, если его вернул **хотя бы один** инстанс. То есть достаточно одному инстансу вернуть `DROP` — оригинал будет выброшен, что бы ни вернули остальные.
|
||
|
||
---
|
||
|
||
## Как изучать `desync` вживую
|
||
|
||
Структуру `desync` лучше всего изучать не по таблицам, а по её **реальному содержимому** на конкретном пакете. Для этого в `zapret-lib.lua` есть готовая тестовая функция-инстанс **`pktdebug`** — она выводит всё содержимое `desync` в лог. Достаточно добавить её в профиль:
|
||
|
||
```bash
|
||
--lua-desync=pktdebug
|
||
```
|
||
|
||
Дальнейшие таблицы полей — это как раз то, что вы увидите в выводе `pktdebug` (примеры ниже сняты, в частности, на HTTP-запросе по IPv6 к `http://one.one.one.one`).
|
||
|
||
---
|
||
|
||
## Структура таблицы `desync`
|
||
|
||
Верхний уровень `desync` — это «паспорт» пакета: кто его обрабатывает (какой инстанс/профиль), куда он идёт, что за протокол, и вложенные таблицы с самим пакетом (`dis`) и потоком (`track`).
|
||
|
||
| Поле | Тип | Содержание | Примечание |
|
||
|:-----|:----|:-----------|:-----------|
|
||
| `func` | string | имя desync-функции | |
|
||
| `func_n` | number | номер инстанса внутри профиля | |
|
||
| `func_instance` | string | название инстанса | производная от имени функции, номера инстанса и номера профиля |
|
||
| `profile_n` | number | номер профиля | |
|
||
| `profile_name` | string | название профиля | может отсутствовать |
|
||
| `cookie` | string | значение параметра `--cookie` для профиля | может отсутствовать |
|
||
| `outgoing` | bool | `true`, если направление исходящее | |
|
||
| `ifin` | string | имя входящего интерфейса | может отсутствовать |
|
||
| `ifout` | string | имя исходящего интерфейса | может отсутствовать |
|
||
| `fwmark` | number | fwmark текущего пакета | только в Linux |
|
||
| `target` | table | ip-адрес и порт, по которым проверяются ipset-ы и фильтры по портам | |
|
||
| `replay` | bool | идёт проигрывание задержанного пакета (replay) | |
|
||
| `replay_piece` | number | номер проигрываемой части | нумерация с 1 |
|
||
| `replay_piece_count` | number | количество проигрываемых частей | |
|
||
| `replay_piece_last` | bool | последняя проигрываемая часть | |
|
||
| `l7payload` | string | тип пейлоада текущего пакета или группы пакетов | если неизвестно — `unknown` |
|
||
| `l7proto` | string | тип протокола потока | если неизвестно — `unknown` |
|
||
| `reasm_data` | string | результат сборки многопакетного сообщения, либо сам пейлоад, если сборки не было | пока только для TCP |
|
||
| `reasm_offset` | number | смещение текущего перепроигрываемого пакета в сборке | пока только для TCP |
|
||
| `decrypt_data` | string | результат сборки и дешифровки пейлоадов нескольких пакетов | применяется для QUIC |
|
||
| `tcp_mss` | number | MSS противоположного конца TCP-соединения | присутствует всегда, только для TCP |
|
||
| `track` | table | данные, привязанные к записи conntrack | только если есть conntrack, может не быть |
|
||
| `arg` | table | все аргументы инстанса и их значения | подстановки `%` и `#` уже замещены |
|
||
| `dis` | table | диссект текущего пакета | |
|
||
|
||
> [!note] `l7payload` vs `l7proto`
|
||
> `l7proto` — тип протокола **всего потока** (ставится один раз и держится до конца). `l7payload` — тип содержимого **конкретного пакета** внутри этого потока; у разных пакетов одного потока он может отличаться, а нераспознанный помечается как `unknown`. Подробнее про распознавание — [[payload]].
|
||
|
||
---
|
||
|
||
## Структура диссекта (`desync.dis`)
|
||
|
||
**Диссект** — это разобранный на поля текущий пакет. Прежде чем читать таблицы, важно понять три общих правила устройства диссекта:
|
||
|
||
1. **Версия IP и L4-протокол определяются по наличию полей.** Есть `dis.ip` → это IPv4, есть `dis.ip6` → IPv6. Есть `dis.tcp` → транспорт TCP, есть `dis.udp` → UDP. Не «читайте номер протокола», а проверяйте наличие подтаблицы.
|
||
2. **Имена полей повторяют C-структуры.** Таблицы заголовков копируют названия полей из системных заголовков `netinet/{ip,ip6,tcp,udp}.h`. IP-адреса и IPv4 options передаются как «сырая» строка (raw string) — для перевода raw IP в текст есть C-функция `ntop` (она сама определяет версию по размеру). IPv6 extension headers и TCP options представлены таблицами.
|
||
3. **Порядок байт уже машинный.** Все многобайтовые числовые значения автоматически переведены из сетевого порядка байт (network byte order) в порядок машины (machine byte order) — читать и сравнивать их можно как обычные числа. Исключение по осторожности — sequence numbers, см. [раздел ниже](#работа-с-sequence-numbers).
|
||
|
||
### Верхний уровень диссекта
|
||
|
||
| Поле | Тип | Описание |
|
||
|:-----|:----|:---------|
|
||
| `ip` | table | заголовок IPv4 |
|
||
| `ip6` | table | заголовок IPv6 |
|
||
| `frag_off` | number | смещение IP-фрагмента; присутствует **только** в IP-фрагментах |
|
||
| `tcp` | table | заголовок TCP |
|
||
| `udp` | table | заголовок UDP |
|
||
| `icmp` | table | заголовок ICMP |
|
||
| `l4proto` | number | `IPPROTO_TCP` или `IPPROTO_UDP` |
|
||
| `transport_len` | number | длина пакета без L3-заголовков |
|
||
| `l3_len` | number | длина L3-заголовков, включая ip options и IPv6 extension headers |
|
||
| `l4_len` | number | длина L4-заголовка, включая tcp options |
|
||
| `payload` | string | L4-пейлоад (или содержимое после L3-заголовков для raw IP) |
|
||
|
||
### Заголовок IPv4 (`dis.ip`)
|
||
|
||
| Поле | Описание |
|
||
|:-----|:---------|
|
||
| `ip_v` | версия IP — 4 |
|
||
| `ip_hl` | длина IP-заголовка в блоках по 4 байта (5 без ip options) |
|
||
| `ip_tos` | type of service; содержит DSCP |
|
||
| `ip_len` | полная длина IP-пакета со всеми заголовками и пейлоадом |
|
||
| `ip_id` | идентификатор пакета для сборки из фрагментов |
|
||
| `ip_off` | offset фрагмента, флаги MF (more fragments) и DF (don't fragment) |
|
||
| `ip_ttl` | time to live — максимальное число хопов |
|
||
| `ip_p` | номер IP-протокола (как правило `IPPROTO_TCP` или `IPPROTO_UDP`) |
|
||
| `ip_sum` | контрольная сумма IP-заголовка |
|
||
| `ip_src` | IP источника |
|
||
| `ip_dst` | IP назначения |
|
||
| `options` | бинарный блок ip options (почти не используется, режется всеми) |
|
||
|
||
### Заголовок IPv6 (`dis.ip6`)
|
||
|
||
| Поле | Описание |
|
||
|:-----|:---------|
|
||
| `ip6_flow` | первые 4 байта IPv6-заголовка: version (6), traffic class, flow label |
|
||
| `ip6_plen` | длина пакета за вычетом базового заголовка IPv6 — `IP6_BASE_LEN` (40 байт) |
|
||
| `ip6_nxt` | следующий протокол; если нет exthdr — `IPPROTO_TCP` (6) или `IPPROTO_UDP` (17) |
|
||
| `ip6_hlim` | hop limit (тот же смысл, что TTL в IPv4) |
|
||
| `ip6_src` | IPv6-адрес источника |
|
||
| `ip6_dst` | IPv6-адрес приёмника |
|
||
| `exthdr` | массив таблиц расширенных заголовков (индекс с 1) |
|
||
|
||
### IPv6 extension header (`dis.ip6.exthdr[i]`)
|
||
|
||
| Поле | Описание |
|
||
|:-----|:---------|
|
||
| `type` | тип заголовка: `IPPROTO_HOPOPTS`, `IPPROTO_ROUTING`, `IPPROTO_DSTOPTS`, `IPPROTO_MH`, `IPPROTO_HIP`, `IPPROTO_SHIM6`, `IPPROTO_FRAGMENT`, `IPPROTO_AH` |
|
||
| `next` | тип следующего заголовка (аналогично `type`); для последнего может быть `IPPROTO_TCP`/`IPPROTO_UDP` |
|
||
| `data` | данные без первых двух байт (типа и длины) |
|
||
|
||
### Заголовок UDP (`dis.udp`)
|
||
|
||
| Поле | Описание |
|
||
|:-----|:---------|
|
||
| `uh_sport` | порт источника |
|
||
| `uh_dport` | порт приёмника |
|
||
| `uh_ulen` | длина UDP — `UDP_BASE_LEN` (8) + длина пейлоада |
|
||
| `uh_sum` | контрольная сумма UDP |
|
||
|
||
### Заголовок TCP (`dis.tcp`)
|
||
|
||
| Поле | Описание |
|
||
|:-----|:---------|
|
||
| `th_sport` | порт источника |
|
||
| `th_dport` | порт приёмника |
|
||
| `th_x2` | зарезервированное поле; используется для расширенных TCP-флагов |
|
||
| `th_off` | размер TCP-заголовка в блоках по 4 байта |
|
||
| `th_flags` | TCP-флаги: `TH_FIN`, `TH_SYN`, `TH_RST`, `TH_PUSH`, `TH_ACK`, `TH_URG`, `TH_ECE`, `TH_CWR` |
|
||
| `th_seq` | sequence number |
|
||
| `th_ack` | acknowledgement number |
|
||
| `th_win` | размер TCP-окна |
|
||
| `th_sum` | контрольная сумма TCP |
|
||
| `th_urp` | urgent pointer |
|
||
| `options` | массив таблиц TCP-опций (индекс с 1) |
|
||
|
||
### TCP-опция (`dis.tcp.options[i]`)
|
||
|
||
| Поле | Описание |
|
||
|:-----|:---------|
|
||
| `kind` | тип опции: `TCP_KIND_END`, `TCP_KIND_NOOP`, `TCP_KIND_MSS`, `TCP_KIND_SCALE`, `TCP_KIND_SACK_PERM`, `TCP_KIND_SACK`, `TCP_KIND_TS`, `TCP_KIND_MD5`, `TCP_KIND_AO`, `TCP_KIND_FASTOPEN` |
|
||
| `data` | данные опции без `kind` и длины; отсутствует для `TCP_KIND_END` и `TCP_KIND_NOOP` |
|
||
|
||
### Заголовок ICMP (`dis.icmp`)
|
||
|
||
Заголовком ICMP считается его постоянная часть — **первые 8 байт**. Они универсальны и для IPv4-, и для IPv6-версии ICMP. Всё остальное содержимое зависит от типа ICMP и лежит в `payload` — включая возможные специальные поля заголовка или обрезанный исходный пакет, на который был сгенерирован ICMP.
|
||
|
||
| Поле | Описание |
|
||
|:-----|:---------|
|
||
| `icmp_type` | тип ICMP |
|
||
| `icmp_code` | код ICMP |
|
||
| `icmp_cksum` | контрольная сумма ICMP |
|
||
| `icmp_data` | 32-битное поле данных со смещения 4 |
|
||
|
||
---
|
||
|
||
## Приём многопакетных пейлоадов (reasm / decrypt / replay)
|
||
|
||
Иногда одно логическое сообщение не помещается в один пакет. Его сборка называется **reasm** (reassemble). Она выполняется автоматически C-кодом, если встречен пейлоад, требующий сборки, доступен conntrack и сборка не запрещена флагом `--reasm-disable`. На данный момент таких пейлоадов два — **`tls_client_hello`** и **`quic_initial`**: оба могут содержать постквантовую криптографию Kyber, которая не влезает в один пакет.
|
||
|
||
Сборка идёт по-разному:
|
||
|
||
- **`tls_client_hello`** — обычная сборка: пейлоады последовательных TCP-сегментов объединяются в единый блок `reasm_data`.
|
||
- **`quic_initial`** — отдельные пакеты накапливаются во внутреннем буфере, затем происходит их дешифровка, объединение и **дефрагментация** частей пейлоада, разбросанных по пакетам и разным смещениям (так делает Chrome — намеренно, чтобы все реализации следовали стандарту и умели корректно собирать пейлоад по частям).
|
||
|
||
### Как это видно в Lua (replay)
|
||
|
||
Пока сборка не финализирована, пакеты копятся во внутреннем буфере **без вызовов Lua**. Как только сборка завершена, начинается **перепроигрывание** отдельных частей (replay): в Lua-инстансы приходит диссект каждого задержанного пакета, но при этом выставлены поля `desync.replay=true`, `desync.replay_piece`, `desync.replay_piece_count` и `desync.replay_piece_last`.
|
||
|
||
Различие между TCP и QUIC в том, какое поле несёт собранный результат:
|
||
|
||
- **TCP-сборка:** выставляется `desync.reasm_data` — полный блок собранных данных. При этом `desync.dis.payload` по-прежнему содержит пейлоад **отдельного** перепроигрываемого пакета. Если replay нет (одиночный пакет), для TCP `desync.reasm_data` содержит копию `desync.dis.payload`.
|
||
- **QUIC-сборка:** `desync.reasm_data` **отсутствует** — вместо него передаётся `desync.decrypt_data` с результатом дешифровки и дефрагментации всех пейлоадов сборки. (Для QUIC этот `decrypt_data` содержит `tls_client_hello` без record layer.)
|
||
|
||
> [!note] Проще говоря
|
||
> `reasm_data`/`decrypt_data` — это «целое собранное сообщение», а `dis.payload` — «кусок из конкретного пакета». Стратегии, которым нужна полная картина (например, найти домен в SNI и разрезать по нему), работают с собранным блоком; отправка же по-прежнему идёт по отдельным пакетам-частям. Как это использует конкретная функция — см. [[multisplit]].
|
||
|
||
---
|
||
|
||
## Структура `track` (данные потока из conntrack)
|
||
|
||
Таблица `track` присутствует в `desync` **только если** для текущего пакета нашлась запись в conntrack (системе [[схема обработки трафика|отслеживания потоков]]). Её может не быть, если `nfqws2` не видел пакет SYN или SYN,ACK: соединение установили до запуска `nfqws2`, вы не перехватили SYN/SYN,ACK из ядра, либо conntrack принудительно выключен через `--ctrack-disable`.
|
||
|
||
> [!danger] Всегда проверяйте наличие track
|
||
> Ваш код обязан проверять наличие `desync.track` (и опциональных полей внутри него) **перед** обращением — иначе он упадёт с ошибкой на пакетах без conntrack. Проверяйте свой код с `--ctrack-disable` и на разных протоколах — TCP и UDP.
|
||
|
||
### Поля `track`
|
||
|
||
| Поле | Тип | Описание | Примечание |
|
||
|:-----|:----|:---------|:-----------|
|
||
| `incoming_ttl` | number | TTL/hop limit первого входящего пакета потока | может не быть, если не определено |
|
||
| `l7proto` | string | протокол потока | есть всегда; если неизвестно — `unknown` |
|
||
| `hostname` | string | имя хоста (по анализу L6/L7-протоколов) | появляется только после определения |
|
||
| `hostname_is_ip` | bool | является ли hostname IP-адресом | только если есть hostname |
|
||
| `lua_state` | table | хранилище состояния, привязанного к потоку | есть всегда, передаётся с каждым пакетом потока |
|
||
| `lua_in_cutoff` | bool | отсечение Lua от входящего направления | только для чтения |
|
||
| `lua_out_cutoff` | bool | отсечение Lua от исходящего направления | только для чтения |
|
||
| `t_start` | number | unix-время первого пакета потока | с дробной частью высокой точности |
|
||
| `pos` | table | счётчики по направлениям | содержит таблицы `client`, `server`, `direct`, `reverse` |
|
||
|
||
> [!tip] `lua_state` — долгая память потока
|
||
> `track.lua_state` есть всегда (когда есть `track`) и выдаётся **одна и та же** таблица на каждый пакет потока. В ней хранят состояние между пакетами: счётчики попыток, флаги «уже отправлено» и т. п. Для временных данных в пределах одного пакета используйте саму таблицу `desync` (см. [[desync]] про два уровня памяти).
|
||
|
||
### Счётчики `track.pos` (client / server / direct / reverse)
|
||
|
||
`track.pos` содержит подтаблицы счётчиков по двум сторонам соединения: **`client`** — пакеты от клиента, **`server`** — пакеты от сервера. Ещё две подтаблицы, **`direct`** и **`reverse`**, — это просто **ссылки** на `client`/`server`. Куда они указывают, зависит от текущего направления (`desync.outgoing`) и серверного режима (`b_server`): `direct` всегда указывает на **текущее** направление, `reverse` — на **противоположное**.
|
||
|
||
Кроме того, в `track.pos` есть поле **`dt`** — время получения пакета в секундах от `t_start` (с дробной частью высокой точности).
|
||
|
||
Набор счётчиков в каждой подтаблице (подтаблица `tcp` присутствует только для TCP):
|
||
|
||
| Поле | Описание | Примечание |
|
||
|:-----|:---------|:-----------|
|
||
| `pcounter` | счётчик пакетов | |
|
||
| `pdcounter` | счётчик пакетов **с данными** | у которых размер L4-пейлоада не равен 0 |
|
||
| `pbcounter` | счётчик переданных байт | считается только размер L4-пейлоада, без заголовков |
|
||
| `ip6_flow` | последнее поле `ip6.ip6_flow` | отсутствует, если неизвестно или протокол не IPv6 |
|
||
| `tcp.seq0` | начальный sequence соединения | |
|
||
| `tcp.seq` | sequence текущего пакета | |
|
||
| `tcp.rseq` | relative sequence текущего пакета | вычисляется как `seq - seq0` |
|
||
| `tcp.rseq_over_2G` | был переход `rseq` за границу 2 ГБ | s- и p-позиции больше учитывать нельзя |
|
||
| `tcp.pos` | relative sequence верхней границы текущего пакета | вычисляется как `rseq + payload_size` |
|
||
| `tcp.uppos` | максимальный `pos` в соединении | |
|
||
| `tcp.uppos_prev` | `uppos` в предыдущем пакете с данными | полезно для определения ретрансмиссий |
|
||
| `tcp.winsize` | последнее поле `th_win` | без коррекции по scale |
|
||
| `tcp.scale` | последнее значение TCP-опции scale | |
|
||
| `tcp.winsize_calc` | winsize с учётом scale | эффективный размер TCP-окна |
|
||
| `tcp.mss` | последнее значение TCP-опции MSS | |
|
||
|
||
> [!warning] Не перепутайте сторону: MSS / winsize / scale
|
||
> Значения MSS, winsize и scale одна сторона соединения передаёт другой, чтобы та знала допустимые параметры партнёра. Если вам нужно узнать, **какого размера пакеты можно отсылать**, смотрите **противоположную** сторону — то, что она может принять. MSS дополнительно дублируется в `desync.tcp_mss` независимо от наличия conntrack, и там значение уже рассчитано под вычисление размера отправляемого пакета. Если conntrack нет или MSS не был согласован, используется значение по умолчанию `DEFAULT_MSS` (1220).
|
||
|
||
---
|
||
|
||
## Работа с sequence numbers
|
||
|
||
Общее правило «числа уже в машинном порядке байт» верно, но у TCP sequence/ack есть отдельная ловушка: они **32-битные беззнаковые** и переполняются по кругу. Если к `4294967280` (`0xFFFFFFF0`) прибавить `100`, по правилам TCP получится не `4294967380`, а `84` (`0x54`) — счётчик «завернулся».
|
||
|
||
Проблема в том, что Lua считает числа в более широком, знаковом представлении: обычное `seq1 + seq2` даст «неправильные» с точки зрения TCP `4294967380`, без заворота. Поэтому для арифметики и сравнения sequence используйте C-функции **`u32add`** и **`bitand`**. Пример — проверка `seq1 >= seq2`:
|
||
|
||
```lua
|
||
-- истина, если seq1 >= seq2 (при расхождении не более 2 ГБ)
|
||
0 == bitand(u32add(seq1, -seq2), 0x80000000)
|
||
```
|
||
|
||
Наивный вариант через обычное сложение работать корректно не будет, а этот — будет, **если** `seq1` ушёл от `seq2` не более чем на 2 ГБ. Большее расстояние через sequence в принципе не отследить, поэтому при передаче больших объёмов данных sequence не может служить надёжным счётчиком.
|
||
|
||
> [!tip] Для счёта пакетов/байт берите p*counter
|
||
> Счётчики `pcounter`/`pdcounter`/`pbcounter` в [`track.pos`](#счётчики-trackpos--client--server--direct--reverse) — **64-битные**, у них проблемы заворота нет. Если нужен именно счётчик, используйте их, а не арифметику sequence.
|
||
|
||
---
|
||
|
||
## Особенности обработки ICMP
|
||
|
||
Некоторые типы ICMP несут прикреплённый исходный пакет, на который был сгенерирован ICMP, — их называют **«related»**. `nfqws2` распознаёт такие пейлоады и ищет по прикреплённому пакету исходную запись conntrack. Если она находится:
|
||
|
||
- выбирается **кэшированный профиль** (тот, к которому относится прикреплённый пакет);
|
||
- **направление** выбирается как обратное относительно найденной записи;
|
||
- тип пейлоада ставится как `ipv4` или `ipv6`, тип протокола сеанса берётся из профиля исходного пакета;
|
||
- дальше ICMP проходит по профилю обычным образом.
|
||
|
||
> [!warning] ICMP без TCP/UDP, но с track
|
||
> В функцию может прийти диссект с `icmp`, **без** `tcp` или `udp`, но при этом **с** `desync.track` (взятым от исходной записи). Учитывайте это в коде — не считайте, что раз есть `track`, то есть и `tcp`.
|
||
|
||
Если ICMP не содержит прикреплённого пакета, он невалиден или запись conntrack не найдена — ICMP проходит самостоятельно, без `track`. Важно: conntrack ведёт учёт только для TCP и UDP; пинги и прочие ICMP он не отслеживает, и при проходе ICMP по записи conntrack **никакие счётчики не меняются**.
|
||
|
||
---
|
||
|
||
## Особенности обработки raw IP
|
||
|
||
Если IP-протокол не распознан как TCP, UDP, ICMP или ICMPv6, пакет считается **raw IP**. В диссекте тогда присутствуют поля `ip`/`ip6` и `payload`, где `payload` — это всё содержимое пакета после L3-заголовков. `desync.track` для raw IP **всегда отсутствует**.
|
||
|
||
---
|
||
|
||
## 📚 См. также
|
||
|
||
- [[desync|Механизм --lua-desync]] — как и зачем вызываются desync-функции (эта заметка — про их входные данные)
|
||
- [[жизненный цикл desync-функции|Жизненный цикл desync-функции]] — что функция делает с этими входными данными: восемь стадий, общих для всех техник дурения
|
||
- [[схема обработки трафика|Схема обработки трафика]] — где в конвейере пакета появляются `dis`, `track`, reasm и replay
|
||
- [[структура проекта|Структура проекта]] — почему логика в Lua, а разбор пакета и conntrack — в C-ядре
|
||
- [[multisplit]] — практический пример: как одна функция читает `desync` и возвращает вердикт
|
||
- [[payload|Тип payload]] — что означают `l7payload` и `l7proto`
|
||
- [[blob|Blob-данные]] — заготовки, на которые ссылаются аргументы `blob=…`
|
||
- 🔗 [Официальная документация (docs/manual.md)](https://github.com/bol-van/zapret2/blob/master/docs/manual.md) — первоисточник
|
||
|
||
---
|
||
|
||
> [!quote] 🤖 Эти статьи открыты — можно обучать на них ИИ
|
||
> При желании вы можете натренировать ИИ на наших статьях. Исходное форматирование и скачивание всего репозитория одним zip-архивом доступны в Forgejo: [исходник этой заметки](https://git.zapret.moe/zapretdiscordyoutube/todo/src/branch/main/Zapret2/%D1%81%D1%82%D1%80%D1%83%D0%BA%D1%82%D1%83%D1%80%D0%B0%20desync%20%D0%B8%20%D0%B4%D0%B8%D1%81%D1%81%D0%B5%D0%BA%D1%82%D0%B0.md) · [весь репозиторий](https://git.zapret.moe/zapretdiscordyoutube/todo/src/branch/main).
|