## 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
# Несколько фильтров
[email protected] [email protected] [email protected]
```
---
### 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 ^
[email protected] [email protected] ^
--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 ^
[email protected] [email protected] ^
--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 ^
[email protected] [email protected] ^
--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" ^
[email protected] [email protected] ^
--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 фильтров не предназначены для множественного указания. Вместо этого используется **встроенная поддержка списков через запятую**.
---
**Итог:** Всегда используйте запятые для перечисления портов в одном параметре. Это правильно, быстрее и работает как задумано!