Template
1
0
mirror of https://github.com/bol-van/zapret2.git synced 2026-03-17 07:09:44 +00:00

66 Commits
v0.7 ... v0.7.1

Author SHA1 Message Date
bol-van
0aac2965c1 nfqws2: minor reorder struct members 2025-12-13 22:13:04 +03:00
bol-van
d1128a8bc6 update docs 2025-12-13 20:56:17 +03:00
bol-van
e016fc0e42 update docs 2025-12-13 20:55:50 +03:00
bol-van
f48ea2f6a7 update docs 2025-12-13 20:39:03 +03:00
bol-van
2ab71ab895 update docs 2025-12-13 20:19:02 +03:00
bol-van
736e0ba3d4 update docs 2025-12-13 20:16:13 +03:00
bol-van
f2ae880c11 update docs 2025-12-13 20:15:05 +03:00
bol-van
019f3089c6 update docs 2025-12-13 20:09:12 +03:00
bol-van
30d28488c9 update docs 2025-12-13 19:53:54 +03:00
bol-van
5bcec4aada update docs 2025-12-13 19:50:16 +03:00
bol-van
886fbabcfc update docs 2025-12-13 17:00:17 +03:00
bol-van
cd8dbf2a2b update docs 2025-12-13 16:59:05 +03:00
bol-van
002742bd03 update docs 2025-12-13 16:56:45 +03:00
bol-van
dc2c707c3c update docs 2025-12-13 16:55:22 +03:00
bol-van
9630d0a9df update docs 2025-12-13 16:54:54 +03:00
bol-van
f4c4d5e558 update docs 2025-12-13 16:43:11 +03:00
bol-van
7b37880954 update docs 2025-12-13 16:39:53 +03:00
bol-van
6b7738ac16 update docs 2025-12-13 16:38:46 +03:00
bol-van
8dec014b50 update docs 2025-12-13 16:36:46 +03:00
bol-van
b0ee32f3dc update docs 2025-12-13 16:36:21 +03:00
bol-van
0e770ff46d update docs 2025-12-13 16:35:52 +03:00
bol-van
14b3aef030 update docs 2025-12-13 16:33:05 +03:00
bol-van
004c583595 update docs 2025-12-13 16:31:18 +03:00
bol-van
c4818a6a32 nfqws2: solved inability to get SSID using nl80211 on kernels 5.19+ 2025-12-13 15:33:57 +03:00
bol-van
58d57fed01 update docs 2025-12-13 11:27:16 +03:00
bol-van
d6b73fe7e0 update docs 2025-12-13 11:26:58 +03:00
bol-van
4867838fce update docs 2025-12-13 11:24:35 +03:00
bol-van
4b2551509f update docs 2025-12-13 11:22:16 +03:00
bol-van
ed6acb36a1 nfqws2: update docs 2025-12-12 23:45:32 +03:00
bol-van
26b80e80b6 nfqws2: update docs 2025-12-12 23:43:47 +03:00
bol-van
79b776b5a9 nfqws2: update docs 2025-12-12 23:42:27 +03:00
bol-van
3b251b9ee6 nfqws2: update docs 2025-12-12 23:41:43 +03:00
bol-van
8c65a966d9 nfqws2: update docs 2025-12-12 23:40:58 +03:00
bol-van
9da0b13aa3 nfqws2: update docs 2025-12-12 23:39:18 +03:00
bol-van
d7fd491121 nfqws2: update docs 2025-12-12 23:38:09 +03:00
bol-van
c60ef399ec nfqws2: update docs 2025-12-12 23:36:15 +03:00
bol-van
2abab21e4b nfqws2: update docs 2025-12-12 23:31:17 +03:00
bol-van
6190babb99 nfqws2: update docs 2025-12-12 23:29:55 +03:00
bol-van
7ce0b4a996 nfqws2: reduce default retrans maxseq to 32768, adjust config 2025-12-12 23:28:49 +03:00
bol-van
053556fe2d nfqws2: autohostlist reset fail counter if udp_in > threshold 2025-12-12 23:11:11 +03:00
bol-van
52571045fe nfqws2: add EOL at the end of truncated buffered DLOG line if it's too large. increase log line buffer 2025-12-12 20:37:58 +03:00
bol-van
db875ed1d4 nfqws2: cancel reasm if server window size is smaller than expected reasm size 2025-12-12 20:20:12 +03:00
bol-van
e828864811 nfqws2: cancel reasm if server window size is smaller than expected reasm size 2025-12-12 20:18:55 +03:00
bol-van
4404127fa3 update docs 2025-12-12 18:26:30 +03:00
bol-van
13e81e4b6f update docs 2025-12-12 18:25:45 +03:00
bol-van
a631add2d9 update docs 2025-12-12 18:24:34 +03:00
bol-van
26b9b63a20 update docs 2025-12-12 18:21:35 +03:00
bol-van
90489fad2f update docs 2025-12-12 18:21:17 +03:00
bol-van
d93c243d21 update docs 2025-12-12 18:20:46 +03:00
bol-van
65235d71d7 update docs 2025-12-12 18:19:53 +03:00
bol-van
fc01e6715f update docs 2025-12-12 18:04:18 +03:00
bol-van
1a33d68998 update docs 2025-12-12 18:00:57 +03:00
bol-van
dfaa475d2a update docs 2025-12-12 17:50:42 +03:00
bol-van
743018423a update docs 2025-12-12 17:47:15 +03:00
bol-van
762023f201 update docs 2025-12-12 16:45:16 +03:00
bol-van
a296b93b7a update docs 2025-12-12 16:44:17 +03:00
bol-van
1c9b3aa1bc update docs 2025-12-12 16:40:02 +03:00
bol-van
565fa8e337 init.d: fix non-working incoming redirect 2025-12-12 16:09:31 +03:00
bol-van
9fcecd07d1 update docs 2025-12-12 12:19:04 +03:00
bol-van
652e271877 update docs 2025-12-12 12:06:55 +03:00
bol-van
fc7ed4f4a8 update docs 2025-12-12 12:04:57 +03:00
bol-van
e9e5bdc860 update docs 2025-12-12 12:04:31 +03:00
bol-van
a2b8300219 update docs 2025-12-12 12:03:45 +03:00
bol-van
dfdcfbdf51 update docs 2025-12-12 12:02:12 +03:00
bol-van
170ec372fb update docs 2025-12-12 12:00:53 +03:00
bol-van
3f073908a6 update docs 2025-12-12 11:59:17 +03:00
10 changed files with 880 additions and 56 deletions

View File

@@ -97,17 +97,19 @@ nft_activate_chain4()
{
# $1 - chain name
# $2 - saddr/daddr
local b rule markf= act
local b rule markf= act flt_ifname
[ "$DISABLE_IPV4" = "1" ] || {
eval act="\$${1}_act4"
[ -n "$act" ] && return
b=0
nft_wanif_filter_present && b=1
flt_ifname="oifname"
starts_with "$1" pre && flt_ifname="iifname"
[ "$2" = daddr ] && markf=$(nft_mark_filter)
rule="meta mark and $DESYNC_MARK == 0 $markf"
[ $b = 1 ] && rule="$rule oifname @wanif"
[ $b = 1 ] && rule="$rule $flt_ifname @wanif"
rule="$rule ip $2 != @nozapret jump $1"
nft_rule_exists ${1}_hook "$rule" || nft_add_rule ${1}_hook $rule
@@ -118,17 +120,19 @@ nft_activate_chain6()
{
# $1 - chain name
# $2 - saddr/daddr
local b rule markf=
local b rule markf= act flt_ifname
[ "$DISABLE_IPV6" = "1" ] || {
eval act="\$${1}_act6"
[ -n "$act" ] && return
b=0
nft_wanif6_filter_present && b=1
flt_ifname="oifname"
starts_with "$1" pre && flt_ifname="iifname"
[ "$2" = daddr ] && markf=$(nft_mark_filter)
rule="meta mark and $DESYNC_MARK == 0 $markf"
[ $b = 1 ] && rule="$rule oifname @wanif6"
[ $b = 1 ] && rule="$rule $flt_ifname @wanif6"
rule="$rule ip6 $2 != @nozapret6 jump $1"
nft_rule_exists ${1}_hook "$rule" || nft_add_rule ${1}_hook $rule

View File

@@ -26,8 +26,10 @@ IPSET_OPT="hashsize 262144 maxelem $SET_MAXELEM"
IP2NET_OPT4="--prefix-length=22-30 --v4-threshold=3/4"
IP2NET_OPT6="--prefix-length=56-64 --v6-threshold=5"
# options for auto hostlist
# NOTE : in order for these adjustment to work it's required to redirect enough starting packets
# NOTE : set PKT_IN, PKT_OUT variables appropriately
AUTOHOSTLIST_INCOMING_MAXSEQ=4096
AUTOHOSTLIST_RETRANS_MAXSEQ=65536
AUTOHOSTLIST_RETRANS_MAXSEQ=32768
AUTOHOSTLIST_RETRANS_THRESHOLD=3
AUTOHOSTLIST_FAIL_THRESHOLD=3
AUTOHOSTLIST_FAIL_TIME=60
@@ -64,11 +66,10 @@ NFQWS2_PORTS_TCP=80,443
NFQWS2_PORTS_UDP=443
# PKT_OUT means connbytes dir original
# PKT_IN means connbytes dir reply
# this is --dpi-desync-cutoff=nX kernel mode implementation for linux. it saves a lot of CPU.
NFQWS2_TCP_PKT_OUT=$((6+$AUTOHOSTLIST_RETRANS_THRESHOLD))
NFQWS2_TCP_PKT_IN=3
NFQWS2_UDP_PKT_OUT=$((6+$AUTOHOSTLIST_RETRANS_THRESHOLD))
NFQWS2_UDP_PKT_IN=0
NFQWS2_TCP_PKT_OUT=20
NFQWS2_TCP_PKT_IN=10
NFQWS2_UDP_PKT_OUT=5
NFQWS2_UDP_PKT_IN=3
# redirect outgoing traffic without connbytes limiter and incoming with connbytes limiter
# normally it's needed only for stateless DPI that matches every packet in a single TCP session
# typical example are plain HTTP keep alives

View File

@@ -90,3 +90,12 @@ v0.7
* nfqws2: push desync.track.pos.dt as float with nsec accuracy
* zapret-auto: override host autostate key in automate_host_record
* nfqws2: rewrite udp autohostlist failure detector logic
v0.7.1
* init.d: fix non-working incoming redirect
* nfqws2: cancel reasm if server window size is smaller than expected reasm size
* nfqws2: add EOL at the end of truncated buffered DLOG line if it's too large. increase log line buffer
* nfqws2: autohostlist reset fail counter if udp_in > threshold
* nfqws2: reduced default retrans maxseq to 32768
* nfqws2: solved inability to get SSID using nl80211 on kernels 5.19+

View File

@@ -111,3 +111,712 @@ LUA инстанс может сам себя отключить от получ
После выполнения всей цепочки инстансов профиля C код получает итоговый вердикт - что делать с текущим диссектом. Отправить как есть, отправить модифицированный вариант или дропнуть.
В конце nfqws2 переходит к ожиданию следующего пакета, и цикл повторяется вновь.
# Перехват трафика из ядра ОС
## Перехват трафика в ядре Linux
Осуществляется при помощи iptables или nftables с использованием механизма очередей NFQUEUE.
nftables - предпочтительны, потому что позволяют работать с трафиком после NAT, а iptables - нет.
Это важно при обработке проходящего (forwarded) трафика. На iptables перехват после NAT невозможен, поэтому некоторые воздействия, ломающие NAT,
на iptables на проходящем трафике нереализуемы.
У nftables есть один важный недостаток - чрезмерное требование к памяти при загрузке
больших set-ов. Например, чтобы загрузить 100K IP адресов, требуется от 256-320 Mb, что для роутеров часто оказывается за пределом возможностей.
ipset от iptables такое может провернуть даже на 64 Mb RAM.
Приведенные далее тестовые примеры предназначены для своей системы запуска или запуска вручную.
Скрипты запуска zapret сами генерируют необходимые правила, никаких ip/nf tables самому писать не нужно.
### Перехват трафика с помощью nftables
Тестовая таблица для POSTNAT схемы.
Обеспечивает перехват первых входящих и исходящих пакетов по потоку после NAT, если таковой присутствует.
Из-за NAT IP адреса клиентов теряются, замещаясь IP wan интерфейса.
Количество первых пакетов регулируется согласно вашей стратегии. Лишний перехват - дополнительная нагрузка на CPU.
Перехват RST и FIN желателен для максимально корректной работы conntrack.
Фильтр по mark необходим для предотвращения кольца. Без этого возможны зависания и неправильная работа.
notrack нужен, чтобы NAT не ломал техники, которые не совместимы с NAT.
Генерируемые nfqws2 пакеты не должны проходить проверки на валидность с точки зрения NAT и дропаться стандартными правилами таблиц.
Подстановка IP адресов NAT не требуется, поскольку попадающий на nfqws2 пакет уже прошел NAT и имеет корректные адрес и порт источника для wan.
```
IFACE_WAN=wan
MAX_PKT_IN=15
MAX_PKT_OUT=15
FWMARK=0x40000000
PORTS_TCP=80,443
PORTS_UDP=443
QNUM=200
nft create table inet ztest
nft add chain inet ztest postnat "{type filter hook postrouting priority srcnat+1;}"
nft add rule inet ztest postnat oifname $IFACE_WAN meta mark and $FWMARK == 0 udp dport "{$PORTS_UDP}" ct original packets 1-$MAX_PKT_OUT queue num $QNUM bypass
nft add rule inet ztest postnat oifname $IFACE_WAN meta mark and $FWMARK == 0 tcp dport "{$PORTS_TCP}" ct original packets 1-$MAX_PKT_OUT queue num $QNUM bypass
nft add rule inet ztest postnat oifname $IFACE_WAN meta mark and $FWMARK == 0 tcp dport "{$PORTS_TCP}" tcp flags fin,rst queue num $QNUM bypass
nft add chain inet ztest pre "{type filter hook prerouting priority filter;}"
nft add rule inet ztest pre iifname $IFACE_WAN udp sport "{$PORTS_UDP}" ct reply packets 1-$MAX_PKT_IN queue num $QNUM bypass
nft add rule inet ztest pre iifname $IFACE_WAN tcp sport "{$PORTS_TCP}" ct reply packets 1-$MAX_PKT_IN queue num $QNUM bypass
nft add rule inet ztest pre iifname $IFACE_WAN tcp sport "{$PORTS_TCP}" "tcp flags & (syn | ack) == (syn | ack)" queue num $QNUM bypass
nft add rule inet ztest pre iifname $IFACE_WAN tcp sport "{$PORTS_TCP}" tcp flags fin,rst queue num $QNUM bypass
nft add chain inet ztest predefrag "{type filter hook output priority -401;}"
nft add rule inet ztest predefrag "mark & $FWMARK != 0x00000000 notrack"
```
Удаление тестовой таблицы :
```
nft delete table inet ztest
```
### Перехват трафика с помощью iptables
> [!CAUTION]
> Начиная с ядер Linux 6.17 присутствует параметр конфигурации ядра CONFIG_NETFILTER_XTABLES_LEGACY, который по умолчанию в дистрибутиве может быть "not set". Отсутствие этой настройки выключает iptables-legacy. Это часть процесса депрекации iptables. Тем не менее iptables-nft будут работать, поскольку используют backend nftables.
Тестовые правила для PRENAT схемы.
Обеспечивают перехват первых входящих и исходящих пакетов по потоку до NAT, если таковой присутствует.
Адреса и порты источника внутренней сети сохраняются. Атаки на проходящий трафик, ломающие NAT, невозможны, но возможны с самой системы.
```
IFACE_WAN=wan
MAX_PKT_IN=15
MAX_PKT_OUT=15
FWMARK=0x40000000
PORTS_TCP=80,443
PORTS_UDP=443
QNUM=200
JNFQ="-j NFQUEUE --queue-num $QNUM --queue-bypass"
CHECKMARK="-m mark ! --mark $FWMARK/$FWMARK"
CB_ORIG="-m connbytes --connbytes-dir=original --connbytes-mode=packets"
CB_REPLY="-m connbytes --connbytes-dir=reply --connbytes-mode=packets"
for tables in iptables ip6tables; do
$tables -t mangle -N ztest_post 2>/dev/null
$tables -t mangle -F ztest_post
$tables -t mangle -C POSTROUTING -j ztest_post 2>/dev/null || $tables -t mangle -A POSTROUTING -j ztest_post
$tables -t mangle -N ztest_pre 2>/dev/null
$tables -t mangle -F ztest_pre
$tables -t mangle -C PREROUTING -j ztest_pre 2>/dev/null || $tables -t mangle -A PREROUTING -j ztest_pre
$tables -t mangle -I ztest_post -o $IFACE_WAN $CHECKMARK -p udp -m multiport --dports $PORTS_UDP $CB_ORIG --connbytes 1:$MAX_PKT_OUT $JNFQ
$tables -t mangle -I ztest_post -o $IFACE_WAN $CHECKMARK -p tcp -m multiport --dports $PORTS_TCP $CB_ORIG --connbytes 1:$MAX_PKT_OUT $JNFQ
$tables -t mangle -I ztest_post -o $IFACE_WAN $CHECKMARK -p tcp -m multiport --dports $PORTS_TCP --tcp-flags fin fin $JNFQ
$tables -t mangle -I ztest_post -o $IFACE_WAN $CHECKMARK -p tcp -m multiport --dports $PORTS_TCP --tcp-flags rst rst $JNFQ
$tables -t mangle -I ztest_pre -i $IFACE_WAN -p udp -m multiport --sports $PORTS_UDP $CB_REPLY --connbytes 1:$MAX_PKT_IN $JNFQ
$tables -t mangle -I ztest_pre -i $IFACE_WAN -p tcp -m multiport --sports $PORTS_TCP $CB_REPLY --connbytes 1:$MAX_PKT_IN $JNFQ
$tables -t mangle -I ztest_pre -i $IFACE_WAN -p tcp -m multiport --sports $PORTS_TCP --tcp-flags syn,ack syn,ack $JNFQ
$tables -t mangle -I ztest_pre -i $IFACE_WAN -p tcp -m multiport --sports $PORTS_TCP --tcp-flags fin fin $JNFQ
$tables -t mangle -I ztest_pre -i $IFACE_WAN -p tcp -m multiport --sports $PORTS_TCP --tcp-flags rst rst $JNFQ
done
```
Удаление тестовых правил zapret :
```
for tables in iptables ip6tables; do
$tables -t mangle -D POSTROUTING -j ztest_post
$tables -t mangle -D PREROUTING -j ztest_pre
$tables -t mangle -F ztest_post
$tables -t mangle -X ztest_post
$tables -t mangle -F ztest_pre
$tables -t mangle -X ztest_pre
done
```
Удаление всех правил из таблицы mangle, включая и иные правила :
```
iptables -F -t mangle
ip6tables -F -t mangle
```
## Перехват трафика в ядре FreeBSD
Основная боль при перехвате трафика на системах, отличных от Linux, - невозможность перехватить первые пакеты потока.
Можно перехватить только весь поток целиком по направлению.
В BSD с этим дела хуже всего - нет даже возможностей фильтрации raw payload, то есть по содержимому пакета.
Поэтому первый набор правил - это перехват всех исходящих по портам и перехват только SYN+ACK,FIN,RST для TCP, чтобы
без нагрузки на процессор можно было задействовать режим autottl и максимально корректно работал conntrack.
Однако, в таком варианте не будет работать ничего, что требует иного входящего трафика.
```
RULE=100
IFACE_WAN=vmx0
PORTS_TCP=80,443
PORTS_UDP=443
PORT_DIVERT=989
ipfw delete $RULE
ipfw add $RULE divert $PORT_DIVERT tcp from any to any $PORTS_TCP out not diverted xmit $IFACE_WAN
ipfw add $RULE divert $PORT_DIVERT udp from any to any $PORTS_UDP out not diverted xmit $IFACE_WAN
ipfw add $RULE divert $PORT_DIVERT tcp from any $PORTS_TCP to any tcpflags syn,ack in not diverted recv $IFACE_WAN
ipfw add $RULE divert $PORT_DIVERT tcp from any $PORTS_TCP to any tcpflags fin in not diverted recv $IFACE_WAN
ipfw add $RULE divert $PORT_DIVERT tcp from any $PORTS_TCP to any tcpflags rst in not diverted recv $IFACE_WAN
```
Вариант с перехватом потока в обе стороны. Особо сильно загружает процессор.
Все скачиваемые гигабайты пойдут через dvtws2. Из них обычно нужно всего 1-2 пакета, все остальное - впустую расходует CPU,
но средства ipfw не предоставляют иных возможностей.
```
RULE=100
IFACE_WAN=vmx0
PORTS_TCP=80,443
PORTS_UDP=443
PORT_DIVERT=989
ipfw delete $RULE
ipfw add $RULE divert $PORT_DIVERT tcp from any to any $PORTS_TCP out not diverted xmit $IFACE_WAN
ipfw add $RULE divert $PORT_DIVERT udp from any to any $PORTS_UDP out not diverted xmit $IFACE_WAN
ipfw add $RULE divert $PORT_DIVERT tcp from any $PORTS_TCP to any in not diverted recv $IFACE_WAN
ipfw add $RULE divert $PORT_DIVERT udp from any $PORTS_UDP to any in not diverted recv $IFACE_WAN
```
Теоретически возможен перехват через pf divert-to, но на практике механизм предотвращения зацикливания сломан,
поэтому использовать pf нереально.
На pfsense и opnsense требуются дополнительные меры для задействования pf и ipfw одновременно.
Часто с этим бывают проблемы, конфликты и глюки.
## Перехват трафика в ядре OpenBSD
В OpenBSD есть только pf. С механизмом предотвращения зацикливания divert все в порядке.
Первый вариант - перехват всех исходящих по портам и перехват только SYN+ACK,FIN,RST для TCP, чтобы
без нагрузки на процессор можно было задействовать режим autottl и максимально корректно работал conntrack.
Однако, в таком варианте не будет работать ничего, что требует иного входящего трафика.
pf требует файлов с правилами. Вы пишите pf файл (обычно используется /etc/pf.conf), потом его применяете через
`pfctl -f /etc/pf.conf`. `pfctl -e` включает pf, `pfctl -d` - выключает.
Возможно использование якорей (anchors) с отдельными файлами правил, читайте документацию по pf.
Трюки с no state нужны, чтобы предотвратить автоматический перехват и входящих пакетов по потоку.
```
IFACE_WAN = "em0"
PORTS_TCP = "80,443"
PORTS_UDP = "443"
PORT_DIVERT = "989"
pass in quick on $IFACE_WAN proto tcp from port { $PORTS_TCP } flags SA/SA divert-packet port $PORT_DIVERT no state
pass in quick on $IFACE_WAN proto tcp from port { $PORTS_TCP } flags R/R divert-packet port $PORT_DIVERT no state
pass in quick on $IFACE_WAN proto tcp from port { $PORTS_TCP } flags F/F divert-packet port $PORT_DIVERT no state
pass in quick on $IFACE_WAN proto tcp from port { $PORTS_TCP } no state
pass out quick on $IFACE_WAN proto tcp to port { $PORTS_TCP } divert-packet port $PORT_DIVERT no state
pass out quick on $IFACE_WAN proto udp to port { $PORTS_UDP } divert-packet port $PORT_DIVERT no state
```
Вариант с перехватом потока в обе стороны. Особо сильно загружает процессор.
Все скачиваемые гигабайты пойдут через dvtws2. Из них обычно нужно всего 1-2 пакета, все остальное - впустую расходует CPU,
но средства pf не предоставляют иных возможностей.
Перехват входящих по тем же портам обеспечивается автоматически за счет state.
```
IFACE_WAN = "em0"
PORTS_TCP = "80,443"
PORTS_UDP = "443"
PORT_DIVERT = "989"
pass out quick on $IFACE_WAN proto tcp to port { $PORTS_TCP } divert-packet port $PORT_DIVERT
pass out quick on $IFACE_WAN proto udp to port { $PORTS_UDP } divert-packet port $PORT_DIVERT
```
> [!CAUTION]
> В FreeBSD другая версия pf и немного другой синтаксис. Однако, фактически pf в FreeBSD сломан, поскольку не работает предотвращение зацикливания. В MacOS хотя и используется pf, ipdivert из ядра убран, правила работать не будут.
## Перехват трафика в ядре Windows
В Windows нет встроенных средств для перехвата трафика. Используется стороннее решение - драйвер windivert.
Управление интегрируется в сам процесс winws2.
windivert принимает [текстовые фильтры](https://reqrypt.org/windivert-doc.html#filter_language), похожие на фильтры wireshark и tcpdump.
В них есть возможности фильтрации по ip (без ipset), портам и raw пейлоадам. Нет побитовых логических операций и сдвигов.
Нет отслеживания потоков и ограничения по первым пакетам.
Драйвер windivert больше не разрабатывается, однако имеются подписанные варианты драйвера, совместимые со всеми современными windows,
но только для архитектуры x86_64. На arm64 есть неподписанный драйвер, требующий тестового режима подписи драйверов.
При использовании winws2 на Windows 11 arm64 приходится пользоваться x86_64 версией, поскольку winws2 написан под cygwin, а его нет для arm.
Драйвер .sys при этом заменяется на неподписанную arm64 версию. Запуск на Windows 10 arm64 теоретически возможен, но только с 32-битной
версией winws2 x86, поскольку эмуляция x64 в Windows 10 не предусмотрена.
windivert является частой целью для нападок антивирусов. Это хакерский инструмент, но вирусом он не является.
Правильнее воспринимать его как замену iptables для windows.
Иногда случаются конфликты со сторонним ПО, использующим драйвера режима ядра - прежде всего антивирусы и фаерволы, вплоть до синих экранов.
Практически исправить это нереально хотя бы из-за подписи драйверов, которую получить простому смертному без стоящих за ним корпораций очень непросто и накладно.
windivert не может обеспечить корректного перехвата проходящего трафика при раздаче сети средствами windows и как следствие использовании NAT,
поэтому возможности работы по проходящему трафику не реализованы. Единственный доступный вариант - установить proxy server.
winws2 может принимать полные raw фильтры - вы пишите фильтр сами и указываете его в параметре `--wf-raw=<filter>` или `--wf-raw=@<filter_file>`.
Но это обычно не очень удобно, поэтому существует встроенный конструктор фильтров.
`--wf-tcp-out`, `--wf-tcp-in`, `--wf-udp-out`, `--wf-udp-in` берут список портов (`80,443`) или диапазонов портов (`80,443,500-1000`)
и включают полный перехват портов по указанному направлению.
`--wf-raw-part` принимает частичные windivert фильтры. Синтаксис аналогичен `--wf-raw`. `--wf-raw-part` может быть несколько.
Частичные фильтры встраиваются конструктором в итоговый фильтр по принципу OR. Или указанные порты, или ваш фильтр1 или ваш фильтр2.
`--wf-save=<filter_file>` записывает созданный конструктором фильтр в файл для последующего анализа и модификации.
Конструктор фильтров автоматически перехватывает входящие tcp с флагами SYN+ACK,FIN,RST. Писать специально правила не нужно.
`--wf-filter-lan` (по умолчанию включен) отфильтровывает пакеты на не глобальные IP адреса, такие как `192.168.0.0/16`.
`--wf-tcp-empty` (по умолчанию выключен) включает перехват пустых tcp пакетов без флагов SYN,FIN,RST.
Если параметр не включен, перехват пустых ACK пакетов не производится, что позволяет существенно сэкономить на процессоре.
Но для некоторых стратегий эти пакеты могут понадобиться. Только вы можете знать нужны они вам или нет.
Если есть перехват любого tcp порта, автоматически включается перехват всех http редиректов, чтобы работал autohostlist.
Перехват http redirect работает по payload сигнатуре, то есть проверяются байты в определенных позициях содержимого пакета.
Предпочтительно по udp протоколам, особенно по тем, где порт не определен, использовать свои `--wf-raw-part` фильтры,
чтобы сэкономить на процессоре. Для tcp тоже возможны фильтры, но надо учитывать потребности conntrack. Он нуждается
как минимум в SYN пакете, а желательно еще и FIN, RST. Если нужна фильтрация по сообщениям , занимающим более одного tcp сегмента,
такое отфильтровать средствами windivert невозможно - требуется полный перехват порта по направлению.
# nfqws2
## Общие принципы задания параметров
Все параметры nfqws2 передаются в командной строке, либо загружаются из файла в том же самом формате.
nfqws2 использует стандартный парсер getopt_long_only.
Опции имеют формат `--name[=value]`. Некоторые опции не требуют параметров, другие требуют, а третьи могут их брать опционально.
Парсер getopt позволяет задавать значение через знак `=` или через пробел. Лишние значения через пробел могут игнорироваться,
поэтому казалось бы ошибочные параметры могут не вызвать ошибку. Лучше всегда писать значения через знак `=`.
Чтение параметров из файла реализовано через задание единственной опции `@config_file`.
Все остальные параметры командной строки будут проигнорированы.
Опции будут прочитаны из файла, как будто бы вы ввели его содержимое в командой строке.
Возможность не поддерживается в Android и OpenBSD версиях.
## Полный список опций
Общие параметры для всех версий - nfqws2, dvtws2, winws2.
```
@<config_file> ; чтение опций командной строки из файла. все остальные опции из командной строки игнорируются.
--debug=0|1|syslog|android|@<filename> ; писать дебаг лог. 0 - нет , 1 - на консоль, syslog - в unix syslog, android - системный log android, @<filename> - в файл
--version ; вывести версию и выйти
--dry-run ; проверить валидность параметров командной строки и наличие файлов. не проверяет корректность скриптов LUA !
--comment=any_text ; любой текст. игнорируется
--daemon ; отключиться от консоли (демонизироваться)
--pidfile=<filename> ; запись PID в файл
--ctrack-timeouts=S:E:F[:U] ; таймауты conntrack для стадий tcp SYN, ESTABLISHED, FIN и для udp
--ctrack-disable=[0|1] ; 1 отключает conntrack
--server=[0|1] ; серверный режим. для обслуживания listener-ов меняются многие аспекты выбора направления и ip/port источника/приемника
--ipcache-lifetime=<int> ; время жизни записей кэша IP в секундах. 0 - без ограничений.
--ipcache-hostname=[0|1] ; 1 или отсутствие аргумента включают кэширование имен хостов для применения в стратегиях нулевой фазы
--reasm-disable=[proto[,proto]] ; отключить сборку фрагментов для списка пейлоадов : tls_client_hello quic_initial . без аргумента - отключить reasm для всего.
DESYNC ENGINE INIT:
--writeable[=<dir_name>] ; создать директорию для LUA с разрешением записи и поместить путь к ней в переменную env "WRITEABLE" (только одна директория)
--blob=<item_name>:[+ofs]@<filename>|0xHEX ; загрузить бинарный файл или hex строку в переменную LUA <item_name>. +ofs задает смещение от начала файла
--lua-init=@<filename>|<lua_text> ; однократно при старте выполнить LUA код из строки или из файла
--lua-gc=<int> ; интервал вызова сборщика мусора LUA в секундах. 0 отключает периодический вызов.
MULTI-STRATEGY:
--new ; начало нового профиля
--skip ; игнорировать профиль
--name=<name> ; установить имя профиля
--template[=<name>] ; использовать профиль как шаблон, задать имя
--cookie[=<string>] ; установить значение LUA переменной "desync.cookie", передаваемое каждому инстансу данного профиля
--import=<name> ; копировать настройки из шаблона в текущий профиль с полным замещением
--filter-l3=ipv4|ipv6 ; фильтр профиля : версия ip протокола
--filter-tcp=[~]port1[-port2]|* ; фильтр профиля : порты tcp или диапазоны портов через запятую
--filter-udp=[~]port1[-port2]|* ; фильтр профиля : порты udp или диапазоны портов через запятую
--filter-l7=proto[,proto] ; фильтр профиля : список протоколов потока. полный список доступен в help тексте программы.
--ipset=<filename> ; фильтр профиля : включающий список ip адресов или подсетей из файла. может быть смешанным ipv4+ipv6.
--ipset-ip=<ip_list> ; фильтр профиля : включающий фиксированный список ip адресов или подсетей через запятую
--ipset-exclude=<filename> ; фильтр профиля : исключающий список ip адресов или подсетей из файла. может быть смешанным ipv4+ipv6.
--ipset-exclude-ip=<ip_list> ; фильтр профиля : исключающий фиксированный список ip адресов или подсетей через запятую
--hostlist=<filename> ; фильтр профиля : включающий список доменов из файла
--hostlist-domains=<domain_list> ; фильтр профиля : включающий фиксированный список доменов из файла
--hostlist-exclude=<filename> ; фильтр профиля : исключающий список доменов из файла
--hostlist-exclude-domains=<domain_list> ; фильтр профиля : исключающий фиксированный список доменов из файла
--hostlist-auto=<filename> ; фильтр профиля : автоматически пополняемый по обратной связи включающий фильтр доменов
--hostlist-auto-fail-threshold=<int> ; параметр автолиста : количество неудач подряд для занесения в лист. по умолчанию 3
--hostlist-auto-fail-time=<int> ; параметр автолиста : максимальное время между неудачами без сброса счетчика. по умолчанию 60 секунд
--hostlist-auto-retrans-threshold=<int> ; параметр автолиста : количество tcp ретрансмиссий в одном сеансе для фиксации неудачи. по умолчанию 3
--hostlist-auto-retrans-maxseq=<int> ; параметр автолиста : исходящий relative sequence, после которого детект неудачи прекращается. по умолчанию 32768
--hostlist-auto-incoming-maxseq=<int> ; параметр автолиста : входящий relative sequence, после которого детект неудачи прекращается, а счетчик сбрасывается. по умолчанию 4096
--hostlist-auto-udp-out=<int> ; параметр автолиста : условие неудачи udp : количество исходящих пакетов больше или равно значению. по умолчанию 4
--hostlist-auto-udp-in=<int> ; параметр автолиста : условие неудачи udp : количество входящих пакетов меньше или равно значению. по умолчанию 1
--hostlist-auto-debug=<logfile> ; дебаг лог автолиста
LUA PACKET PASS MODE:
--payload=type[,type] ; внутрипрофильный фильтр : фильтр пейлоада для последующих инстансов внутри профиля. список пейлоадов доступен в help тексте программы.
--out-range=[(n|a|d|s|p)<int>](-|<)[(n|a|d|s|p)<int>] ; внутрипрофильный фильтр : диапазон счетчиков conntrack для последующих инстансов внутри профиля - исходящее направление
--in-range=[(n|a|d|s|p)<int>](-|<)[(n|a|d|s|p)<int>] ; внутрипрофильный фильтр : диапазон счетчиков conntrack для последующих инстансов внутри профиля - входящее направление
LUA DESYNC ACTION:
--lua-desync=<functon>[:param1=val1[:param2=val2]] ; при обработке профиля вызвать LUA инстанс с указанными параметрами, если соблюдены условия внутрипрофильных фильтров
```
Специфические параметры nfqws2 :
```
--qnum=<nfqueue_number> ; номер очереди NFQUEUE в Linux
--user=<username> ; сменить uid/gid на те, что связаны с указанным именем пользователя
--uid=uid[:gid1,gid2,...] ; сменить uid/gid на указанные числовые значения
--bind-fix4 ; лечение проблемы ухода генерированных пакетов на Linux с неверного интерфейса при использовании PBR (ipv4)
--bind-fix6 ; аналогично для ipv6
--fwmark=<int|0xHEX> ; бит в mark для предотвращения зацикливания. default = 0x40000000
--filter-ssid=ssid1[,ssid2,ssid3,...] ; фильтр профиля : имя wifi сети
```
Специфические параметры dvtws2 :
```
--port=<port> ; номер divert порта
--user=<username> ; сменить uid/gid на те, что связаны с указанным именем пользователя
--uid=uid[:gid1,gid2,...] ; сменить uid/gid на указанные числовые значения
```
Специфические параметры winws2 :
```
--wf-iface=<int>[.<int>] ; windivert конструктор : номер сетевого интерфейса
--wf-l3=ipv4|ipv6 ; windivert конструктор : версия ip
--wf-tcp-in=[~]port1[-port2] ; windivert конструктор : tcp порты или диапазоны портов для перехвата по входящему направлению. список через запятую
--wf-udp-in=[~]port1[-port2] ; windivert конструктор : udp порты или диапазоны портов для перехвата по входящему направлению. список через запятую
--wf-tcp-in=[~]port1[-port2] ; windivert конструктор : tcp порты или диапазоны портов для перехвата по исходящему направлению. список через запятую
--wf-udp-in=[~]port1[-port2] ; windivert конструктор : udp порты или диапазоны портов для перехвата по исходящему направлению. список через запятую
--wf-tcp-empty=[~]port1[-port2] ; windivert конструктор : перехватывать пустые tcp пакеты ACK. по умолчанию - нет.
--wf-raw-part=<filter>|@<filename> ; windivert конструктор : частичный windivert raw фильтр. обьединяется по принципу ИЛИ.
--wf-filter-lan=0|1 ; windivert конструктор : отфильтровывать неглобальные IP адреса. по умолчанию - да.
--wf-raw=<filter>|@<filename> ; полный windivert фильтр. замещает конструктор.
--wf-save=<filename> ; сохранить полный итоговый windivert фильтр в файл
LOGICAL NETWORK FILTER:
--ssid-filter=ssid1[,ssid2,ssid3,...] ; список сетей wifi, при наличии подключения к которым перехват включается, а иначе не включается.
--nlm-filter=net1[,net2,net3,...] ; список сетей Network List Manager, при наличии подключения к которым перехват включается, а иначе не включается.
--nlm-list[=all] ; вывести список подключенных NLM сетей. all - список всех NLM сетей
```
## Использование множественных профилей
Профили существуют, чтобы в зависимости от указанных условий фильтра выбрать ту или иную стратегию воздействия на трафик.
Общая схема использования профилей следующая :
```
nfqws2 <глобальные_параметры>
<фильтр 1> <стратегия 1> --new
<фильтр 2> <стратегия 2> --new
...............
<фильтр N> <стратегия N>
```
Когда на вход поступает пакет, и для него еще нет записи в conntrack, происходит выбор профиля.
Фильтры профиля проверяются от первого до последнего - с начала в конец - слева направо, и никак иначе.
Всегда выигрывает только один профиль - по первому совпадению условий фильтра, а все остальные не задействуются.
Если не сработал ни один фильтр, выбирается пустой профиль с номером 0, не предполагающий никаких действий с трафиком.
Все условия, кроме `--filter-l7` и хостлистов, являются однозначными и известными с момента начала обработки потока (с начала соединения).
В начале как правило еще неизвестнен протокол потока и имя хоста, выделяемое из сообщений потока.
Когда эти величины становятся известны, происходит поиск профиля заново. Если выбирается другой профиль - происходит перескок.
Таких перескоков может быть до двух, поскольку есть только 2 величины, влияющие на выбор, неизвестные с самого начала.
Для протоколов потока tls, http, quic обычно бывает только 1 перескок, поскольку определение протокола и имени хоста
происходит по одному пакету или группе пакетов. Для XMPP это 2 перескока - сначала определяется xmpp как таковой,
затем ловится переход на TLS, и только в нем выделяется имя хоста.
При написании стратегий следует их продумывать с учетом логики перескоков.
Если нужно, чтобы стратегия начала работу с самого первого пакета и продолжила работать дальше после изменения профиля,
нужно дублировать вызовы во всех профилях, по которым может пройти поток.
### Шаблоны профилей
Когда имеется много сложных и повторяющихся стратегий, может быть удобно использовать шаблоны.
Шаблон - это такой же профиль, только он не идет в работу, а попадает в отдельный список шаблонов.
Шаблоном профиль становится через задание параметра `--template=<name>`.
Далее он может быть импортирован (`--import=<name>`). Импорт предполагает копирование профиля полностью,
а не только тех настроек, которые в нем были указаны - все остальные настройки имеют значения по умолчанию.
Любые настройки текущего профиля стираются, включая и имя.
Поэтому `--import` надо писать в начале, а потом добавлять уникальные для профиля параметры.
Шаблоны могут импортировать и друг друга. Для шаблона обязательно уникальное имя, а при импорте имя копируется,
поэтому обязательно надо задать уникальное имя через `--name`.
```
nfqws2 <глобальные_параметры>
--template=tpl1 <базовые параметры 1> --new
--template=tpl2 <базовые параметры 2> --new
--template --import tpl2 --name tpl3 <базовые параметры 3> --new
--import tpl1 --name prof1 <дополнительные параметры 1> --new
--import tpl3 --name prof2 <дополнительные параметры 2> --new
--name prof3 <параметры 3>
```
В примере имеется 3 рабочих профиля и 3 шаблона, 1 из которых импортирует настройки другого.
* Профиль prof1 получает обьединение `<базовые параметры 1>` и `<дополнительные параметры 1>`.
* Профиль prof2 получает обьединение `<базовые параметры 2>`, `<базовые параметры 3>` и `<дополнительные параметры 2>`
* Профиль prof3 получает `<параметры 3>`. Он не импортирует шаблоны.
В шаблонах допустимы любые параметры, относящиеся к профилям, включая и фильтры.
### Фильтрация по листам
Если имеются фильтры по хостлистам, и есть хотя бы один домен в любом хостлисте или указан автохостлист,
то профиль никогда не будет выбран при отсутствующем имени хоста.
Случай, когда нет автохостлиста, а все файлы листов пустые, приравнивается к отсутствию фильтра по хостлисту.
Если нет автохостлиста, но есть записи в обычных хостлистах, то профиль выбирается только если текущий
хост проходит по любому из включающих хостлистов, но не проходит ни по одному из исключающих.
Если есть автохостлист, то при наличии имени хоста профиль выбирается всегда вне зависимости от его вхождения
в какие-либо листы этого профиля. Действия зависят от вхождения в листы.
* Если хост входит в исключающие листы, не происходит никаких действий и не происходит попытки выяснить работает ли ресурс.
* Если хост не входит в исключающие листы, но входит во включающие - происходит применение стратегии без
попыток выяснить работает ли ресурс с ней.
* Если хост не входит ни в исключающие листы, ни во включающие - стратегия не применяется, происходит обнаружение
неудачи доступа к ресурсу. Если случилась неудача, увеличивается счетчик неудач. Если случается удача или превышается
интервал времени между неудачами `--hostlist-auto-fail-time` - счетчик сбрасывается.
Когда счетчик достигает `--hostlist-auto-fail-threshold`, происходит занесение хоста в автолист.
При следующем запросе будет считаться, что хост входит во включающий лист.
* Файлы хостлистов и ipset-ов перечитываются автоматически при изменении - перезапуск nfqws2 не нужен.
* Сигнал SIGHUP помечает все листы для принудительной перечитки при обработки следующего пакета
* Каждая запись о домене, IP адресе или подсети идет на новой строке.
* Хостлисты и ipset-ы поддерживают комментарии. Пустые строки и строки, начинающиеся с `#`, игнорируются.
* В хостлистах поддомены учитываются автоматически. `*` не поддерживается. Если в начале идет символ `^`, автоматический учет поддоменов отменяется для этого домена.
* ipset-ы могут включать адреса и подсети как ipv4, так и ipv6.
* В статическом варианте `--ipset-ip` и `--hostlist-domains` домены идут через запятую. В статическом хостлисте `#` и `^` так же поддерживаются.
* Для листов поддерживается сжатие gzip.
### Детектор неудач автохостлистов
Детектор срабатывает только при наличии имени хоста. Неудачей считается :
* tcp : происходит не менее `--hostlist-auto-retrans-threshold` ретрансмиссий в пределах исходящего relative sequence `--hostlist-auto-retrans-maxseq`
* tcp : приходит RST в пределах входящего relative sequence от 1 до `--hostlist-auto-incoming-maxseq`
* tcp : принят пейлоад `http_reply` и http ответ является переадресацией 302 или 307 на абсолютный URL с доменом 2 уровня, не совпадающим с доменом 2 уровня хоста.
* udp : ушло не менее `--hostlist-auto-udp-out` пакетов, пришло не более `--hostlist-auto-udp-in` пакетов. Эта ситуация означает, что клиент шлет запросы, а сервер на них не отвечает или отвечает меньше, чем должен по протоколу.
Удачей считается :
* tcp : превышение исходящего relative sequence `--hostlist-auto-retrans-maxseq` . Клиент смог отослать достаточно много, что вряд ли бы случилось в случае реакции DPI.
* tcp : превышение входящего relative sequence `--hostlist-auto-incoming-maxseq` . Сервер прислал достаточно много, чтобы это не было похоже на ответ DPI.
* udp : превышение количества пришедших пакетов `--hostlist-auto-udp-in` . Сервер ответил достаточно много.
При неудаче, если с прошлой неудачи прошло не более `--hostlist-auto-fail-time` секунд, счетчик неудач увеличивается.
Если прошло больше времени - счетчик сбрасывается, счет идет заново.
При удаче счетчик сбрасывается. Считается, что ресурс работает, а сбой был временным и не связанным с блокировкой.
При достижении счетчиком `--hostlist-auto-fail-threshold` происходит занесение хоста в лист.
Большинство критериев удачи или неудачи требует анализа входящего и исходящего трафика, поэтому необходим их перехват в достаточном обьеме
для возможности срабатывания критериев.
### Фильтр по наличию сетей
Если, допустим, вам нужно применить одну стратегию для wifi, другую для провода, это делается через фильтр, основанный на имени интерфейса.
Но что делать, если вы подключаетесь к разным wifi сетям или подключаете провод в разных локациях ?
В случае провода решение есть только на windows, на других системах - нет. Для wifi есть решение на Linux и Windows, на BSD - нет.
Фильтр по wifi берет список SSID через запятую, однако он реализован по-разному в Linux и Windows.
В Linux используется фильтр профиля `--filter-ssid`. Если он задан, nfqws2 пытается получить SSID на интерфейсе, куда этот пакет уходит
или откуда он приходит. Если ему это удается - происходит проверка по списку сетей, если нет - условие фильтра не соблюдается, профиль не выбирается.
Такая концепция позволяет работать даже если вы подключаетесь к нескольким wifi сетям на разных адаптерах.
В Windows концепция иная. Мониторится наличие указанных wifi сетей на всех wifi адаптерах, и если на любом из них SSID есть,
перехват windivert включается, а иначе выключается. Чтобы обслужить wifi сети с разными стратегиями нужно запускать несколько истансов winws2.
Один будет включаться, остальные - отключаться. Список SSID задается параметром `--ssid-filter`.
Другой способ решить вопрос, и не только с wifi, - использование фильтра NLM - Network List Manager.
`--nlm-list[=all]` вернет список GUID подключенных или всех сетей, если указано значение параметра "all".
Далее вписываете список GUID через запятую в `--nlm-filter`.
Сеть NLM - это результат детекта системой подключения к конкретной сети.
Вы можете подключиться к роутеру по wifi или проводу, но сеть будет одна. Для различения сетей система обычно смотрит на MAC шлюза.
Технология NLM интересная и полезная, но , увы , адекватное управление ей было только в Windows 7.
В остальных системах нужно копаться в powershell или лезть в реестр, чтобы раскидать подключения по нужным GUID,
если вдруг они раскидались системой неправильно. Но можно и не бороться, а просто внести список GUID, назначенных системой автоматически.
## Серверный режим
Некоторые виды воздействий можно осуществлять не только со стороны клиента, но и сервера.
nfqws2 создавался с учетом полноценной работы как по входящему, так и по исходящему трафику,
как на клиентской стороне, так и на стороне сервера.
Понятие направления в сети во многом условно. Для адресатов пакетов все ясно -
что-то отсылается, что-то принимается. Но для роутера это неясно совсем.
Есть только входящий интерфейс и исходящий. По сути 2 направления равнозначны,
если рассматривать только уровень L3 - уровень отдельных IP пакетов.
Поэтому направление в nfqws2 учитывается за счет слежения за потоками.
Поток - это либо tcp соединение, либо последовательность udp пакетов.
udp не предполагает понятия соединения, поэтому оставлено общее название - поток.
Поток характеризуется 4 величинами : ip1:port1-ip2:port2. Их совокупность (tuple)
определяет принадлежность пакета к конкретному потоку.
За потоками в nfqws2 следит conntrack.
Тот, кто первым послал SYN (tcp) или первый UDP пакет, считается клиентом,
а противоположный конец - сервером.
Если создание записи о потоке в conntrack произошло по SYN,ACK пакету (tcp),
то этот конец считается сервером, а противоположный - клиентом.
Таким образом conntrack определяет роли в установлении соединения и хранит
по каждой роли отдельный набор счетчиков - сколько пакетов прошло,
сколько пакетов с данными, сколько байт передано и тд.
В клиентском режиме исходящим направлением считается направление от клиента,
в серверном (`--server`) - от сервера. Входящим считается противоположное направление.
При указании `--server` направления инвертируются.
`--in-range`, `--out-range`, а так же признак `desync.outgoing` в LUA функциях
меняются местами, чтобы соответствовать фактически отсылаемым или принимаемым данным
со стороны сервера. Клиент шлет запросы (http_req) и принимает ответы (http_reply).
Сервер шлет ответы (http_reply) и принимает запросы (http_req).
Для обоих режимов - клиентского и серверного - поиск в ipset осуществляется по IP адресу
назначения для исходящего направления и IP адресу источника для входящего направления.
В клиентском режиме фильтры портов проверяют порт назначения для исходящего направления и порт источника для входящего направления.
В серверном режиме фильтры портов проверяют порт назначения для входящего направления и порт источника для исходящего направления.
Это сделано потому, что в фильтрации реальный смысл имеет только порт сервера.
Порт клиента выбирается как правило случайно из некоторого диапазона и не подходит для осмысленной фильтрации.
Таким образом, сервер фильтрует по ipset ip клиентов, а клиент - ip серверов.
Но и сервер, и клиент фильтруют порт сервера.
Linux conntrack имеет похожий способ определения направлений.
Для клиента исходящими будут пакеты original, входящими - reply. Для сервера - наоборот.
Это нужно учитывать при написании правил перехвата, полагающихся на направление conntrack.
Можно определять направление и по именам интерфейсов. При стандартной конфигурации lan-wan
входящими считаются пакеты, полученные с wan , а исходящими - отправленные через wan.
Именно wan, поскольку обычно используется NAT, а для nfqws2 важно перехватывать исходящие пакеты после NAT
и принимать входящие до NAT.
Если роутер работает без NAT (типично для ipv6), не имеет значения на каком этапе перехватываются пакеты.
Все IP адреса равнозначны. Вы можете подключаться к интернету, интернет - к вам. Вы - его составная часть
с прямой IP адресацией. Но на практике все же к вам подключаться скорее всего не будут, а вы защититесь
от таких подключений фаерволом. Поэтому направление все равно по интерфейсу определяется достаточно однозначно.
Но если у вас все-же есть внутренний сервер, вы можете для него запустить отдельный инстанс nfqws2 в серверном режиме,
а для клиентского трафика - в обычном режиме.
В BSD правила ipfw или pf обычно так и пишутся - "xmit wan", "recv wan" + добавляются фильтры по номерам
портов назначения для xmit и портов источника для recv (или наоборот для серверного режима).
Это достаточно надежно идентифицирует необходимый к перехвату трафик по направлению.
## Песочница
В целях безопасности nfqws2 после инициализации сбрасывает свои привилегии.
Весь LUA код выполняется только после сброса привилегий. Он никогда не получает исходные права.
BSD :
* Меняется UID/GID на указанные в параметрах `--user`, `--uid`. По умолчанию на 0x7FFFFFFF.
Linux :
* Меняется UID/GID на указанные в параметрах `--user`, `--uid`. По умолчанию на 0x7FFFFFFF.
* Сбрасываются capabilities до cap_net_raw, cap_net_admin (требуется для NFQUEUE). Сбрасывается bounding set до нуля.
* Выставляется признак NO_NEW_PRIVS, чтобы не работали suid биты и caps на файлах.
* Включается seccomp фильтр, запрещающий exec и ряд файловых операций - чтение содержимого каталогов, создание/удаление каталогов, создание специальных файлов (линки, девайсы), chmod, chown, посылание сигналов (kill), ptrace.
В случае нарушения процесс аварийно завершается.
Windows :
* Хотя драйвер windivert требует привилегий администратора, после его инициализации процесс winws2 ставит себе low mandatory level.
Это предотвращает доступ на запись практически ко всем файлам и обьектам, защищенным security descriptor.
Процесс больше не может управлять службами и осуществлять привилегированные действия. Однако, группа Administrators остается в токене процесса,
поэтому ничто не предотвращает чтение большинства файлов, если на них есть доступ для Administrators. LUA не имеет встроенных средств
чтения содержимого каталогов, поэтому обнаружение интересующих файлов для злоумышленника затруднено.
* Безвозвратно убираются все Se* привилегии из токена, кроме SeChangeNotifyPrivilege.
Есть простой способ передать LUA коду каталог, доступный на запись - параметр `--writeable[=<dirname>]`.
nfqws2 создает каталог, назначает на него такие права, чтобы LUA код смог писать туда файлы, передает имя директории в переменной env `WRITEABLE`.
Если dirname не задан, на Windows создается каталог внутри `%USERPROFILE%/AppData/LocalLow`
Со стороны LUA убираются опасные функции - os.execute, io.popen, package.loadlib и модуль debug.
На github исполняемые файлы nfqws2 собираются с вариантом luajit без FFI.
## Вызов LUA кода
LUA код вызывается в 2 этапа.
1. Однократно при запуске программы через `--lua-init=code|@file`. Если значение параметра начинается с `@`, выполняется файл, иначе значение параметра является LUA кодом.
2. При обработке профиля через `--lua-desync=function_name:arg1[=val1]:arg2[=val2]:argN[=valN]`.
Сначала идет имя функции, затем через двоеточия аргументы и их значения.
Все значения являются строками. Если значение не задано, оно равно пустой строке.
Реализовано 2 типа автоматических подстановок на строне C кода.
`%var` подставляет значение переменной `desync.var` или `var`, если первая отсутствует.
`#var` подставляет длину переменной `desync.var` или `var`, если первая отсутствует.
Двоеточия и знаки `%`, `#` в начале могут быть эскейпнуты через `\`.
И `--lua-init`, и `--lua-desync` может быть несколько. Выполнение производится строго в порядке указания.
### Передача блобов
Блоб - это двоичный блок данных любого размера, который может быть загружен в переменную LUA средствами C кода при старте программы.
`--blob=<item_name>:[+ofs]@<filename>|0xHEX`
* `item_name` - имя переменной LUA.
* `[+ofs]@<filename>` - загрузка из файла со смещения ofs.
* `0xHEX` - загрузка из HEX строки
Прямые операции с файлами из кода LUA не рекомендованы без необходимости.
LUA код выполняется с ограниченными правами, задуманное может не получиться или не работать на разных ОС в разных условиях.
Загрузка blob происходит до вхождения в песочницу, поэтому шансов на успех больше.
### Внутрипрофильные фильтры
Они бывают трех видов - `--payload`, `--in-range`, `--out-range`.
Значения фильтров действуют с момента их указания до следующего переопределения.
* `--payload=type1[,type2][,type2]...` принимает список известных пейлоадов через зяпятую, "all" или "known". Список известных пейлоадов доступен в help тексте nfqws2. По умолчанию `--payload=all`.
* `--(in-range|out-range)=[(n|a|d|s|p)<int>](-|<)[(n|a|d|s|p)<int>]` задает диапазоны счетчиков conntrack по входящему и исходящему направлениям. По умолчанию `--in-range=x`, `--out-range=a`.
Диапазоны задаются в формах : `mX-mY`, `mX<mY`, `-mY`, `<mY`, `mX-`, где m - режим счетчика, X - нижнее значение, Y - верхнее значение.
Режимы `x` и `a` задаются без диапазона и значения счетчика - единственной буквой. Знак `-` означает включающую верхнюю границу, `<` - исключающую.
Доступны следующие режимы счетчика :
* 'a' - всегда
* 'x' - никогда
* 'n' - номер пакета
* 'd' - номер пакета с данными
* 'b' - байт-позиция переданных данных
* 's' - tcp: relative sequence начала текущего пакета
* 'p' - tcp: relative sequence верхней границы текущего пакета (s + длина пейлоада)
nfqws2 следит за превышением верхней границы счетчиков для всех LUA инстансов.
Если во всех инстансах превышена верхняя граница по направлению или инстансы вошли по направлению в состояние cutoff добровольно,
происходит lua cutoff - выключение процессинга LUA в текущем потоке. Это нужно для экономии ресурсов процессора,
поскольку проверка единственного bool признака практически не требует никаких ресурсов.
### Типичная схема вызова инстансов внутри профиля
В рассматриваемом примере решается задача для пейлоадов tls_client_hello и http_req попробовать фейки,
отдельные для каждого типа пейлоада, а если это не работает, перейти на multidisorder для tls и multisplit для http.
Если и это не работает - крутить стратегии по кругу.
```
--filter-tcp=80,443 --filter-l7=http,tls
--out-range=-d10
--in-range=-s1 --lua-desync=circular
--in-range=x
--payload=tls_client_hello
--lua-desync=fake:blob=fake_default_tls:badsum:strategy=1
--lua-desync=multidisorder:strategy=2
--payload=http_req
--lua-desync=fake:blob=fake_default_http:badsum:strategy=1
--lua-desync=multisplit:strategy=2
```
Не суть важно как работают конкретные функции, сейчас важно понять как работают внутрипрофильные фильтры и как передаются параметры LUA инстансам.
* Профильный фильтр по tcp портам и типу протокола потока позволяет избежать вызовов LUA на другом трафике.
Профиль вообще не будет задействован, если условия фильтра не выполнятся.
* `--out-range` указан, чтобы отсечь поток от LUA по исходящему направлению после 10 отправленных пакетов с данными - для экономии процессора.
На Linux может быть не нужно, если используется фильтр connbytes.
* Инстанс circular для своей работы требует начальные входящие пакеты потока, а по умолчанию они отключены.
Поэтому включаем вплоть до первого пакета с данными tcp, который имеет relative sequence 1.
* Остальные инстансы не нуждаются во входящем трафике. Снова отключаем. Действие `--in-range=x` распространяется до конца профиля.
* Действие `--payload` распространяется на два следующих за ним инстанса.
* Строчка `--lua-desync=fake:blob=fake_default_tls:badsum:strategy=1` вызывает функцию `fake` с 3 аргументами : `blob`, `badsum`, `strategy`.
Значением аргумента `badsum` является пустая строка.

View File

@@ -25,9 +25,9 @@ typedef struct
// tcp only state, not used in udp
uint32_t seq0; // starting seq and ack
uint16_t winsize; // last seen window size
uint8_t scale; // last seen window scale factor. SCALE_NONE if none
uint32_t winsize_calc; // calculated window size
uint16_t mss;
uint32_t winsize_calc; // calculated window size
uint8_t scale; // last seen window scale factor. SCALE_NONE if none
} t_ctrack_position;
typedef struct

View File

@@ -40,9 +40,6 @@
#include <linux/genetlink.h>
#include <libmnl/libmnl.h>
#include <net/if.h>
#define _LINUX_IF_H // prevent conflict between linux/if.h and net/if.h in old gcc 4.x
#include <linux/wireless.h>
#include <sys/ioctl.h>
#endif
uint32_t net32_add(uint32_t netorder_value, uint32_t cpuorder_increment)
@@ -1578,9 +1575,9 @@ bool rawsend_queue(struct rawpacket_tailhead *q)
// linux-specific wlan retrieval implementation
typedef void netlink_prepare_nlh_cb_t(struct nlmsghdr *nlh);
typedef void netlink_prepare_nlh_cb_t(struct nlmsghdr *nlh, void *param);
static bool netlink_genl_simple_transact(struct mnl_socket* nl, uint16_t type, uint16_t flags, uint8_t cmd, uint8_t version, netlink_prepare_nlh_cb_t cb_prepare_nlh, mnl_cb_t cb_data, void *data)
static bool netlink_genl_simple_transact(struct mnl_socket* nl, uint16_t type, uint16_t flags, uint8_t cmd, uint8_t version, netlink_prepare_nlh_cb_t cb_prepare_nlh, void *prepare_data, mnl_cb_t cb_data, void *data)
{
char buf[MNL_SOCKET_BUFFER_SIZE];
struct nlmsghdr *nlh;
@@ -1595,7 +1592,7 @@ static bool netlink_genl_simple_transact(struct mnl_socket* nl, uint16_t type, u
genl->cmd = cmd;
genl->version = version;
if (cb_prepare_nlh) cb_prepare_nlh(nlh);
if (cb_prepare_nlh) cb_prepare_nlh(nlh, prepare_data);
if (mnl_socket_sendto(nl, nlh, nlh->nlmsg_len) < 0)
{
@@ -1619,7 +1616,7 @@ static bool netlink_genl_simple_transact(struct mnl_socket* nl, uint16_t type, u
return false;
}
static void wlan_id_prepare(struct nlmsghdr *nlh)
static void wlan_id_prepare(struct nlmsghdr *nlh, void *param)
{
mnl_attr_put_strz(nlh, CTRL_ATTR_FAMILY_NAME, "nl80211");
}
@@ -1651,7 +1648,7 @@ static int wlan_id_cb(const struct nlmsghdr *nlh, void *data)
static uint16_t wlan_get_family_id(struct mnl_socket* nl)
{
uint16_t id;
return netlink_genl_simple_transact(nl, GENL_ID_CTRL, NLM_F_REQUEST | NLM_F_ACK, CTRL_CMD_GETFAMILY, 1, wlan_id_prepare, wlan_id_cb, &id) ? id : 0;
return netlink_genl_simple_transact(nl, GENL_ID_CTRL, NLM_F_REQUEST | NLM_F_ACK, CTRL_CMD_GETFAMILY, 1, wlan_id_prepare, NULL, wlan_id_cb, &id) ? id : 0;
}
static int wlan_info_attr_cb(const struct nlattr *attr, void *data)
@@ -1686,42 +1683,130 @@ static int wlan_info_attr_cb(const struct nlattr *attr, void *data)
}
return MNL_CB_OK;
}
struct wlan_info_req
{
struct wlan_interface_collection *wc;
bool bReqSSID;
};
static int wlan_info_cb(const struct nlmsghdr *nlh, void *data)
{
int ret;
struct wlan_info_req *wr = (struct wlan_info_req*)data;
if (wr->wc->count>=WLAN_INTERFACE_MAX) return MNL_CB_OK;
memset(wr->wc->wlan + wr->wc->count,0,sizeof(struct wlan_interface));
ret = mnl_attr_parse(nlh, sizeof(struct genlmsghdr), wlan_info_attr_cb, wr->wc->wlan + wr->wc->count);
if (ret>=0 && (!wr->bReqSSID || *wr->wc->wlan[wr->wc->count].ssid) && *wr->wc->wlan[wr->wc->count].ifname && wr->wc->wlan[wr->wc->count].ifindex)
wr->wc->count++;
return ret;
}
static bool wlan_info(struct mnl_socket* nl, uint16_t wlan_family_id, struct wlan_interface_collection* w, bool bReqSSID)
{
struct wlan_info_req req = { .bReqSSID = bReqSSID, .wc = w };
return netlink_genl_simple_transact(nl, wlan_family_id, NLM_F_REQUEST | NLM_F_ACK | NLM_F_DUMP, NL80211_CMD_GET_INTERFACE, 0, NULL, NULL, wlan_info_cb, &req);
}
static void scan_prepare(struct nlmsghdr *nlh, void *param)
{
mnl_attr_put_u32(nlh, NL80211_ATTR_IFINDEX, *(int*)param);
}
static uint8_t *find_ie(uint8_t *buf, size_t len, uint8_t ie)
{
while (len>=2)
{
if (len<(2+buf[1])) break;
if (buf[0]==ie) return buf;
buf+=buf[1]+2;
len-=buf[1]+2;
}
return NULL;
}
static int scan_info_attr_cb(const struct nlattr *attr, void *data)
{
struct wlan_interface *wlan = (struct wlan_interface *)data;
const struct nlattr *nested;
uint8_t *payload, *ie;
uint16_t payload_len;
bool ok;
switch(mnl_attr_get_type(attr))
{
case NL80211_ATTR_IFINDEX:
if (mnl_attr_validate(attr, MNL_TYPE_U32) < 0)
{
DLOG_PERROR("mnl_attr_validate");
return MNL_CB_ERROR;
}
wlan->ifindex = mnl_attr_get_u32(attr);
if (!if_indextoname(wlan->ifindex, wlan->ifname))
DLOG_PERROR("if_indextoname");
break;
case NL80211_ATTR_BSS:
if (mnl_attr_validate(attr, MNL_TYPE_NESTED) < 0)
{
DLOG_PERROR("mnl_attr_validate");
return MNL_CB_ERROR;
}
ok = false;
mnl_attr_for_each_nested(nested, attr)
{
if (mnl_attr_get_type(nested)==NL80211_BSS_STATUS)
{
uint32_t status = mnl_attr_get_u32(nested);
if (status==NL80211_BSS_STATUS_ASSOCIATED || status==NL80211_BSS_STATUS_AUTHENTICATED || status==NL80211_BSS_STATUS_IBSS_JOINED)
{
ok=1;
break;
}
}
}
if (!ok) break;
mnl_attr_for_each_nested(nested, attr)
{
switch(mnl_attr_get_type(nested))
{
case NL80211_BSS_INFORMATION_ELEMENTS:
payload_len = mnl_attr_get_payload_len(nested);
payload = mnl_attr_get_payload(nested);
ie = find_ie(payload,payload_len,0);
if (ie)
{
uint8_t l = ie[1];
if (l>=(sizeof(wlan->ssid))) l=sizeof(wlan->ssid)-1;
memcpy(wlan->ssid,ie+2,l);
wlan->ssid[l]=0;
}
break;
}
}
break;
}
return MNL_CB_OK;
}
static int scan_info_cb(const struct nlmsghdr *nlh, void *data)
{
int ret;
struct wlan_interface_collection *wc = (struct wlan_interface_collection*)data;
if (wc->count>=WLAN_INTERFACE_MAX) return MNL_CB_OK;
memset(wc->wlan+wc->count,0,sizeof(wc->wlan[0]));
ret = mnl_attr_parse(nlh, sizeof(struct genlmsghdr), wlan_info_attr_cb, wc->wlan+wc->count);
if (ret>=0 && *wc->wlan[wc->count].ifname && wc->wlan[wc->count].ifindex)
{
if (*wc->wlan[wc->count].ssid)
wc->count++;
else
{
// sometimes nl80211 does not return SSID but wireless ext does
int wext_fd = socket(AF_INET, SOCK_DGRAM, 0);
if (wext_fd!=-1)
{
struct iwreq req;
snprintf(req.ifr_ifrn.ifrn_name,sizeof(req.ifr_ifrn.ifrn_name),"%s",wc->wlan[wc->count].ifname);
req.u.essid.pointer = wc->wlan[wc->count].ssid;
req.u.essid.length = sizeof(wc->wlan[wc->count].ssid);
req.u.essid.flags = 0;
if (ioctl(wext_fd, SIOCGIWESSID, &req)!=-1)
if (*wc->wlan[wc->count].ssid)
wc->count++;
close(wext_fd);
}
}
}
ret = mnl_attr_parse(nlh, sizeof(struct genlmsghdr), scan_info_attr_cb, wc->wlan+wc->count);
if (ret>=0 && *wc->wlan[wc->count].ssid && *wc->wlan[wc->count].ifname && wc->wlan[wc->count].ifindex)
wc->count++;
return ret;
}
static bool wlan_info(struct mnl_socket* nl, uint16_t wlan_family_id, struct wlan_interface_collection* w)
static bool scan_info(struct mnl_socket* nl, uint16_t wlan_family_id, struct wlan_interface_collection* w)
{
return netlink_genl_simple_transact(nl, wlan_family_id, NLM_F_REQUEST | NLM_F_ACK | NLM_F_DUMP, NL80211_CMD_GET_INTERFACE, 0, NULL, wlan_info_cb, w);
struct wlan_interface_collection wc_all = { .count = 0 };
// wlan_info does not return ssid since kernel 5.19
// it's used to enumerate all wifi interfaces then call scan_info on each
if (!wlan_info(nl, wlan_family_id, &wc_all, false)) return false;
for(int i=0;i<wc_all.count;i++)
if (!netlink_genl_simple_transact(nl, wlan_family_id, NLM_F_REQUEST | NLM_F_ACK | NLM_F_DUMP, NL80211_CMD_GET_SCAN, 0, scan_prepare, (void*)&wc_all.wlan[i].ifindex, scan_info_cb, w))
return false;
return true;
}
static bool wlan_init80211(struct mnl_socket** nl)
{
if (!(*nl = mnl_socket_open(NETLINK_GENERIC)))
@@ -1755,7 +1840,7 @@ static bool wlan_info_rate_limited(struct mnl_socket* nl, uint16_t wlan_family_i
// do not purge too often to save resources
if (wlan_info_last != now)
{
bres = wlan_info(nl,wlan_family_id,w);
bres = scan_info(nl,wlan_family_id,w);
wlan_info_last = now;
}
return bres;
@@ -1781,10 +1866,6 @@ bool wlan_info_init(void)
}
return true;
}
bool wlan_info_get(void)
{
return wlan_info(nl_wifi, id_nl80211, &wlans);
}
bool wlan_info_get_rate_limited(void)
{
return wlan_info_rate_limited(nl_wifi, id_nl80211, &wlans);

View File

@@ -190,7 +190,6 @@ extern struct wlan_interface_collection wlans;
void wlan_info_deinit(void);
bool wlan_info_init(void);
bool wlan_info_get(void);
bool wlan_info_get_rate_limited(void);
const char *wlan_ssid_search_ifname(const char *ifname);
const char *wlan_ssid_search_ifidx(int ifidx);

View File

@@ -348,15 +348,20 @@ static void process_udp_fail(t_ctrack *ctrack, const t_ctrack_positions *tpos, c
if (!params.server && ctrack && ctrack->dp && ctrack->hostname && ctrack->hostname_ah_check &&
!ctrack->failure_detect_finalized && ctrack->dp->hostlist_auto_udp_out)
{
char client_ip_port[48];
if (!tpos) tpos = &ctrack->pos;
//printf("UDP_POS %u %u\n",tpos->client.pcounter, tpos->server.pcounter);
if (tpos->server.pcounter > ctrack->dp->hostlist_auto_udp_in)
{
// success
ctrack->failure_detect_finalized = true;
fill_client_ip_port(client, client_ip_port, sizeof(client_ip_port));
auto_hostlist_reset_fail_counter(ctrack->dp, ctrack->hostname, client_ip_port, ctrack->l7proto);
}
else if (tpos->client.pcounter >= ctrack->dp->hostlist_auto_udp_out)
{
// failure
char client_ip_port[48];
ctrack->failure_detect_finalized = true;
fill_client_ip_port(client, client_ip_port, sizeof(client_ip_port));
HOSTLIST_DEBUGLOG_APPEND("%s : profile %u (%s) : client %s : proto %s : udp_in %u<=%u udp_out %u>=%u",
@@ -410,6 +415,15 @@ static bool reasm_start(t_ctrack *ctrack, t_reassemble *reasm, uint8_t proto, ui
static bool reasm_client_start(t_ctrack *ctrack, uint8_t proto, size_t sz, size_t szMax, const uint8_t *data_payload, size_t len_payload)
{
if (!ctrack) return false;
if (proto==IPPROTO_TCP && ctrack->pos.server.winsize_calc < sz)
{
// this is rare but possible situation
// server gave us too small tcp window
// client will not send all pieces of reasm
// if we drop packets and wait for next pieces we will see nothing but retransmissions
DLOG("reasm cancelled because server window size %u is smaller than expected reasm size %u\n", ctrack->pos.server.winsize_calc, sz);
return false;
}
return reasm_start(ctrack, &ctrack->reasm_client, proto, (proto == IPPROTO_TCP) ? ctrack->pos.client.seq_last : 0, sz, szMax, data_payload, len_payload);
}
static bool reasm_feed(t_ctrack *ctrack, t_reassemble *reasm, uint8_t proto, uint32_t seq, const uint8_t *data_payload, size_t len_payload)

View File

@@ -112,7 +112,7 @@ int DLOG_FILENAME_VA(const char *filename, const char *format, va_list args)
typedef void (*f_log_function)(int priority, const char *line);
static char log_buf[1024];
static char log_buf[4096];
static size_t log_buf_sz=0;
static void syslog_log_function(int priority, const char *line)
{
@@ -158,11 +158,18 @@ static void android_log_function(int priority, const char *line)
#endif
static void log_buffered(f_log_function log_function, int syslog_priority, const char *format, va_list args)
{
if (vsnprintf(log_buf+log_buf_sz,sizeof(log_buf)-log_buf_sz,format,args)>0)
if (vsnprintf(log_buf+log_buf_sz,sizeof(log_buf)-log_buf_sz-1,format,args)>0)
{
log_buf_sz=strlen(log_buf);
// log when buffer is full or buffer ends with \n
if (log_buf_sz>=(sizeof(log_buf)-1) || (log_buf_sz && log_buf[log_buf_sz-1]=='\n'))
if (log_buf_sz==(sizeof(log_buf)-2))
{
log_buf[log_buf_sz++] = '\n';
log_buf[log_buf_sz] = 0;
log_function(syslog_priority,log_buf);
log_buf_sz = 0;
}
else if (log_buf_sz && log_buf[log_buf_sz-1]=='\n')
{
log_function(syslog_priority,log_buf);
log_buf_sz = 0;

View File

@@ -30,7 +30,7 @@
#define HOSTLIST_AUTO_FAIL_THRESHOLD_DEFAULT 3
#define HOSTLIST_AUTO_FAIL_TIME_DEFAULT 60
#define HOSTLIST_AUTO_RETRANS_THRESHOLD_DEFAULT 3
#define HOSTLIST_AUTO_RETRANS_MAXSEQ 65536
#define HOSTLIST_AUTO_RETRANS_MAXSEQ 32768
#define HOSTLIST_AUTO_INCOMING_MAXSEQ 4096
#define HOSTLIST_AUTO_UDP_OUT 4
#define HOSTLIST_AUTO_UDP_IN 1