---
tags:
link:
aliases:
img:
---

## 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 фильтров не предназначены для множественного указания. Вместо этого используется **встроенная поддержка списков через запятую**.

---

**Итог:** Всегда используйте запятые для перечисления портов в одном параметре. Это правильно, быстрее и работает как задумано!