todo/Zapret2/wf.md
loop-uh 0ad0bcc8dd
Some checks failed
Published content check / validate (push) Failing after 5s
seo+зеркало: [!mirror]-плашка со ссылкой на вики и description во frontmatter всех заметок
Каждая публикуемая заметка получила callout-шапку со ссылкой на свою страницу
wiki.zapret.moe (на самой вики она вырезается транформером RemoveMirrorCallout,
видна только на зеркале Obsidian Publish и в Forgejo) и SEO-поле description —
1–2 предложения для meta description обоих сайтов.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-26 22:05:57 +03:00

702 lines
No EOL
24 KiB
Markdown
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
tags:
link:
aliases:
img:
description: "WinDivert-фильтры --wf-* в zapret2 (winws2): порядок фильтрации, синтаксис --wf-tcp/udp и --wf-raw-part, язык фильтров, примеры для QUIC, STUN и Discord."
---
> [!mirror] Резервное зеркало
> Актуальная версия этой страницы — на основной вики: [wiki.zapret.moe/Zapret2/wf](https://wiki.zapret.moe/Zapret2/wf)
## WinDivert фильтры (`--wf-*`) в [[zapret2]] для Windows
**WinDivert** — это драйвер для Windows, который позволяет перехватывать и модифицировать сетевые пакеты на уровне ядра. В zapret2 для Windows используется программа **winws2** (вместо nfqws2 для Linux), которая работает с WinDivert.
**WinDivert фильтр применяется ПЕРВЫМ** — это фундаментальная архитектура.
### Порядок фильтрации (pipeline):
```
┌─────────────────────────────────────────────────────────────────┐
│ СЕТЕВОЙ СТЕК
│ (все пакеты) │
└──────────────────────────┬──────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ 1⃣ WinDivert ФИЛЬТР (--wf-*) │
│ [уровень драйвера] │
│ │
│ Пропускает только пакеты, соответствующие фильтру │
│ Остальные идут мимо nfqws напрямую в сеть │
└──────────────────────────┬──────────────────────────────────────┘
│ только захваченные пакеты
┌─────────────────────────────────────────────────────────────────┐
│ nfqws │
│ │
│ 2⃣ --filter-tcp/udp — фильтр по портам │
│ 3⃣ --ipset — фильтр по IP │
│ 4⃣ --hostlist — фильтр по доменам (из payload) │
│ 5⃣ desync actions — применение обхода DPI │
└─────────────────────────────────────────────────────────────────┘
```
### Почему это важно:
- **Производительность**: WinDivert фильтрует на уровне драйвера — CPU-efficient
- **nfqws видит только нужные пакеты** — не тратит ресурсы на разбор всего трафика
- `--wf-*` = **грубый фильтр** (порты, протоколы, IP версия)
- `--hostlist` = **тонкий фильтр** (по содержимому пакета, доменам)
---
**Да, `--wf-*` применяется первым, на уровне драйвера, до того как пакет попадёт в nfqws.**
---
## 📦 **Основные параметры WinDivert фильтров**
### 1. **`--wf-iface`** - Фильтр по сетевому интерфейсу
**Синтаксис:**
```bash
--wf-iface=<int>[.<int>]
```
**Параметры:**
- `int` - индекс сетевого интерфейса
- `int.int` - индекс интерфейса и подинтерфейса
**Примеры:**
```bash
--wf-iface=1 # интерфейс 1
--wf-iface=2.0 # интерфейс 2, подинтерфейс 0
```
---
### 2. **`--wf-l3`** - Фильтр L3 протокола
**Синтаксис:**
```bash
--wf-l3=ipv4|ipv6
```
**Параметры:**
- `ipv4` - только IPv4
- `ipv6` - только IPv6
- Можно указывать несколько через запятую
**Примеры:**
```bash
--wf-l3=ipv4
--wf-l3=ipv4,ipv6
```
---
### 3. **`--wf-tcp-in`** - TCP входящие порты
**Синтаксис:**
```bash
--wf-tcp-in=[~]port1[-port2]
```
**Параметры:**
- `port` - конкретный порт
- `port1-port2` - диапазон портов
- `~port` - отрицание (все кроме)
- Можно указывать несколько через запятую
**Примеры:**
```bash
--wf-tcp-in=80,443 # входящие на порты 80 и 443
--wf-tcp-in=1000-2000 # диапазон
--wf-tcp-in=~443 # все кроме 443
```
---
### 4. **`--wf-tcp-out`** - TCP исходящие порты
**Синтаксис:**
```bash
--wf-tcp-out=[~]port1[-port2]
```
**Параметры:** (аналогично `--wf-tcp-in`)
**Примеры:**
```bash
--wf-tcp-out=80,443 # исходящие на порты 80 и 443
```
---
### 5. **`--wf-udp-in`** - UDP входящие порты
**Синтаксис:**
```bash
--wf-udp-in=[~]port1[-port2]
```
**Параметры:** (аналогично TCP)
**Примеры:**
```bash
--wf-udp-in=53,443 # DNS и QUIC входящие
```
---
### 6. **`--wf-udp-out`** - UDP исходящие порты
**Синтаксис:**
```bash
--wf-udp-out=[~]port1[-port2]
```
**Параметры:** (аналогично TCP)
**Примеры:**
```bash
--wf-udp-out=443 # QUIC исходящие
```
---
### 7. **`--wf-tcp-empty`** - Обработка пустых TCP пакетов
**Синтаксис:**
```bash
--wf-tcp-empty=[0|1]
```
**Параметры:**
- `0` - не обрабатывать пустые TCP пакеты без флагов SYN/RST/FIN (по умолчанию)
- `1` - обрабатывать пустые TCP пакеты
**Примеры:**
```bash
--wf-tcp-empty=1 # включить обработку пустых пакетов
```
---
### 8. **[[--wf-raw-part]]** - Частичный raw фильтр ⭐
**Синтаксис:**
```bash
--wf-raw-part=<filter>|@<filename>
```
**Параметры:**
- `filter` - строка фильтра на языке WinDivert
- `@filename` - загрузить фильтр из файла
- Можно указывать **несколько раз** - они объединяются через OR
**Особенности:**
- Фильтры работают на уровне ядра (очень быстро)
- Язык фильтров похож на tcpdump/Wireshark
- Позволяет фильтровать по содержимому payload
- Экономит CPU, не перенаправляя весь поток
**Примеры:**
```bash
# Прямо в командной строке
--wf-raw-part="outbound and udp.DstPort=443 and udp.PayloadLength>=256"
# Из файла
--wf-raw-part=@windivert_part.discord_media.txt
# Несколько фильтров
--wf-raw-part=@discord.txt --wf-raw-part=@stun.txt --wf-raw-part=@quic.txt
```
---
### 9. **`--wf-filter-lan`** - Фильтр локальных сетей
**Синтаксис:**
```bash
--wf-filter-lan=0|1
```
**Параметры:**
- `0` - не фильтровать локальные сети
- `1` - исключить локальные IP адреса (по умолчанию)
**Исключаемые диапазоны:**
- IPv4: 127.0.0.0/8, 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 169.254.0.0/16
- IPv6: ::1/128, fc00::/7, fe80::/10, ff00::/8
**Примеры:**
```bash
--wf-filter-lan=0 # не исключать локальные сети
```
---
### 10. **`--wf-raw`** - Полный raw фильтр
**Синтаксис:**
```bash
--wf-raw=<filter>|@<filename>
```
**Параметры:**
- `filter` - полная строка фильтра WinDivert
- `@filename` - загрузить из файла
- **ЗАМЕНЯЕТ** все `--wf-tcp`, `--wf-udp`, `--wf-raw-part`
**Примеры:**
```bash
--wf-raw="!impostor and !loopback and outbound and tcp.DstPort=443"
--wf-raw=@full_filter.txt
```
---
### 11. **`--wf-save`** - Сохранить фильтр и выйти
**Синтаксис:**
```bash
--wf-save=<filename>
```
**Параметры:**
- `filename` - путь для сохранения сгенерированного фильтра
- После сохранения программа завершается
**Примеры:**
```bash
--wf-tcp-out=80,443 --wf-save=generated_filter.txt
```
---
### 🔤 Язык фильтров WinDivert
Основные операторы:
Логические:
- and - И
- or - ИЛИ
- not или ! - НЕ
Сравнения:
- == или = - равно
- != - не равно
- <, >, <=, >= - сравнения
Направление:
- outbound - исходящие пакеты
- inbound - входящие пакеты
Специальные:
- impostor - пакеты от других драйверов
- loopback - loopback интерфейс
---
### Доступные поля:
#### **IP (IPv4):**
```
ip # IPv4 пакет
ip.SrcAddr # IP источника
ip.DstAddr # IP назначения
ip.Length # Длина IP пакета
ip.TTL # Time To Live
ip.Protocol # Протокол (6=TCP, 17=UDP)
```
#### **IPv6:**
```
ipv6 # IPv6 пакет
ipv6.SrcAddr # IPv6 источника
ipv6.DstAddr # IPv6 назначения
ipv6.Length # Длина пакета
ipv6.HopLimit # Hop Limit
ipv6.NextHdr # Следующий заголовок
```
#### **TCP:**
```
tcp # TCP пакет
tcp.SrcPort # Порт источника
tcp.DstPort # Порт назначения
tcp.Syn # SYN флаг
tcp.Rst # RST флаг
tcp.Fin # FIN флаг
tcp.Ack # ACK флаг
tcp.Psh # PSH флаг
tcp.PayloadLength # Длина payload
tcp.Payload[N] # Байт payload по индексу N
tcp.Payload16[N] # 16-bit значение (little-endian)
tcp.Payload32[N] # 32-bit значение (little-endian)
```
#### **UDP:**
```
udp # UDP пакет
udp.SrcPort # Порт источника
udp.DstPort # Порт назначения
udp.PayloadLength # Длина payload
udp.Payload[N] # Байт payload по индексу N
udp.Payload16[N] # 16-bit значение (little-endian)
udp.Payload32[N] # 32-bit значение (little-endian)
```
#### **Интерфейс:**
```
ifIdx # Индекс интерфейса
subIfIdx # Индекс подинтерфейса
```
---
## 📝 **Примеры фильтров**
### Пример 1: QUIC Initial (IETF)
```
outbound and
udp.PayloadLength>=256 and
udp.Payload[0]>=0xC0 and udp.Payload[0]<0xD0 and
udp.Payload[1]=0 and udp.Payload16[1]=0 and udp.Payload[4]=1
```
### Пример 2: STUN
```
outbound and
udp.PayloadLength>=20 and
udp.Payload32[1]=0x2112A442 and udp.Payload[0]<0x40
```
### Пример 3: WireGuard
```
outbound and
udp.PayloadLength=148 and
udp.Payload[0]=0x01
```
### Пример 4: Discord Media
```
outbound and ip and
udp.DstPort>=50000 and udp.DstPort<=50099 and
udp.PayloadLength=74 and
udp.Payload32[0]=0x00010046 and
udp.Payload32[2]=0 and
udp.Payload32[3]=0
```
### Пример 5: TLS Client Hello
```
outbound and
tcp.PayloadLength>=5 and
tcp.Payload[0]=0x16 and
tcp.Payload[1]=0x03 and
tcp.Payload[2]>=0x01 and tcp.Payload[2]<=0x03
```
### Пример 6: HTTP Redirect (302/307)
```
tcp.PayloadLength>=12 and
tcp.Payload32[0]==0x48545450 and
tcp.Payload16[2]==0x2F31 and
tcp.Payload[6]==0x2E and
tcp.Payload16[4]==0x2033 and
tcp.Payload[10]==0x30 and
(tcp.Payload[11]==0x32 or tcp.Payload[11]==0x37)
```
---
## 💡 **Полные примеры использования**
### Пример 1: Базовый перехват HTTP/HTTPS
```bash
winws2 ^
--wf-tcp-out=80,443 ^
--lua-init=@zapret-lib.lua --lua-init=@zapret-antidpi.lua ^
--filter-tcp=80,443 --filter-l7=http,tls ^
--lua-desync=fake:blob=fake_default_tls
```
### Пример 2: С частичными фильтрами
```bash
winws2 ^
--wf-tcp-out=80,443 ^
--wf-raw-part=@windivert_part.discord_media.txt ^
--wf-raw-part=@windivert_part.stun.txt ^
--wf-raw-part=@windivert_part.quic_initial_ietf.txt ^
--lua-init=@zapret-lib.lua --lua-init=@zapret-antidpi.lua ^
--filter-tcp=80,443 --filter-l7=http,tls ^
--lua-desync=fake:blob=fake_default_tls ^
--new ^
--filter-l7=quic,stun,discord ^
--lua-desync=fake:blob=fake_default_quic:repeats=2
```
### Пример 3: Только QUIC с фильтром
```bash
winws2 ^
--wf-raw-part=@windivert_part.quic_initial_ietf.txt ^
--lua-init=@zapret-lib.lua --lua-init=@zapret-antidpi.lua ^
--filter-l7=quic ^
--lua-desync=fake:blob=fake_default_quic:repeats=6
```
### Пример 4: Полный raw фильтр
```bash
winws2 ^
--wf-raw="!impostor and !loopback and outbound and tcp.DstPort=443 and tcp.PayloadLength>0" ^
--lua-init=@zapret-lib.lua --lua-init=@zapret-antidpi.lua ^
--filter-tcp=443 ^
--lua-desync=fake:blob=fake_default_tls
```
---
## ⚠️ **Важные особенности и ограничения**
### 1. **Отсутствие conntrack**
- WinDivert НЕ отслеживает соединения (нет аналога connbytes из iptables)
- Если перехватывать порт целиком, весь трафик идет через winws2
- Это нагружает процессор при передаче больших объемов данных
### 2. **Ограничения языка фильтров**
- Нет операций с битовыми полями
- Нет сдвигов (shift)
- Нет побитовой логики (AND, OR, XOR)
- Фильтры могут пропускать неправильные пакеты
- Дофильтрация выполняется в winws2
### 3. **Рекомендации**
- Используйте `--wf-raw-part` для точной фильтрации payload
- Это экономит CPU, отсекая ненужный трафик на уровне ядра
- Комбинируйте несколько `--wf-raw-part` для разных протоколов
### 4. **Автоматическая генерация**
- winws2 автоматически добавляет `!impostor and !loopback`
- Автоматически добавляет фильтр для SYN/RST/FIN (для conntrack)
- Автоматически добавляет фильтр HTTP redirect (302/307)
- Автоматически исключает локальные сети (если `--wf-filter-lan=1`)
---
## 📚 **Документация WinDivert**
Полная документация языка фильтров:
https://reqrypt.org/windivert-doc.html#filter_language
---
## 🎯 **Резюме**
**WinDivert фильтры** — это мощный механизм для перехвата пакетов в Windows на уровне ядра:
- **`--wf-tcp-in/out`**, **`--wf-udp-in/out`** — простая фильтрация по портам
- **`--wf-raw-part`** — частичные фильтры по содержимому payload (рекомендуется!)
- **`--wf-raw`** — полный контроль над фильтром
- **Язык фильтров** — похож на tcpdump, но с ограничениями
- **Производительность** — фильтры работают в ядре, очень быстро
Используйте `--wf-raw-part` для оптимальной производительности и точной фильтрации!
## Разница между `--wf-tcp-out=80,443` и `--wf-tcp-out=80 --wf-tcp-out=443`
### 🔍 **Анализ кода**
Изучив функцию `wf_make_pf` в коде (строки 1193-1222), я могу точно объяснить разницу:
---
## ✅ **Вариант 1: `--wf-tcp-out=80,443` (ПРАВИЛЬНЫЙ)**
**Что происходит:**
1. Параметр обрабатывается **один раз**
2. Функция `wf_make_pf` парсит строку `"80,443"` и разбивает по запятой
3. Генерируется **один** фильтр WinDivert:
```
((tcp.DstPort == 80) or (tcp.DstPort == 443))
```
**Результат:**
- ✅ Один вызов парсера
- ✅ Оптимальный фильтр
- ✅ Меньше строковых операций
---
## ❌ **Вариант 2: `--wf-tcp-out=80 --wf-tcp-out=443` (НЕПРАВИЛЬНЫЙ)**
**Что происходит:**
1. Первый параметр `--wf-tcp-out=80` обрабатывается
2. Генерируется фильтр: `((tcp.DstPort == 80))`
3. **Второй параметр ПЕРЕЗАПИСЫВАЕТ первый!**
4. Генерируется новый фильтр: `((tcp.DstPort == 443))`
**Результат:**
- ❌ **Первый порт (80) теряется!**
- ❌ Работает только последний указанный порт (443)
- ❌ Два вызова парсера (лишняя работа)
---
## 📊 **Доказательство из кода**
Посмотрим на обработку параметра (строки 2330-2337):
```c
case IDX_WF_TCP_OUT:
hash_wf_tcp_out = hash_jen(optarg, strlen(optarg));
if (!wf_make_pf(optarg, "tcp", "SrcPort", params.wf_pf_tcp_src_out, WINDIVERT_PORTFILTER_MAX) ||
!wf_make_pf(optarg, "tcp", "DstPort", params.wf_pf_tcp_dst_out, WINDIVERT_PORTFILTER_MAX))
{
DLOG_ERR("bad value for --wf-tcp-out\n");
exit_clean(1);
}
break;
```
**Ключевой момент:** Каждый вызов `--wf-tcp-out` **перезаписывает** буферы `params.wf_pf_tcp_src_out` и `params.wf_pf_tcp_dst_out`, а не добавляет к ним!
---
## 🔬 **Как работает `wf_make_pf`**
```c
static bool wf_make_pf(char *opt, const char *l4, const char *portname, char *buf, size_t len)
{
// ...
for (n = 0, p = opt, *buf = '(', buf[1] = 0; p; n++) // Начинает с '('
{
if ((e = strchr(p, ','))) // Ищет запятую
{
c = *e;
*e = 0;
}
if (!pf_parse(p, &pf)) return false;
// Генерирует условие для порта
if (pf.from == pf.to)
snprintf(s1, sizeof(s1), "(%s.%s %s %u)", l4, portname, pf.neg ? "!=" : "==", pf.from);
if (n) strncat(buf, " or ", len - strlen(buf) - 1); // Добавляет "or" между портами
strncat(buf, s1, len - strlen(buf) - 1);
// ...
}
strncat(buf, ")", len - strlen(buf) - 1); // Закрывает скобку
return true;
}
```
**Функция:**
1. Парсит строку по запятым
2. Для каждого порта генерирует условие
3. Объединяет их через `or`
4. Возвращает один готовый фильтр
---
## 📈 **Производительность**
### Вариант 1: `--wf-tcp-out=80,443`
**Время парсинга:**
- 1 вызов `wf_make_pf`
- 2 итерации цикла внутри функции
- 1 выделение строки
**Итоговый фильтр:**
```
((tcp.DstPort == 80) or (tcp.DstPort == 443))
```
**Производительность WinDivert:** ⚡ **Оптимальная**
- Один компактный фильтр
- Эффективная проверка в ядре
---
### Вариант 2: `--wf-tcp-out=80 --wf-tcp-out=443`
**Время парсинга:**
- 2 вызова `wf_make_pf`
- 2 выделения строк
- **Первый результат выбрасывается!**
**Итоговый фильтр:**
```
((tcp.DstPort == 443)) // Порт 80 потерян!
```
**Производительность WinDivert:** ❌ **Неправильная работа**
- Фильтрует только последний порт
- Порт 80 не перехватывается!
---
## 🎯 **Вывод**
### ✅ **Используйте:**
```bash
--wf-tcp-out=80,443
```
**Преимущества:**
1. ✅ **Работает правильно** - оба порта перехватываются
2. ✅ **Быстрее парсинг** - один вызов вместо двух
3. ✅ **Меньше памяти** - одна строка вместо двух
4. ✅ **Читабельнее** - короче командная строка
---
### ❌ **НЕ используйте:**
```bash
--wf-tcp-out=80 --wf-tcp-out=443
```
**Проблемы:**
1. ❌ **Не работает!** - только последний порт (443)
2. ❌ **Медленнее** - лишний вызов функции
3. ❌ **Больше кода** - длиннее командная строка
4. ❌ **Теряются данные** - первый порт (80) игнорируется
---
## 📋 **Правильные примеры**
```bash
# Правильно: несколько портов через запятую
--wf-tcp-out=80,443
--wf-tcp-out=80,443,8080,8443
--wf-udp-out=53,443,853
# Правильно: диапазоны
--wf-tcp-out=1000-2000
--wf-tcp-out=80,443,8000-9000
# Правильно: отрицание
--wf-tcp-out=~443
--wf-tcp-out=~80,~443
# НЕПРАВИЛЬНО: повторяющиеся параметры
--wf-tcp-out=80 --wf-tcp-out=443 # ❌ Работает только 443!
```
---
## 🔧 **Техническое объяснение**
Параметры командной строки в `getopt_long` обрабатываются **последовательно**. Когда встречается один и тот же параметр дважды, второй вызов **перезаписывает** результат первого, потому что они записываются в **один и тот же буфер** (`params.wf_pf_tcp_dst_out`).
Это не баг, а особенность дизайна - параметры WinDivert фильтров не предназначены для множественного указания. Вместо этого используется **встроенная поддержка списков через запятую**.
---
**Итог:** Всегда используйте запятые для перечисления портов в одном параметре. Это правильно, быстрее и работает как задумано!