mirror of
https://github.com/bol-van/zapret2.git
synced 2026-03-21 16:55:49 +00:00
Compare commits
66 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0aac2965c1 | ||
|
|
d1128a8bc6 | ||
|
|
e016fc0e42 | ||
|
|
f48ea2f6a7 | ||
|
|
2ab71ab895 | ||
|
|
736e0ba3d4 | ||
|
|
f2ae880c11 | ||
|
|
019f3089c6 | ||
|
|
30d28488c9 | ||
|
|
5bcec4aada | ||
|
|
886fbabcfc | ||
|
|
cd8dbf2a2b | ||
|
|
002742bd03 | ||
|
|
dc2c707c3c | ||
|
|
9630d0a9df | ||
|
|
f4c4d5e558 | ||
|
|
7b37880954 | ||
|
|
6b7738ac16 | ||
|
|
8dec014b50 | ||
|
|
b0ee32f3dc | ||
|
|
0e770ff46d | ||
|
|
14b3aef030 | ||
|
|
004c583595 | ||
|
|
c4818a6a32 | ||
|
|
58d57fed01 | ||
|
|
d6b73fe7e0 | ||
|
|
4867838fce | ||
|
|
4b2551509f | ||
|
|
ed6acb36a1 | ||
|
|
26b80e80b6 | ||
|
|
79b776b5a9 | ||
|
|
3b251b9ee6 | ||
|
|
8c65a966d9 | ||
|
|
9da0b13aa3 | ||
|
|
d7fd491121 | ||
|
|
c60ef399ec | ||
|
|
2abab21e4b | ||
|
|
6190babb99 | ||
|
|
7ce0b4a996 | ||
|
|
053556fe2d | ||
|
|
52571045fe | ||
|
|
db875ed1d4 | ||
|
|
e828864811 | ||
|
|
4404127fa3 | ||
|
|
13e81e4b6f | ||
|
|
a631add2d9 | ||
|
|
26b9b63a20 | ||
|
|
90489fad2f | ||
|
|
d93c243d21 | ||
|
|
65235d71d7 | ||
|
|
fc01e6715f | ||
|
|
1a33d68998 | ||
|
|
dfaa475d2a | ||
|
|
743018423a | ||
|
|
762023f201 | ||
|
|
a296b93b7a | ||
|
|
1c9b3aa1bc | ||
|
|
565fa8e337 | ||
|
|
9fcecd07d1 | ||
|
|
652e271877 | ||
|
|
fc7ed4f4a8 | ||
|
|
e9e5bdc860 | ||
|
|
a2b8300219 | ||
|
|
dfdcfbdf51 | ||
|
|
170ec372fb | ||
|
|
3f073908a6 |
@@ -97,17 +97,19 @@ nft_activate_chain4()
|
|||||||
{
|
{
|
||||||
# $1 - chain name
|
# $1 - chain name
|
||||||
# $2 - saddr/daddr
|
# $2 - saddr/daddr
|
||||||
local b rule markf= act
|
local b rule markf= act flt_ifname
|
||||||
[ "$DISABLE_IPV4" = "1" ] || {
|
[ "$DISABLE_IPV4" = "1" ] || {
|
||||||
eval act="\$${1}_act4"
|
eval act="\$${1}_act4"
|
||||||
[ -n "$act" ] && return
|
[ -n "$act" ] && return
|
||||||
|
|
||||||
b=0
|
b=0
|
||||||
nft_wanif_filter_present && b=1
|
nft_wanif_filter_present && b=1
|
||||||
|
flt_ifname="oifname"
|
||||||
|
starts_with "$1" pre && flt_ifname="iifname"
|
||||||
|
|
||||||
[ "$2" = daddr ] && markf=$(nft_mark_filter)
|
[ "$2" = daddr ] && markf=$(nft_mark_filter)
|
||||||
rule="meta mark and $DESYNC_MARK == 0 $markf"
|
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"
|
rule="$rule ip $2 != @nozapret jump $1"
|
||||||
nft_rule_exists ${1}_hook "$rule" || nft_add_rule ${1}_hook $rule
|
nft_rule_exists ${1}_hook "$rule" || nft_add_rule ${1}_hook $rule
|
||||||
|
|
||||||
@@ -118,17 +120,19 @@ nft_activate_chain6()
|
|||||||
{
|
{
|
||||||
# $1 - chain name
|
# $1 - chain name
|
||||||
# $2 - saddr/daddr
|
# $2 - saddr/daddr
|
||||||
local b rule markf=
|
local b rule markf= act flt_ifname
|
||||||
[ "$DISABLE_IPV6" = "1" ] || {
|
[ "$DISABLE_IPV6" = "1" ] || {
|
||||||
eval act="\$${1}_act6"
|
eval act="\$${1}_act6"
|
||||||
[ -n "$act" ] && return
|
[ -n "$act" ] && return
|
||||||
|
|
||||||
b=0
|
b=0
|
||||||
nft_wanif6_filter_present && b=1
|
nft_wanif6_filter_present && b=1
|
||||||
|
flt_ifname="oifname"
|
||||||
|
starts_with "$1" pre && flt_ifname="iifname"
|
||||||
|
|
||||||
[ "$2" = daddr ] && markf=$(nft_mark_filter)
|
[ "$2" = daddr ] && markf=$(nft_mark_filter)
|
||||||
rule="meta mark and $DESYNC_MARK == 0 $markf"
|
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"
|
rule="$rule ip6 $2 != @nozapret6 jump $1"
|
||||||
nft_rule_exists ${1}_hook "$rule" || nft_add_rule ${1}_hook $rule
|
nft_rule_exists ${1}_hook "$rule" || nft_add_rule ${1}_hook $rule
|
||||||
|
|
||||||
|
|||||||
@@ -26,8 +26,10 @@ IPSET_OPT="hashsize 262144 maxelem $SET_MAXELEM"
|
|||||||
IP2NET_OPT4="--prefix-length=22-30 --v4-threshold=3/4"
|
IP2NET_OPT4="--prefix-length=22-30 --v4-threshold=3/4"
|
||||||
IP2NET_OPT6="--prefix-length=56-64 --v6-threshold=5"
|
IP2NET_OPT6="--prefix-length=56-64 --v6-threshold=5"
|
||||||
# options for auto hostlist
|
# 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_INCOMING_MAXSEQ=4096
|
||||||
AUTOHOSTLIST_RETRANS_MAXSEQ=65536
|
AUTOHOSTLIST_RETRANS_MAXSEQ=32768
|
||||||
AUTOHOSTLIST_RETRANS_THRESHOLD=3
|
AUTOHOSTLIST_RETRANS_THRESHOLD=3
|
||||||
AUTOHOSTLIST_FAIL_THRESHOLD=3
|
AUTOHOSTLIST_FAIL_THRESHOLD=3
|
||||||
AUTOHOSTLIST_FAIL_TIME=60
|
AUTOHOSTLIST_FAIL_TIME=60
|
||||||
@@ -64,11 +66,10 @@ NFQWS2_PORTS_TCP=80,443
|
|||||||
NFQWS2_PORTS_UDP=443
|
NFQWS2_PORTS_UDP=443
|
||||||
# PKT_OUT means connbytes dir original
|
# PKT_OUT means connbytes dir original
|
||||||
# PKT_IN means connbytes dir reply
|
# 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=20
|
||||||
NFQWS2_TCP_PKT_OUT=$((6+$AUTOHOSTLIST_RETRANS_THRESHOLD))
|
NFQWS2_TCP_PKT_IN=10
|
||||||
NFQWS2_TCP_PKT_IN=3
|
NFQWS2_UDP_PKT_OUT=5
|
||||||
NFQWS2_UDP_PKT_OUT=$((6+$AUTOHOSTLIST_RETRANS_THRESHOLD))
|
NFQWS2_UDP_PKT_IN=3
|
||||||
NFQWS2_UDP_PKT_IN=0
|
|
||||||
# redirect outgoing traffic without connbytes limiter and incoming with connbytes limiter
|
# 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
|
# normally it's needed only for stateless DPI that matches every packet in a single TCP session
|
||||||
# typical example are plain HTTP keep alives
|
# typical example are plain HTTP keep alives
|
||||||
|
|||||||
@@ -90,3 +90,12 @@ v0.7
|
|||||||
* nfqws2: push desync.track.pos.dt as float with nsec accuracy
|
* nfqws2: push desync.track.pos.dt as float with nsec accuracy
|
||||||
* zapret-auto: override host autostate key in automate_host_record
|
* zapret-auto: override host autostate key in automate_host_record
|
||||||
* nfqws2: rewrite udp autohostlist failure detector logic
|
* 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+
|
||||||
|
|||||||
709
docs/manual.md
709
docs/manual.md
@@ -111,3 +111,712 @@ LUA инстанс может сам себя отключить от получ
|
|||||||
После выполнения всей цепочки инстансов профиля C код получает итоговый вердикт - что делать с текущим диссектом. Отправить как есть, отправить модифицированный вариант или дропнуть.
|
После выполнения всей цепочки инстансов профиля C код получает итоговый вердикт - что делать с текущим диссектом. Отправить как есть, отправить модифицированный вариант или дропнуть.
|
||||||
|
|
||||||
В конце nfqws2 переходит к ожиданию следующего пакета, и цикл повторяется вновь.
|
В конце 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` является пустая строка.
|
||||||
|
|
||||||
|
|||||||
@@ -25,9 +25,9 @@ typedef struct
|
|||||||
// tcp only state, not used in udp
|
// tcp only state, not used in udp
|
||||||
uint32_t seq0; // starting seq and ack
|
uint32_t seq0; // starting seq and ack
|
||||||
uint16_t winsize; // last seen window size
|
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;
|
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;
|
} t_ctrack_position;
|
||||||
|
|
||||||
typedef struct
|
typedef struct
|
||||||
|
|||||||
157
nfq2/darkmagic.c
157
nfq2/darkmagic.c
@@ -40,9 +40,6 @@
|
|||||||
#include <linux/genetlink.h>
|
#include <linux/genetlink.h>
|
||||||
#include <libmnl/libmnl.h>
|
#include <libmnl/libmnl.h>
|
||||||
#include <net/if.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
|
#endif
|
||||||
|
|
||||||
uint32_t net32_add(uint32_t netorder_value, uint32_t cpuorder_increment)
|
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
|
// 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];
|
char buf[MNL_SOCKET_BUFFER_SIZE];
|
||||||
struct nlmsghdr *nlh;
|
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->cmd = cmd;
|
||||||
genl->version = version;
|
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)
|
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;
|
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");
|
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)
|
static uint16_t wlan_get_family_id(struct mnl_socket* nl)
|
||||||
{
|
{
|
||||||
uint16_t id;
|
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)
|
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;
|
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)
|
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;
|
int ret;
|
||||||
struct wlan_interface_collection *wc = (struct wlan_interface_collection*)data;
|
struct wlan_interface_collection *wc = (struct wlan_interface_collection*)data;
|
||||||
if (wc->count>=WLAN_INTERFACE_MAX) return MNL_CB_OK;
|
if (wc->count>=WLAN_INTERFACE_MAX) return MNL_CB_OK;
|
||||||
memset(wc->wlan+wc->count,0,sizeof(wc->wlan[0]));
|
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);
|
ret = mnl_attr_parse(nlh, sizeof(struct genlmsghdr), scan_info_attr_cb, wc->wlan+wc->count);
|
||||||
if (ret>=0 && *wc->wlan[wc->count].ifname && wc->wlan[wc->count].ifindex)
|
if (ret>=0 && *wc->wlan[wc->count].ssid && *wc->wlan[wc->count].ifname && wc->wlan[wc->count].ifindex)
|
||||||
{
|
wc->count++;
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ret;
|
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)
|
static bool wlan_init80211(struct mnl_socket** nl)
|
||||||
{
|
{
|
||||||
if (!(*nl = mnl_socket_open(NETLINK_GENERIC)))
|
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
|
// do not purge too often to save resources
|
||||||
if (wlan_info_last != now)
|
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;
|
wlan_info_last = now;
|
||||||
}
|
}
|
||||||
return bres;
|
return bres;
|
||||||
@@ -1781,10 +1866,6 @@ bool wlan_info_init(void)
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
bool wlan_info_get(void)
|
|
||||||
{
|
|
||||||
return wlan_info(nl_wifi, id_nl80211, &wlans);
|
|
||||||
}
|
|
||||||
bool wlan_info_get_rate_limited(void)
|
bool wlan_info_get_rate_limited(void)
|
||||||
{
|
{
|
||||||
return wlan_info_rate_limited(nl_wifi, id_nl80211, &wlans);
|
return wlan_info_rate_limited(nl_wifi, id_nl80211, &wlans);
|
||||||
|
|||||||
@@ -190,7 +190,6 @@ extern struct wlan_interface_collection wlans;
|
|||||||
|
|
||||||
void wlan_info_deinit(void);
|
void wlan_info_deinit(void);
|
||||||
bool wlan_info_init(void);
|
bool wlan_info_init(void);
|
||||||
bool wlan_info_get(void);
|
|
||||||
bool wlan_info_get_rate_limited(void);
|
bool wlan_info_get_rate_limited(void);
|
||||||
const char *wlan_ssid_search_ifname(const char *ifname);
|
const char *wlan_ssid_search_ifname(const char *ifname);
|
||||||
const char *wlan_ssid_search_ifidx(int ifidx);
|
const char *wlan_ssid_search_ifidx(int ifidx);
|
||||||
|
|||||||
@@ -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 &&
|
if (!params.server && ctrack && ctrack->dp && ctrack->hostname && ctrack->hostname_ah_check &&
|
||||||
!ctrack->failure_detect_finalized && ctrack->dp->hostlist_auto_udp_out)
|
!ctrack->failure_detect_finalized && ctrack->dp->hostlist_auto_udp_out)
|
||||||
{
|
{
|
||||||
|
char client_ip_port[48];
|
||||||
|
|
||||||
if (!tpos) tpos = &ctrack->pos;
|
if (!tpos) tpos = &ctrack->pos;
|
||||||
//printf("UDP_POS %u %u\n",tpos->client.pcounter, tpos->server.pcounter);
|
//printf("UDP_POS %u %u\n",tpos->client.pcounter, tpos->server.pcounter);
|
||||||
if (tpos->server.pcounter > ctrack->dp->hostlist_auto_udp_in)
|
if (tpos->server.pcounter > ctrack->dp->hostlist_auto_udp_in)
|
||||||
|
{
|
||||||
// success
|
// success
|
||||||
ctrack->failure_detect_finalized = true;
|
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)
|
else if (tpos->client.pcounter >= ctrack->dp->hostlist_auto_udp_out)
|
||||||
{
|
{
|
||||||
// failure
|
// failure
|
||||||
char client_ip_port[48];
|
|
||||||
ctrack->failure_detect_finalized = true;
|
ctrack->failure_detect_finalized = true;
|
||||||
fill_client_ip_port(client, client_ip_port, sizeof(client_ip_port));
|
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",
|
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)
|
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 (!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);
|
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)
|
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)
|
||||||
|
|||||||
@@ -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);
|
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 size_t log_buf_sz=0;
|
||||||
static void syslog_log_function(int priority, const char *line)
|
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
|
#endif
|
||||||
static void log_buffered(f_log_function log_function, int syslog_priority, const char *format, va_list args)
|
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_buf_sz=strlen(log_buf);
|
||||||
// log when buffer is full or buffer ends with \n
|
// 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_function(syslog_priority,log_buf);
|
||||||
log_buf_sz = 0;
|
log_buf_sz = 0;
|
||||||
|
|||||||
@@ -30,7 +30,7 @@
|
|||||||
#define HOSTLIST_AUTO_FAIL_THRESHOLD_DEFAULT 3
|
#define HOSTLIST_AUTO_FAIL_THRESHOLD_DEFAULT 3
|
||||||
#define HOSTLIST_AUTO_FAIL_TIME_DEFAULT 60
|
#define HOSTLIST_AUTO_FAIL_TIME_DEFAULT 60
|
||||||
#define HOSTLIST_AUTO_RETRANS_THRESHOLD_DEFAULT 3
|
#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_INCOMING_MAXSEQ 4096
|
||||||
#define HOSTLIST_AUTO_UDP_OUT 4
|
#define HOSTLIST_AUTO_UDP_OUT 4
|
||||||
#define HOSTLIST_AUTO_UDP_IN 1
|
#define HOSTLIST_AUTO_UDP_IN 1
|
||||||
|
|||||||
Reference in New Issue
Block a user