mirror of
https://github.com/bol-van/zapret.git
synced 2024-11-11 17:29:16 +05:00
autohostlist mode
This commit is contained in:
parent
6f3a814f73
commit
ac574ce2ce
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -89,6 +89,14 @@ filter_apply_ipset_target()
|
||||
filter_apply_ipset_target6 $2
|
||||
}
|
||||
|
||||
reverse_nfqws_rule_stream()
|
||||
{
|
||||
sed -e 's/-o /-i /g' -e 's/--dport /--sport /g' -e 's/--dports /--sports /g' -e 's/ dst$/ src/' -e 's/ dst / src /g' -e 's/--connbytes-dir=original/--connbytes-dir=reply/g' -e "s/-m mark ! --mark $DESYNC_MARK\/$DESYNC_MARK//g"
|
||||
}
|
||||
reverse_nfqws_rule()
|
||||
{
|
||||
echo "$@" | reverse_nfqws_rule_stream
|
||||
}
|
||||
|
||||
prepare_tpws_fw4()
|
||||
{
|
||||
@ -250,14 +258,81 @@ fw_nfqws_post()
|
||||
fw_nfqws_post6 $1 "$3" $4
|
||||
}
|
||||
|
||||
_fw_nfqws_pre4()
|
||||
{
|
||||
# $1 - 1 - add, 0 - del
|
||||
# $2 - iptable filter for ipv4
|
||||
# $3 - queue number
|
||||
# $4 - wan interface names space separated
|
||||
[ "$DISABLE_IPV4" = "1" -o -z "$2" ] || {
|
||||
local i
|
||||
|
||||
ipt_print_op $1 "$2" "nfqws input+forward (qnum $3)"
|
||||
|
||||
rule="$2 $IPSET_EXCLUDE src -j NFQUEUE --queue-num $3 --queue-bypass"
|
||||
if [ -n "$4" ] ; then
|
||||
for i in $4; do
|
||||
# iptables PREROUTING chain is before NAT. not possible to have DNATed ip's there
|
||||
ipt_add_del $1 INPUT -t mangle -i $i $rule
|
||||
ipt_add_del $1 FORWARD -t mangle -i $i $rule
|
||||
done
|
||||
else
|
||||
ipt_add_del $1 INPUT -t mangle $rule
|
||||
ipt_add_del $1 FORWARD -t mangle $rule
|
||||
fi
|
||||
}
|
||||
}
|
||||
_fw_nfqws_pre6()
|
||||
{
|
||||
# $1 - 1 - add, 0 - del
|
||||
# $2 - iptable filter for ipv6
|
||||
# $3 - queue number
|
||||
# $4 - wan interface names space separated
|
||||
[ "$DISABLE_IPV6" = "1" -o -z "$2" ] || {
|
||||
local i
|
||||
|
||||
ipt_print_op $1 "$2" "nfqws input+forward (qnum $3)" 6
|
||||
|
||||
rule="$2 $IPSET_EXCLUDE6 src -j NFQUEUE --queue-num $3 --queue-bypass"
|
||||
if [ -n "$4" ] ; then
|
||||
for i in $4; do
|
||||
# iptables PREROUTING chain is before NAT. not possible to have DNATed ip's there
|
||||
ipt6_add_del $1 INPUT -t mangle -i $i $rule
|
||||
ipt6_add_del $1 FORWARD -t mangle -i $i $rule
|
||||
done
|
||||
else
|
||||
ipt6_add_del $1 INPUT -t mangle $rule
|
||||
ipt6_add_del $1 FORWARD -t mangle $rule
|
||||
fi
|
||||
}
|
||||
}
|
||||
fw_nfqws_pre()
|
||||
{
|
||||
# $1 - 1 - add, 0 - del
|
||||
# $2 - iptable filter for ipv4
|
||||
# $3 - iptable filter for ipv6
|
||||
# $4 - queue number
|
||||
fw_nfqws_pre4 $1 "$2" $4
|
||||
fw_nfqws_pre6 $1 "$3" $4
|
||||
}
|
||||
|
||||
|
||||
zapret_do_firewall_rules_ipt()
|
||||
{
|
||||
local mode="${MODE_OVERRIDE:-$MODE}"
|
||||
|
||||
local first_packet_only="-m connbytes --connbytes-dir=original --connbytes-mode=packets --connbytes 1:4"
|
||||
local first_packet_only="-m connbytes --connbytes-dir=original --connbytes-mode=packets --connbytes 1:"
|
||||
local desync="-m mark ! --mark $DESYNC_MARK/$DESYNC_MARK"
|
||||
local f4 f6 qn qns qn6 qns6
|
||||
local n f4 f6 ff qn qns qn6 qns6
|
||||
|
||||
# autohostlist mode requires incoming traffic sample
|
||||
# always use conntrack packet limiter or nfqws will deal with gigabytes
|
||||
if [ "$MODE_FILTER" = "autohostlist" ]; then
|
||||
n=$((4+${AUTOHOSTLIST_RETRANS_THRESHOLD:-3}))
|
||||
else
|
||||
n=4
|
||||
fi
|
||||
first_packet_only="${first_packet_only}$n"
|
||||
|
||||
case "$mode" in
|
||||
tpws)
|
||||
@ -279,17 +354,23 @@ zapret_do_firewall_rules_ipt()
|
||||
f4="$f4 $first_packet_only"
|
||||
filter_apply_ipset_target4 f4
|
||||
fw_nfqws_post4 $1 "$f4 $desync" $qn
|
||||
[ "$MODE_FILTER" = "autohostlist" ] && fw_nfqws_pre4 $1 "$(reverse_nfqws_rule $f4)" $qn
|
||||
else
|
||||
if [ -n "$qn" ]; then
|
||||
f4="-p tcp --dport 80"
|
||||
ff="$f4"
|
||||
[ "$MODE_HTTP_KEEPALIVE" = "1" ] || f4="$f4 $first_packet_only"
|
||||
ff="$ff $first_packet_only"
|
||||
filter_apply_ipset_target4 f4
|
||||
filter_apply_ipset_target4 ff
|
||||
fw_nfqws_post4 $1 "$f4 $desync" $qn
|
||||
[ "$MODE_FILTER" = "autohostlist" ] && fw_nfqws_pre4 $1 "$(reverse_nfqws_rule $ff)" $qn
|
||||
fi
|
||||
if [ -n "$qns" ]; then
|
||||
f4="-p tcp --dport 443 $first_packet_only"
|
||||
filter_apply_ipset_target4 f4
|
||||
fw_nfqws_post4 $1 "$f4 $desync" $qns
|
||||
[ "$MODE_FILTER" = "autohostlist" ] && fw_nfqws_pre4 $1 "$(reverse_nfqws_rule $f4)" $qns
|
||||
fi
|
||||
fi
|
||||
if [ "$MODE_HTTP_KEEPALIVE" != "1" ] && [ -n "$qn6" ] && [ "$qn6" = "$qns6" ]; then
|
||||
@ -297,17 +378,23 @@ zapret_do_firewall_rules_ipt()
|
||||
f6="$f6 $first_packet_only"
|
||||
filter_apply_ipset_target6 f6
|
||||
fw_nfqws_post6 $1 "$f6 $desync" $qn6
|
||||
[ "$MODE_FILTER" = "autohostlist" ] && fw_nfqws_pre6 $1 "$(reverse_nfqws_rule $f6)" $qn
|
||||
else
|
||||
if [ -n "$qn6" ]; then
|
||||
f6="-p tcp --dport 80"
|
||||
ff="$f6"
|
||||
[ "$MODE_HTTP_KEEPALIVE" = "1" ] || f6="$f6 $first_packet_only"
|
||||
ff="$ff $first_packet_only"
|
||||
filter_apply_ipset_target6 f6
|
||||
filter_apply_ipset_target6 ff
|
||||
fw_nfqws_post6 $1 "$f6 $desync" $qn6
|
||||
[ "$MODE_FILTER" = "autohostlist" ] && fw_nfqws_pre6 $1 "$(reverse_nfqws_rule $ff)" $qn6
|
||||
fi
|
||||
if [ -n "$qns6" ]; then
|
||||
f6="-p tcp --dport 443 $first_packet_only"
|
||||
filter_apply_ipset_target6 f6
|
||||
fw_nfqws_post6 $1 "$f6 $desync" $qns6
|
||||
[ "$MODE_FILTER" = "autohostlist" ] && fw_nfqws_pre6 $1 "$(reverse_nfqws_rule $f6)" $qns6
|
||||
fi
|
||||
fi
|
||||
|
||||
|
@ -1,3 +1,7 @@
|
||||
set_conntrack_liberal_mode()
|
||||
{
|
||||
[ -n "$SKIP_CONNTRACK_LIBERAL_MODE" ] || sysctl -w net.netfilter.nf_conntrack_tcp_be_liberal=$1
|
||||
}
|
||||
zapret_do_firewall()
|
||||
{
|
||||
linux_fwtype
|
||||
@ -14,6 +18,13 @@ zapret_do_firewall()
|
||||
;;
|
||||
esac
|
||||
|
||||
# russian DPI sends RST,ACK with wrong ACK.
|
||||
# this is sometimes treated by conntrack as invalid and connbytes fw rules do not pass RST packet to nfqws.
|
||||
# swith on liberal mode on zapret firewall start and switch off on zapret firewall stop
|
||||
# this is only required for processing incoming bad RSTs. incoming rules are only applied in autohostlist mode
|
||||
# calling this after firewall because conntrack module can be not loaded before applying conntrack firewall rules
|
||||
[ "$MODE_FILTER" = "autohostlist" -a "$MODE" != tpws-socks ] && set_conntrack_liberal_mode $1
|
||||
|
||||
[ "$1" = 1 -a -n "$INIT_FW_POST_UP_HOOK" ] && $INIT_FW_POST_UP_HOOK
|
||||
[ "$1" = 0 -a -n "$INIT_FW_POST_DOWN_HOOK" ] && $INIT_FW_POST_DOWN_HOOK
|
||||
|
||||
|
@ -13,13 +13,15 @@ find_hostlists()
|
||||
HOSTLIST_EXCLUDE="$HOSTLIST_BASE/zapret-hosts-user-exclude.txt.gz"
|
||||
[ -f "$HOSTLIST_EXCLUDE" ] || HOSTLIST_EXCLUDE="$HOSTLIST_BASE/zapret-hosts-user-exclude.txt"
|
||||
[ -f "$HOSTLIST_EXCLUDE" ] || HOSTLIST_EXCLUDE=
|
||||
|
||||
HOSTLIST_AUTO="$HOSTLIST_BASE/zapret-hosts-auto.txt"
|
||||
}
|
||||
|
||||
filter_apply_hostlist_target()
|
||||
{
|
||||
# $1 - var name of tpws or nfqws params
|
||||
|
||||
[ "$MODE_FILTER" = "hostlist" ] || return
|
||||
[ "$MODE_FILTER" = "hostlist" -o "$MODE_FILTER" = "autohostlist" ] || return
|
||||
|
||||
local HOSTLIST_BASE HOSTLIST HOSTLIST_USER HOSTLIST_EXCLUDE
|
||||
|
||||
@ -28,4 +30,11 @@ filter_apply_hostlist_target()
|
||||
[ -n "$HOSTLIST" ] && eval $1="\"\$$1 --hostlist=$HOSTLIST\""
|
||||
[ -n "$HOSTLIST_USER" ] && eval $1="\"\$$1 --hostlist=$HOSTLIST_USER\""
|
||||
[ -n "$HOSTLIST_EXCLUDE" ] && eval $1="\"\$$1 --hostlist-exclude=$HOSTLIST_EXCLUDE\""
|
||||
[ "$MODE_FILTER" = "autohostlist" ] && {
|
||||
local parm1="${AUTOHOSTLIST_FAIL_THRESHOLD:+--hostlist-auto-fail-threshold=$AUTOHOSTLIST_FAIL_THRESHOLD}"
|
||||
local parm2="${AUTOHOSTLIST_FAIL_TIME:+--hostlist-auto-fail-time=$AUTOHOSTLIST_FAIL_TIME}"
|
||||
local parm3
|
||||
[ "$MODE" = "tpws" -o "$MODE" = "tpws-socks" ] || parm3="${AUTOHOSTLIST_RETRANS_THRESHOLD:+--hostlist-auto-retrans-threshold=$AUTOHOSTLIST_RETRANS_THRESHOLD}"
|
||||
eval $1="\"\$$1 --hostlist-auto=$HOSTLIST_AUTO $parm1 $parm2 $parm3\""
|
||||
}
|
||||
}
|
||||
|
@ -63,10 +63,8 @@ nft_del_all_chains_from_table()
|
||||
|
||||
nft_create_chains()
|
||||
{
|
||||
# NOTE : postrouting hook has priority 101 to hook packets already processed by nat
|
||||
# NOTE : this is the only way to implement ipfrag for forwarded packets if nat is present
|
||||
# NOTE : to avoid retracking and sport changing use notrack for nfqws generated packets
|
||||
# NOTE : this also prevents defragmentation of ipv6 fragmented frames in kernels 4.16+
|
||||
# NOTE : postrouting hook has priority 99 to hook packets with original source but NATed destination
|
||||
# NOTE : prerouting hook has priority -99 for the same reason
|
||||
cat << EOF | nft -f -
|
||||
add chain inet $ZAPRET_NFT_TABLE dnat_output { type nat hook output priority -101; }
|
||||
flush chain inet $ZAPRET_NFT_TABLE dnat_output
|
||||
@ -84,6 +82,7 @@ cat << EOF | nft -f -
|
||||
add rule inet $ZAPRET_NFT_TABLE localnet_protect ip daddr 127.0.0.0/8 drop comment "route_localnet remote access protection"
|
||||
add rule inet $ZAPRET_NFT_TABLE input iif != lo jump localnet_protect
|
||||
add chain inet $ZAPRET_NFT_TABLE postrouting { type filter hook postrouting priority 99; }
|
||||
add chain inet $ZAPRET_NFT_TABLE prerouting { type filter hook prerouting priority -99; }
|
||||
flush chain inet $ZAPRET_NFT_TABLE postrouting
|
||||
add set inet $ZAPRET_NFT_TABLE lanif { type ifname; }
|
||||
add set inet $ZAPRET_NFT_TABLE wanif { type ifname; }
|
||||
@ -109,6 +108,7 @@ cat << EOF | nft -f - 2>/dev/null
|
||||
delete chain inet $ZAPRET_NFT_TABLE forward
|
||||
delete chain inet $ZAPRET_NFT_TABLE input
|
||||
delete chain inet $ZAPRET_NFT_TABLE postrouting
|
||||
delete chain inet $ZAPRET_NFT_TABLE prerouting
|
||||
delete chain inet $ZAPRET_NFT_TABLE flow_offload
|
||||
delete chain inet $ZAPRET_NFT_TABLE localnet_protect
|
||||
EOF
|
||||
@ -202,7 +202,7 @@ nft_reverse_nfqws_rule()
|
||||
}
|
||||
nft_clean_nfqws_rule()
|
||||
{
|
||||
echo "$@" | sed -e "s/mark and $DESYNC_MARK == 0//g"
|
||||
echo "$@" | sed -e "s/mark and $DESYNC_MARK == 0//g" -e "s/oifname @wanif6//g" -e "s/oifname @wanif//g"
|
||||
}
|
||||
nft_add_nfqws_flow_exempt_rule()
|
||||
{
|
||||
@ -464,6 +464,64 @@ nft_fw_nfqws_post()
|
||||
nft_fw_nfqws_post6 "$2" $3
|
||||
}
|
||||
|
||||
_nft_fw_nfqws_pre4()
|
||||
{
|
||||
# $1 - filter ipv4
|
||||
# $2 - queue number
|
||||
# $3 - not-empty if wan interface filtering required
|
||||
|
||||
[ "$DISABLE_IPV4" = "1" -o -z "$1" ] || {
|
||||
local filter="$1" port="$2" rule
|
||||
nft_print_op "$filter" "nfqws prerouting (qnum $port)" 4
|
||||
rule="${3:+iifname @wanif }$filter ip saddr != @nozapret"
|
||||
nft_add_rule prerouting $rule queue num $port bypass
|
||||
}
|
||||
}
|
||||
_nft_fw_nfqws_pre6()
|
||||
{
|
||||
# $1 - filter ipv6
|
||||
# $2 - queue number
|
||||
# $3 - not-empty if wan interface filtering required
|
||||
|
||||
[ "$DISABLE_IPV6" = "1" -o -z "$1" ] || {
|
||||
local filter="$1" port="$2" rule
|
||||
nft_print_op "$filter" "nfqws prerouting (qnum $port)" 6
|
||||
rule="${3:+iifname @wanif6 }$filter ip6 saddr != @nozapret6"
|
||||
nft_add_rule prerouting $rule queue num $port bypass
|
||||
}
|
||||
}
|
||||
nft_fw_nfqws_pre()
|
||||
{
|
||||
# $1 - filter ipv4
|
||||
# $2 - filter ipv6
|
||||
# $3 - queue number
|
||||
|
||||
nft_fw_nfqws_pre4 "$1" $3
|
||||
nft_fw_nfqws_pre6 "$2" $3
|
||||
}
|
||||
|
||||
nft_fw_nfqws_both4()
|
||||
{
|
||||
# $1 - filter ipv4
|
||||
# $2 - queue number
|
||||
nft_fw_nfqws_post4 "$@"
|
||||
nft_fw_nfqws_pre4 "$(nft_reverse_nfqws_rule $1)" $2
|
||||
}
|
||||
nft_fw_nfqws_both6()
|
||||
{
|
||||
# $1 - filter ipv6
|
||||
# $2 - queue number
|
||||
nft_fw_nfqws_post6 "$@"
|
||||
nft_fw_nfqws_pre6 "$(nft_reverse_nfqws_rule $1)" $2
|
||||
}
|
||||
nft_fw_nfqws_both()
|
||||
{
|
||||
# $1 - filter ipv4
|
||||
# $2 - filter ipv6
|
||||
# $3 - queue number
|
||||
nft_fw_nfqws_both4 "$1" "$3"
|
||||
nft_fw_nfqws_both6 "$2" "$3"
|
||||
}
|
||||
|
||||
zapret_reload_ifsets()
|
||||
{
|
||||
@ -485,9 +543,18 @@ zapret_apply_firewall_rules_nft()
|
||||
{
|
||||
local mode="${MODE_OVERRIDE:-$MODE}"
|
||||
|
||||
local first_packet_only="ct original packets 1-4"
|
||||
local first_packet_only
|
||||
local desync="mark and $DESYNC_MARK == 0"
|
||||
local f4 f6 qn qns qn6 qns6
|
||||
local f4 f6 ff qn qns qn6 qns6
|
||||
|
||||
# autohostlist mode requires incoming traffic sample
|
||||
# always use conntrack packet limiter or nfqws will deal with gigabytes
|
||||
if [ "$MODE_FILTER" = "autohostlist" ]; then
|
||||
first_packet_only=$((4+${AUTOHOSTLIST_RETRANS_THRESHOLD:-3}))
|
||||
else
|
||||
first_packet_only=4
|
||||
fi
|
||||
first_packet_only="ct original packets 1-$first_packet_only"
|
||||
|
||||
case "$mode" in
|
||||
tpws)
|
||||
@ -508,17 +575,23 @@ zapret_apply_firewall_rules_nft()
|
||||
f4="$f4 $first_packet_only"
|
||||
nft_filter_apply_ipset_target4 f4
|
||||
nft_fw_nfqws_post4 "$f4 $desync" $qn
|
||||
[ "$MODE_FILTER" = "autohostlist" ] && nft_fw_nfqws_pre4 "$(nft_reverse_nfqws_rule $f4)" $qn
|
||||
else
|
||||
if [ -n "$qn" ]; then
|
||||
f4="tcp dport 80"
|
||||
ff="$f4"
|
||||
[ "$MODE_HTTP_KEEPALIVE" = "1" ] || f4="$f4 $first_packet_only"
|
||||
ff="$ff $first_packet_only"
|
||||
nft_filter_apply_ipset_target4 f4
|
||||
nft_filter_apply_ipset_target4 ff
|
||||
nft_fw_nfqws_post4 "$f4 $desync" $qn
|
||||
[ "$MODE_FILTER" = "autohostlist" ] && nft_fw_nfqws_pre4 "$(nft_reverse_nfqws_rule $ff)" $qn
|
||||
fi
|
||||
if [ -n "$qns" ]; then
|
||||
f4="tcp dport 443 $first_packet_only"
|
||||
nft_filter_apply_ipset_target4 f4
|
||||
nft_fw_nfqws_post4 "$f4 $desync" $qns
|
||||
[ "$MODE_FILTER" = "autohostlist" ] && nft_fw_nfqws_pre4 "$(nft_reverse_nfqws_rule $f4)" $qns
|
||||
fi
|
||||
fi
|
||||
if [ "$MODE_HTTP_KEEPALIVE" != "1" ] && [ -n "$qn6" ] && [ "$qn6" = "$qns6" ]; then
|
||||
@ -526,17 +599,23 @@ zapret_apply_firewall_rules_nft()
|
||||
f6="$f6 $first_packet_only"
|
||||
nft_filter_apply_ipset_target6 f6
|
||||
nft_fw_nfqws_post6 "$f6 $desync" $qn6
|
||||
[ "$MODE_FILTER" = "autohostlist" ] && nft_fw_nfqws_pre6 "$(nft_reverse_nfqws_rule $f6)" $qn
|
||||
else
|
||||
if [ -n "$qn6" ]; then
|
||||
f6="tcp dport 80"
|
||||
ff="$f6"
|
||||
[ "$MODE_HTTP_KEEPALIVE" = "1" ] || f6="$f6 $first_packet_only"
|
||||
ff="$ff $first_packet_only"
|
||||
nft_filter_apply_ipset_target6 f6
|
||||
nft_filter_apply_ipset_target6 ff
|
||||
nft_fw_nfqws_post6 "$f6 $desync" $qn6
|
||||
[ "$MODE_FILTER" = "autohostlist" ] && nft_fw_nfqws_pre6 "$(nft_reverse_nfqws_rule $ff)" $qn6
|
||||
fi
|
||||
if [ -n "$qns6" ]; then
|
||||
f6="tcp dport 443 $first_packet_only"
|
||||
nft_filter_apply_ipset_target6 f6
|
||||
nft_fw_nfqws_post6 "$f6 $desync" $qns6
|
||||
[ "$MODE_FILTER" = "autohostlist" ] && nft_fw_nfqws_pre6 "$(nft_reverse_nfqws_rule $f6)" $qns6
|
||||
fi
|
||||
fi
|
||||
|
||||
|
@ -1,3 +1,12 @@
|
||||
apply_unspecified_desync_modes()
|
||||
{
|
||||
NFQWS_OPT_DESYNC_HTTP="${NFQWS_OPT_DESYNC_HTTP:-$NFQWS_OPT_DESYNC}"
|
||||
NFQWS_OPT_DESYNC_HTTPS="${NFQWS_OPT_DESYNC_HTTPS:-$NFQWS_OPT_DESYNC}"
|
||||
NFQWS_OPT_DESYNC_HTTP6="${NFQWS_OPT_DESYNC_HTTP6:-$NFQWS_OPT_DESYNC_HTTP}"
|
||||
NFQWS_OPT_DESYNC_HTTPS6="${NFQWS_OPT_DESYNC_HTTPS6:-$NFQWS_OPT_DESYNC_HTTPS}"
|
||||
NFQWS_OPT_DESYNC_QUIC6="${NFQWS_OPT_DESYNC_QUIC6:-$NFQWS_OPT_DESYNC_QUIC}"
|
||||
}
|
||||
|
||||
get_nfqws_qnums()
|
||||
{
|
||||
# $1 - var name for ipv4 http
|
||||
|
@ -14,6 +14,11 @@ iptables -t mangle -I POSTROUTING -p tcp -m multiport --dports 80,443 -m connbyt
|
||||
iptables -t mangle -I POSTROUTING -p tcp --dport 443 -m mark ! --mark 0x40000000/0x40000000 -j NFQUEUE --queue-num 200 --queue-bypass
|
||||
iptables -t mangle -I POSTROUTING -p udp --dport 443 -m mark ! --mark 0x40000000/0x40000000 -j NFQUEUE --queue-num 200 --queue-bypass
|
||||
|
||||
# auto hostlist with avoiding wrong ACK numbers in RST,ACK packets sent by russian DPI
|
||||
sysctl net.netfilter.nf_conntrack_tcp_be_liberal=1
|
||||
iptables -t mangle -I POSTROUTING -p tcp -m multiport --dports 80,443 -m connbytes --connbytes-dir=original --connbytes-mode=packets --connbytes 1:12 -m mark ! --mark 0x40000000/0x40000000 -j NFQUEUE --queue-num 200 --queue-bypass
|
||||
iptables -t mangle -I PREROUTING -p tcp -m multiport --sports 80,443 -m connbytes --connbytes-dir=reply --connbytes-mode=packets --connbytes 1:4 -m mark ! --mark 0x40000000/0x40000000 -j NFQUEUE --queue-num 200 --queue-bypass
|
||||
|
||||
|
||||
For TPROXY :
|
||||
|
||||
|
@ -19,7 +19,13 @@ For dpi desync attack :
|
||||
nft delete table inet ztest
|
||||
nft create table inet ztest
|
||||
nft add chain inet ztest post "{type filter hook postrouting priority mangle;}"
|
||||
nft add rule inet ztest post tcp dport "{80,443}" queue num 200 bypass
|
||||
nft add rule inet ztest post tcp dport "{80,443}" ct original packets 1-12 queue num 200 bypass
|
||||
nft add rule inet ztest post udp dport 443 ct original packets 1-4 queue num 200 bypass
|
||||
|
||||
# auto hostlist with avoiding wrong ACK numbers in RST,ACK packets sent by russian DPI
|
||||
sysctl net.netfilter.nf_conntrack_tcp_be_liberal=1
|
||||
nft add chain inet ztest pre "{type filter hook prerouting priority filter;}"
|
||||
nft add rule inet ztest pre tcp sport "{80,443}" ct reply packets 1-4 queue num 200 bypass
|
||||
|
||||
|
||||
show rules : nft list table inet ztest
|
||||
|
@ -116,7 +116,7 @@ SOXIFIER_PORT=1099
|
||||
|
||||
create_ipset no-update
|
||||
|
||||
network_find_wan_all wan_iface
|
||||
network_find_wan4_all wan_iface
|
||||
for ext_iface in $wan_iface; do
|
||||
network_get_device ext_device $ext_iface
|
||||
ipt OUTPUT -t nat -o $ext_device -p tcp --dport 443 -m set --match-set zapret dst -m set ! --match-set nozapret dst -j REDIRECT --to-port $SOXIFIER_PORT
|
||||
|
@ -268,7 +268,7 @@ OUTPUT относится к исходящим с роутера пакетам
|
||||
|
||||
create_ipset no-update
|
||||
|
||||
network_find_wan_all wan_iface
|
||||
network_find_wan4_all wan_iface
|
||||
for ext_iface in $wan_iface; do
|
||||
network_get_device DEVICE $ext_iface
|
||||
ipt OUTPUT -t mangle -o $DEVICE -p tcp --dport 443 -m set --match-set zapret dst -m set ! --match-set nozapret dst -j MARK --set-mark 0x800/0x800
|
||||
@ -414,7 +414,7 @@ connmark относится к соединению, fwmark - к пакету.
|
||||
|
||||
create_ipset no-update
|
||||
|
||||
network_find_wan_all wan_iface
|
||||
network_find_wan4_all wan_iface
|
||||
for ext_iface in $wan_iface; do
|
||||
network_get_device DEVICE $ext_iface
|
||||
ipt OUTPUT -t mangle -o $DEVICE -p tcp --dport 443 -m set --match-set zapret dst -m set ! --match-set nozapret dst -j MARK --set-mark 0x800/0x800
|
||||
|
@ -30,12 +30,7 @@ CUSTOM_SCRIPT="$ZAPRET_BASE/init.d/openwrt/custom"
|
||||
IPSET_EXCLUDE="-m set ! --match-set nozapret"
|
||||
IPSET_EXCLUDE6="-m set ! --match-set nozapret6"
|
||||
|
||||
NFQWS_OPT_DESYNC_HTTP="${NFQWS_OPT_DESYNC_HTTP:-$NFQWS_OPT_DESYNC}"
|
||||
NFQWS_OPT_DESYNC_HTTPS="${NFQWS_OPT_DESYNC_HTTPS:-$NFQWS_OPT_DESYNC}"
|
||||
NFQWS_OPT_DESYNC_HTTP6="${NFQWS_OPT_DESYNC_HTTP6:-$NFQWS_OPT_DESYNC_HTTP}"
|
||||
NFQWS_OPT_DESYNC_HTTPS6="${NFQWS_OPT_DESYNC_HTTPS6:-$NFQWS_OPT_DESYNC_HTTPS}"
|
||||
NFQWS_OPT_DESYNC_QUIC6="${NFQWS_OPT_DESYNC_QUIC6:-$NFQWS_OPT_DESYNC_QUIC}"
|
||||
|
||||
apply_unspecified_desync_modes
|
||||
|
||||
|
||||
# can be multiple ipv6 outgoing interfaces
|
||||
@ -60,6 +55,15 @@ network_find_wan6_all()
|
||||
__network_ifstatus "$1" "" "[@.route[@.target='::' && !@.table]].interface" "" 10 2>/dev/null && return
|
||||
network_find_wan6 $1
|
||||
}
|
||||
network_find_wanX_devices()
|
||||
{
|
||||
# $1 - ip version: 4 or 6
|
||||
# $2 - variable to put result to
|
||||
local ifaces
|
||||
network_find_wan${1}_all ifaces
|
||||
call_for_multiple_items network_get_device $2 "$ifaces"
|
||||
}
|
||||
|
||||
|
||||
dnat6_target()
|
||||
{
|
||||
@ -87,25 +91,35 @@ set_route_localnet()
|
||||
}
|
||||
|
||||
|
||||
fw_nfqws_post_x()
|
||||
fw_nfqws_prepost_x()
|
||||
{
|
||||
# $1 - 1 - add, 0 - del
|
||||
# $2 - filter
|
||||
# $3 - queue number
|
||||
# $4 - ip version : 4 or 6
|
||||
# $4 - 4/6
|
||||
# $5 - post/pre
|
||||
|
||||
local ifaces DWAN
|
||||
network_find_wan${4}_all ifaces
|
||||
call_for_multiple_items network_get_device DWAN "$ifaces"
|
||||
|
||||
[ -n "$DWAN" ] && _fw_nfqws_post${4} $1 "$2" $3 "$(unique $DWAN)"
|
||||
[ -n "$DWAN" ] && _fw_nfqws_${5}${4} $1 "$2" $3 "$(unique $DWAN)"
|
||||
}
|
||||
fw_nfqws_post4()
|
||||
{
|
||||
fw_nfqws_post_x $1 "$2" $3 4
|
||||
fw_nfqws_prepost_x $1 "$2" $3 4 post
|
||||
}
|
||||
fw_nfqws_post6()
|
||||
{
|
||||
fw_nfqws_post_x $1 "$2" $3 6
|
||||
fw_nfqws_prepost_x $1 "$2" $3 6 post
|
||||
}
|
||||
fw_nfqws_pre4()
|
||||
{
|
||||
fw_nfqws_prepost_x $1 "$2" $3 4 pre
|
||||
}
|
||||
fw_nfqws_pre6()
|
||||
{
|
||||
fw_nfqws_prepost_x $1 "$2" $3 6 pre
|
||||
}
|
||||
fw_tpws_x()
|
||||
{
|
||||
@ -146,10 +160,6 @@ list_nfqws_rules()
|
||||
grep -E "NFQUEUE --queue-num $QNUM --queue-bypass|NFQUEUE --queue-num $(($QNUM+1)) --queue-bypass|NFQUEUE --queue-num $(($QNUM+2)) --queue-bypass|NFQUEUE --queue-num $(($QNUM+3)) --queue-bypass|NFQUEUE --queue-num $(($QNUM+10)) --queue-bypass|NFQUEUE --queue-num $(($QNUM+11)) --queue-bypass" | \
|
||||
sed -re 's/^-A POSTROUTING (.*) -j NFQUEUE.*$/\1/' -e "s/-m mark ! --mark $DESYNC_MARK\/$DESYNC_MARK//"
|
||||
}
|
||||
reverse_nfqws_rule()
|
||||
{
|
||||
sed -e 's/-o /-i /g' -e 's/--dport /--sport /g' -e 's/--dports /--sports /g' -e 's/ dst$/ src/' -e 's/ dst / src /g'
|
||||
}
|
||||
apply_flow_offloading_enable_rule()
|
||||
{
|
||||
# $1 = '' for ipv4, '6' for ipv6
|
||||
@ -171,14 +181,16 @@ apply_flow_offloading_exempt_rule()
|
||||
}
|
||||
flow_offloading_unexempt_v()
|
||||
{
|
||||
ipt$1_del FORWARD -j forwarding_rule_zapret
|
||||
# $1 = '' for ipv4, '6' for ipv6
|
||||
local DWAN
|
||||
network_find_wanX_devices ${1:-4} DWAN
|
||||
for i in $DWAN; do ipt$1_del FORWARD -o $i -j forwarding_rule_zapret ; done
|
||||
ip$1tables -F forwarding_rule_zapret 2>/dev/null
|
||||
ip$1tables -X forwarding_rule_zapret 2>/dev/null
|
||||
}
|
||||
flow_offloading_exempt_v()
|
||||
{
|
||||
# $1 = '' for ipv4, '6' for ipv6
|
||||
|
||||
is_ipt_flow_offload_avail $1 || return 0
|
||||
|
||||
flow_offloading_unexempt_v $1
|
||||
@ -186,21 +198,19 @@ flow_offloading_exempt_v()
|
||||
[ "$FLOWOFFLOAD" = 'software' -o "$FLOWOFFLOAD" = 'hardware' ] && {
|
||||
ip$1tables -N forwarding_rule_zapret
|
||||
|
||||
list_nfqws_rules $1 |
|
||||
while read rule; do
|
||||
apply_flow_offloading_exempt_rule "$1" $rule
|
||||
done
|
||||
|
||||
list_nfqws_rules $1 | grep -v "connbytes" | reverse_nfqws_rule |
|
||||
# remove outgoing interface
|
||||
list_nfqws_rules $1 | sed -re 's/-o +[^ ]+//g' |
|
||||
while read rule; do
|
||||
apply_flow_offloading_exempt_rule "$1" $rule
|
||||
done
|
||||
|
||||
apply_flow_offloading_enable_rule $1
|
||||
|
||||
ipt$1 FORWARD -j forwarding_rule_zapret
|
||||
# only outgoing to WAN packets trigger flow offloading
|
||||
local DWAN
|
||||
network_find_wanX_devices ${1:-4} DWAN
|
||||
for i in $DWAN; do ipt$1 FORWARD -o $i -j forwarding_rule_zapret; done
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
flow_offloading_exempt()
|
||||
@ -252,3 +262,11 @@ nft_fw_nfqws_post6()
|
||||
{
|
||||
_nft_fw_nfqws_post6 "$1" $2 always_apply_wan_filter
|
||||
}
|
||||
nft_fw_nfqws_pre4()
|
||||
{
|
||||
_nft_fw_nfqws_pre4 "$1" $2 always_apply_wan_filter
|
||||
}
|
||||
nft_fw_nfqws_pre6()
|
||||
{
|
||||
_nft_fw_nfqws_pre6 "$1" $2 always_apply_wan_filter
|
||||
}
|
||||
|
@ -67,10 +67,7 @@ IPSET_CR="$ZAPRET_BASE/ipset/create_ipset.sh"
|
||||
[ -n "$QNUM" ] || QNUM=200
|
||||
[ -n "$NFQWS" ] || NFQWS="$ZAPRET_BASE/nfq/nfqws"
|
||||
NFQWS_OPT_BASE="$USEROPT --dpi-desync-fwmark=$DESYNC_MARK"
|
||||
NFQWS_OPT_DESYNC_HTTP="${NFQWS_OPT_DESYNC_HTTP:-$NFQWS_OPT_DESYNC}"
|
||||
NFQWS_OPT_DESYNC_HTTPS="${NFQWS_OPT_DESYNC_HTTPS:-$NFQWS_OPT_DESYNC}"
|
||||
NFQWS_OPT_DESYNC_HTTP6="${NFQWS_OPT_DESYNC_HTTP6:-$NFQWS_OPT_DESYNC_HTTP}"
|
||||
NFQWS_OPT_DESYNC_HTTPS6="${NFQWS_OPT_DESYNC_HTTPS6:-$NFQWS_OPT_DESYNC_HTTPS}"
|
||||
apply_unspecified_desync_modes
|
||||
|
||||
[ -n "$TPPORT" ] || TPPORT=988
|
||||
[ -n "$TPWS" ] || TPWS="$ZAPRET_BASE/tpws/tpws"
|
||||
@ -113,6 +110,14 @@ fw_nfqws_post6()
|
||||
{
|
||||
_fw_nfqws_post6 $1 "$2" $3 "${IFACE_WAN6:-$IFACE_WAN}"
|
||||
}
|
||||
fw_nfqws_pre4()
|
||||
{
|
||||
_fw_nfqws_pre4 $1 "$2" $3 "$IFACE_WAN"
|
||||
}
|
||||
fw_nfqws_pre6()
|
||||
{
|
||||
_fw_nfqws_pre6 $1 "$2" $3 "${IFACE_WAN6:-$IFACE_WAN}"
|
||||
}
|
||||
fw_tpws4()
|
||||
{
|
||||
_fw_tpws4 $1 "$2" $3 "$IFACE_LAN" "$IFACE_WAN"
|
||||
@ -137,6 +142,14 @@ nft_fw_nfqws_post6()
|
||||
{
|
||||
_nft_fw_nfqws_post6 "$1" $2 "${IFACE_WAN6:-$IFACE_WAN}"
|
||||
}
|
||||
nft_fw_nfqws_pre4()
|
||||
{
|
||||
_nft_fw_nfqws_pre4 "$1" $2 "$IFACE_WAN"
|
||||
}
|
||||
nft_fw_nfqws_pre6()
|
||||
{
|
||||
_nft_fw_nfqws_pre6 "$1" $2 "${IFACE_WAN6:-$IFACE_WAN}"
|
||||
}
|
||||
nft_fill_ifsets_overload()
|
||||
{
|
||||
nft_fill_ifsets "$IFACE_LAN" "$IFACE_WAN" "${IFACE_WAN6:-$IFACE_WAN}"
|
||||
|
@ -225,8 +225,8 @@ select_mode_quic()
|
||||
}
|
||||
select_mode_filter()
|
||||
{
|
||||
local filter="none ipset hostlist"
|
||||
[ "$MODE" = "tpws-socks" ] && filter="none hostlist"
|
||||
local filter="none ipset hostlist autohostlist"
|
||||
[ "$MODE" = "tpws-socks" ] && filter="none hostlist autohostlist"
|
||||
echo
|
||||
echo select filtering :
|
||||
ask_list MODE_FILTER "$filter" none && write_config_var MODE_FILTER
|
||||
@ -487,7 +487,7 @@ backup_restore_settings()
|
||||
{
|
||||
# $1 - 1 - backup, 0 - restore
|
||||
local mode=$1
|
||||
on_off_function _backup_settings _restore_settings $mode "config" "init.d/sysv/custom" "init.d/openwrt/custom" "init.d/macos/custom" "ipset/zapret-hosts-user.txt" "ipset/zapret-hosts-user-exclude.txt" "ipset/zapret-hosts-user-ipban.txt"
|
||||
on_off_function _backup_settings _restore_settings $mode "config" "init.d/sysv/custom" "init.d/openwrt/custom" "init.d/macos/custom" "ipset/zapret-hosts-user.txt" "ipset/zapret-hosts-user-exclude.txt" "ipset/zapret-hosts-user-ipban.txt" "ipset/zapret-hosts-auto.txt"
|
||||
}
|
||||
|
||||
check_location()
|
||||
|
@ -25,11 +25,16 @@ static void connswap(const t_conn *c, t_conn *c2)
|
||||
c2->dport = c->sport;
|
||||
}
|
||||
|
||||
static void ConntrackFreeElem(t_conntrack_pool *elem)
|
||||
{
|
||||
if (elem->track.hostname) free(elem->track.hostname);
|
||||
free(elem);
|
||||
}
|
||||
|
||||
static void ConntrackPoolDestroyPool(t_conntrack_pool **pp)
|
||||
{
|
||||
t_conntrack_pool *elem, *tmp;
|
||||
HASH_ITER(hh, *pp, elem, tmp) { HASH_DEL(*pp, elem); free(elem); }
|
||||
HASH_ITER(hh, *pp, elem, tmp) { HASH_DEL(*pp, elem); ConntrackFreeElem(elem); }
|
||||
}
|
||||
void ConntrackPoolDestroy(t_conntrack *p)
|
||||
{
|
||||
@ -226,7 +231,7 @@ static bool ConntrackPoolDropPool(t_conntrack_pool **pp, const struct ip *ip, co
|
||||
t=ConntrackPoolSearch(*pp,&connswp);
|
||||
}
|
||||
if (!t) return false;
|
||||
HASH_DEL(*pp, t); free(t);
|
||||
HASH_DEL(*pp, t); ConntrackFreeElem(t);
|
||||
return true;
|
||||
}
|
||||
bool ConntrackPoolDrop(t_conntrack *p, const struct ip *ip, const struct ip6_hdr *ip6, const struct tcphdr *tcphdr, const struct udphdr *udphdr)
|
||||
@ -251,7 +256,7 @@ void ConntrackPoolPurge(t_conntrack *p)
|
||||
t->conn.l4proto==IPPROTO_UDP &&
|
||||
tidle>=p->timeout_udp)
|
||||
{
|
||||
HASH_DEL(p->pool, t); free(t);
|
||||
HASH_DEL(p->pool, t); ConntrackFreeElem(t);
|
||||
}
|
||||
}
|
||||
p->t_last_purge = tnow;
|
||||
@ -263,6 +268,18 @@ static void taddr2str(uint8_t l3proto, const t_addr *a, char *buf, size_t bufsiz
|
||||
if (!inet_ntop(family_from_proto(l3proto), a, buf, bufsize) && bufsize) *buf=0;
|
||||
}
|
||||
|
||||
static const char *ConntrackProtoName(t_l7proto proto)
|
||||
{
|
||||
switch(proto)
|
||||
{
|
||||
case HTTP: return "HTTP";
|
||||
case TLS: return "TLS";
|
||||
case QUIC: return "QUIC";
|
||||
case WIREGUARD: return "WIREGUARD";
|
||||
case DHT: return "DHT";
|
||||
default: return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
void ConntrackPoolDump(const t_conntrack *p)
|
||||
{
|
||||
t_conntrack_pool *t, *tmp;
|
||||
@ -271,29 +288,24 @@ void ConntrackPoolDump(const t_conntrack *p)
|
||||
HASH_ITER(hh, p->pool, t, tmp) {
|
||||
taddr2str(t->conn.l3proto, &t->conn.src, sa1, sizeof(sa1));
|
||||
taddr2str(t->conn.l3proto, &t->conn.dst, sa2, sizeof(sa2));
|
||||
if (t->conn.l4proto==IPPROTO_TCP)
|
||||
printf("%s [%s]:%u => [%s]:%u : %s : t0=%llu last=t0+%llu now=last+%llu packets_orig=d%llu/n%llu packets_reply=d%llu/n%llu seq0=%u rseq=%u pos_orig=%u ack0=%u rack=%u pos_reply=%u wsize_orig=%u:%d wsize_reply=%u:%d",
|
||||
printf("%s [%s]:%u => [%s]:%u : %s : t0=%llu last=t0+%llu now=last+%llu packets_orig=d%llu/n%llu packets_reply=d%llu/n%llu ",
|
||||
proto_name(t->conn.l4proto),
|
||||
sa1, t->conn.sport, sa2, t->conn.dport,
|
||||
t->conn.l4proto==IPPROTO_TCP ? connstate_s[t->track.state] : "-",
|
||||
(unsigned long long)t->track.t_start, (unsigned long long)(t->track.t_last - t->track.t_start), (unsigned long long)(tnow - t->track.t_last),
|
||||
(unsigned long long)t->track.pdcounter_orig, (unsigned long long)t->track.pcounter_orig,
|
||||
(unsigned long long)t->track.pdcounter_reply, (unsigned long long)t->track.pcounter_reply,
|
||||
(unsigned long long)t->track.pdcounter_reply, (unsigned long long)t->track.pcounter_reply);
|
||||
if (t->conn.l4proto==IPPROTO_TCP)
|
||||
printf("seq0=%u rseq=%u pos_orig=%u ack0=%u rack=%u pos_reply=%u wsize_orig=%u:%d wsize_reply=%u:%d",
|
||||
t->track.seq0, t->track.seq_last - t->track.seq0, t->track.pos_orig - t->track.seq0,
|
||||
t->track.ack0, t->track.ack_last - t->track.ack0, t->track.pos_reply - t->track.ack0,
|
||||
t->track.winsize_orig, t->track.scale_orig==SCALE_NONE ? -1 : t->track.scale_orig,
|
||||
t->track.winsize_reply, t->track.scale_reply==SCALE_NONE ? -1 : t->track.scale_reply);
|
||||
else
|
||||
printf("%s [%s]:%u => [%s]:%u : %s : t0=%llu last=t0+%llu now=last+%llu packets_orig=d%llu/n%llu packets_reply=d%llu/n%llu rseq=%u pos_orig=%u rack=%u pos_reply=%u",
|
||||
proto_name(t->conn.l4proto),
|
||||
sa1, t->conn.sport, sa2, t->conn.dport,
|
||||
t->conn.l4proto==IPPROTO_TCP ? connstate_s[t->track.state] : "-",
|
||||
(unsigned long long)t->track.t_start, (unsigned long long)(t->track.t_last - t->track.t_start), (unsigned long long)(tnow - t->track.t_last),
|
||||
(unsigned long long)t->track.pdcounter_orig, (unsigned long long)t->track.pcounter_orig,
|
||||
(unsigned long long)t->track.pdcounter_reply, (unsigned long long)t->track.pcounter_reply,
|
||||
printf("rseq=%u pos_orig=%u rack=%u pos_reply=%u",
|
||||
t->track.seq_last, t->track.pos_orig,
|
||||
t->track.ack_last, t->track.pos_reply);
|
||||
printf(" cutoff=%u wss_cutoff=%u d_cutoff=%u\n",
|
||||
t->track.b_cutoff, t->track.b_wssize_cutoff, t->track.b_desync_cutoff);
|
||||
printf(" req_retrans=%u cutoff=%u wss_cutoff=%u d_cutoff=%u hostname=%s l7proto=%s\n",
|
||||
t->track.req_retrans_counter, t->track.b_cutoff, t->track.b_wssize_cutoff, t->track.b_desync_cutoff, t->track.hostname, ConntrackProtoName(t->track.l7proto));
|
||||
};
|
||||
}
|
||||
|
@ -20,6 +20,7 @@
|
||||
#define HASH_FUNCTION HASH_BER
|
||||
#include "uthash.h"
|
||||
|
||||
#define RETRANS_COUNTER_STOP ((uint8_t)-1)
|
||||
|
||||
typedef union {
|
||||
struct in_addr ip;
|
||||
@ -37,6 +38,7 @@ typedef struct
|
||||
// ESTABLISHED - any except SYN or SYN/ACK received
|
||||
// FIN - FIN or RST received
|
||||
typedef enum {SYN=0, ESTABLISHED, FIN} t_connstate;
|
||||
typedef enum {UNKNOWN=0, HTTP, TLS, QUIC, WIREGUARD, DHT} t_l7proto;
|
||||
typedef struct
|
||||
{
|
||||
// common state
|
||||
@ -52,8 +54,14 @@ typedef struct
|
||||
uint16_t winsize_orig, winsize_reply; // last seen window size
|
||||
uint8_t scale_orig, scale_reply; // last seen window scale factor. SCALE_NONE if none
|
||||
|
||||
uint8_t req_retrans_counter; // number of request retransmissions
|
||||
uint32_t req_seq; // sequence number of the request (to track retransmissions)
|
||||
|
||||
bool b_cutoff; // mark for deletion
|
||||
bool b_wssize_cutoff, b_desync_cutoff;
|
||||
|
||||
t_l7proto l7proto;
|
||||
char *hostname;
|
||||
} t_ctrack;
|
||||
|
||||
typedef struct
|
||||
|
169
nfq/desync.c
169
nfq/desync.c
@ -11,9 +11,9 @@
|
||||
|
||||
|
||||
const char *fake_http_request_default = "GET / HTTP/1.1\r\nHost: www.w3.org\r\n"
|
||||
"User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:70.0) Gecko/20100101 Firefox/70.0\r\n"
|
||||
"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n"
|
||||
"Accept-Encoding: gzip, deflate\r\n\r\n";
|
||||
"User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/109.0\r\n"
|
||||
"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8\r\n"
|
||||
"Accept-Encoding: gzip, deflate, br\r\n\r\n";
|
||||
const uint8_t fake_tls_clienthello_default[517] = {
|
||||
0x16, 0x03, 0x01, 0x02, 0x00, 0x01, 0x00, 0x01, 0xfc, 0x03, 0x03, 0x9a, 0x8f, 0xa7, 0x6a, 0x5d,
|
||||
0x57, 0xf3, 0x62, 0x19, 0xbe, 0x46, 0x82, 0x45, 0xe2, 0x59, 0x5c, 0xb4, 0x48, 0x31, 0x12, 0x15,
|
||||
@ -158,7 +158,10 @@ static void maybe_cutoff(t_ctrack *ctrack, uint8_t proto)
|
||||
if (proto==IPPROTO_TCP)
|
||||
// we do not need conntrack entry anymore if all cutoff conditions are either not defined or reached
|
||||
// do not drop udp entry because it will be recreated when next packet arrives
|
||||
ctrack->b_cutoff |= (!params.wssize || ctrack->b_wssize_cutoff) && (!params.desync_cutoff || ctrack->b_desync_cutoff);
|
||||
ctrack->b_cutoff |= \
|
||||
(!params.wssize || ctrack->b_wssize_cutoff) &&
|
||||
(!params.desync_cutoff || ctrack->b_desync_cutoff) &&
|
||||
(!*params.hostlist_auto_filename || ctrack->req_retrans_counter==RETRANS_COUNTER_STOP);
|
||||
}
|
||||
}
|
||||
static void wssize_cutoff(t_ctrack *ctrack)
|
||||
@ -169,7 +172,73 @@ static void wssize_cutoff(t_ctrack *ctrack)
|
||||
maybe_cutoff(ctrack, IPPROTO_TCP);
|
||||
}
|
||||
}
|
||||
#define CONNTRACK_REQUIRED (params.wssize || params.desync_cutoff)
|
||||
|
||||
static void ctrack_stop_req_counter(t_ctrack *ctrack)
|
||||
{
|
||||
ctrack->req_retrans_counter = RETRANS_COUNTER_STOP;
|
||||
maybe_cutoff(ctrack, IPPROTO_TCP);
|
||||
}
|
||||
|
||||
// return true if retrans trigger fires
|
||||
static bool auto_hostlist_retrans(t_ctrack *ctrack, uint8_t l4proto, int threshold)
|
||||
{
|
||||
if (*params.hostlist_auto_filename && ctrack && ctrack->req_retrans_counter!=RETRANS_COUNTER_STOP)
|
||||
{
|
||||
ctrack->req_retrans_counter++;
|
||||
DLOG("req retrans counter : %u/%u\n",ctrack->req_retrans_counter, threshold);
|
||||
if (ctrack->req_retrans_counter >= threshold)
|
||||
{
|
||||
DLOG("req retrans threshold reached\n");
|
||||
ctrack_stop_req_counter(ctrack);
|
||||
return true;
|
||||
}
|
||||
if (l4proto==IPPROTO_TCP)
|
||||
{
|
||||
if (!ctrack->req_seq) ctrack->req_seq = ctrack->seq_last;
|
||||
if (ctrack->seq_last != ctrack->req_seq)
|
||||
{
|
||||
DLOG("another request, not retransmission. stop tracking.\n");
|
||||
ctrack_stop_req_counter(ctrack);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
static void auto_hostlist_failed(const char *hostname)
|
||||
{
|
||||
hostfail_pool *fail_counter;
|
||||
|
||||
fail_counter = HostFailPoolFind(params.hostlist_auto_fail_counters, hostname);
|
||||
if (!fail_counter)
|
||||
{
|
||||
fail_counter = HostFailPoolAdd(¶ms.hostlist_auto_fail_counters, hostname, params.hostlist_auto_fail_time);
|
||||
if (!fail_counter)
|
||||
{
|
||||
fprintf(stderr, "HostFailPoolAdd: out of memory\n");
|
||||
return;
|
||||
}
|
||||
}
|
||||
fail_counter->counter++;
|
||||
DLOG("auto hostlist : %s : fail counter %d/%d\n", hostname, fail_counter->counter, params.hostlist_auto_fail_threshold);
|
||||
if (fail_counter->counter >= params.hostlist_auto_fail_threshold)
|
||||
{
|
||||
DLOG("auto hostlist : fail threshold reached. adding %s to auto hostlist\n", hostname);
|
||||
HostFailPoolDel(¶ms.hostlist_auto_fail_counters, fail_counter);
|
||||
if (!StrPoolAddStr(¶ms.hostlist, hostname))
|
||||
{
|
||||
fprintf(stderr, "StrPoolAddStr out of memory\n");
|
||||
return;
|
||||
}
|
||||
if (!append_to_list_file(params.hostlist_auto_filename, hostname))
|
||||
{
|
||||
perror("write to auto hostlist:");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#define CONNTRACK_REQUIRED (params.wssize || params.desync_cutoff || *params.hostlist_auto_filename)
|
||||
// result : true - drop original packet, false = dont drop
|
||||
packet_process_result dpi_desync_tcp_packet(uint32_t fwmark, const char *ifout, uint8_t *data_pkt, size_t len_pkt, struct ip *ip, struct ip6_hdr *ip6hdr, struct tcphdr *tcphdr, size_t len_tcp, uint8_t *data_payload, size_t len_payload)
|
||||
{
|
||||
@ -190,6 +259,7 @@ packet_process_result dpi_desync_tcp_packet(uint32_t fwmark, const char *ifout,
|
||||
ConntrackPoolPurge(¶ms.conntrack);
|
||||
if (ConntrackPoolFeed(¶ms.conntrack, ip, ip6hdr, tcphdr, NULL, len_payload, &ctrack, &bReverse))
|
||||
maybe_cutoff(ctrack, IPPROTO_TCP);
|
||||
HostFailPoolPurgeRateLimited(¶ms.hostlist_auto_fail_counters);
|
||||
}
|
||||
|
||||
//ConntrackPoolDump(¶ms.conntrack);
|
||||
@ -200,7 +270,44 @@ packet_process_result dpi_desync_tcp_packet(uint32_t fwmark, const char *ifout,
|
||||
res=modify;
|
||||
}
|
||||
|
||||
if (bReverse) return res; // nothing to do. do not waste cpu
|
||||
if (bReverse)
|
||||
{
|
||||
// process reply packets for auto hostlist mode
|
||||
// by looking at RSTs or HTTP replies we decide whether original request looks to be blocked by DPI
|
||||
if (*params.hostlist_auto_filename && ctrack && ctrack->hostname && ctrack->req_retrans_counter != RETRANS_COUNTER_STOP)
|
||||
{
|
||||
bool bFail=false, bStop=false;
|
||||
if (tcphdr->th_flags & TH_RST)
|
||||
{
|
||||
DLOG("incoming RST detected for hostname %s\n", ctrack->hostname);
|
||||
bFail = bStop = true;
|
||||
}
|
||||
else if (len_payload && ctrack->l7proto==HTTP)
|
||||
{
|
||||
if (IsHttpReply(data_payload,len_payload))
|
||||
{
|
||||
DLOG("incoming HTTP reply detected for hostname %s\n", ctrack->hostname);
|
||||
bFail = HttpReplyLooksLikeDPIRedirect(data_payload, len_payload, ctrack->hostname);
|
||||
if (bFail)
|
||||
DLOG("redirect to another domain detected. possibly DPI redirect.\n")
|
||||
else
|
||||
DLOG("local or in-domain redirect detected. it's not a DPI redirect.\n")
|
||||
}
|
||||
else
|
||||
{
|
||||
// received not http reply. do not monitor this connection anymore
|
||||
DLOG("incoming unknown HTTP data detected for hostname %s\n", ctrack->hostname);
|
||||
}
|
||||
bStop = true;
|
||||
}
|
||||
if (bFail)
|
||||
auto_hostlist_failed(ctrack->hostname);
|
||||
if (bStop)
|
||||
ctrack_stop_req_counter(ctrack);
|
||||
}
|
||||
|
||||
return res; // nothing to do. do not waste cpu
|
||||
}
|
||||
|
||||
uint32_t desync_fwmark = fwmark | params.desync_fwmark;
|
||||
|
||||
@ -269,7 +376,7 @@ packet_process_result dpi_desync_tcp_packet(uint32_t fwmark, const char *ifout,
|
||||
}
|
||||
}
|
||||
|
||||
if (!params.wssize && params.desync_mode==DESYNC_NONE && !params.hostcase && !params.hostnospace && !params.domcase) return res; // nothing to do. do not waste cpu
|
||||
if (!params.wssize && params.desync_mode==DESYNC_NONE && !params.hostcase && !params.hostnospace && !params.domcase && !*params.hostlist_auto_filename) return res; // nothing to do. do not waste cpu
|
||||
|
||||
if (!(tcphdr->th_flags & TH_SYN) && len_payload)
|
||||
{
|
||||
@ -280,12 +387,15 @@ packet_process_result dpi_desync_tcp_packet(uint32_t fwmark, const char *ifout,
|
||||
bool bIsHttp;
|
||||
bool bKnownProtocol = false;
|
||||
uint8_t *p, *phost;
|
||||
|
||||
if ((bIsHttp = IsHttp(data_payload,len_payload)))
|
||||
{
|
||||
DLOG("packet contains HTTP request\n")
|
||||
if (params.wssize) DLOG("forced wssize-cutoff\n");
|
||||
if (ctrack && !ctrack->l7proto) ctrack->l7proto = HTTP;
|
||||
if (params.wssize)
|
||||
{
|
||||
DLOG("forced wssize-cutoff\n");
|
||||
wssize_cutoff(ctrack);
|
||||
}
|
||||
fake = params.fake_http;
|
||||
fake_size = params.fake_http_size;
|
||||
if (params.hostlist || params.debug) bHaveHost=HttpExtractHost(data_payload,len_payload,host,sizeof(host));
|
||||
@ -299,8 +409,12 @@ packet_process_result dpi_desync_tcp_packet(uint32_t fwmark, const char *ifout,
|
||||
else if (IsTLSClientHello(data_payload,len_payload))
|
||||
{
|
||||
DLOG("packet contains TLS ClientHello\n")
|
||||
if (params.wssize) DLOG("forced wssize-cutoff\n");
|
||||
if (ctrack && !ctrack->l7proto) ctrack->l7proto = TLS;
|
||||
if (params.wssize)
|
||||
{
|
||||
DLOG("forced wssize-cutoff\n");
|
||||
wssize_cutoff(ctrack);
|
||||
}
|
||||
fake = params.fake_tls;
|
||||
fake_size = params.fake_tls_size;
|
||||
if (params.hostlist || params.desync_skip_nosni || params.debug)
|
||||
@ -316,6 +430,10 @@ packet_process_result dpi_desync_tcp_packet(uint32_t fwmark, const char *ifout,
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ctrack && *params.hostlist_auto_filename)
|
||||
// received unknown payload. it means we are out of the request retransmission phase. stop counter
|
||||
ctrack_stop_req_counter(ctrack);
|
||||
|
||||
if (!params.desync_any_proto) return res;
|
||||
DLOG("applying tampering to unknown protocol\n")
|
||||
fake = params.fake_unknown;
|
||||
@ -325,9 +443,17 @@ packet_process_result dpi_desync_tcp_packet(uint32_t fwmark, const char *ifout,
|
||||
if (bHaveHost)
|
||||
{
|
||||
DLOG("hostname: %s\n",host)
|
||||
if ((params.hostlist || params.hostlist_exclude) && !HostlistCheck(params.hostlist, params.hostlist_exclude, host))
|
||||
bool bExcluded;
|
||||
if ((params.hostlist || params.hostlist_exclude) && !HostlistCheck(params.hostlist, params.hostlist_exclude, host, &bExcluded))
|
||||
{
|
||||
DLOG("not applying tampering to this request\n")
|
||||
if (!bExcluded)
|
||||
{
|
||||
if (*params.hostlist_auto_filename && ctrack && !ctrack->hostname)
|
||||
ctrack->hostname=strdup(host);
|
||||
if (auto_hostlist_retrans(ctrack, IPPROTO_TCP, params.hostlist_auto_retrans_threshold))
|
||||
auto_hostlist_failed(host);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
}
|
||||
@ -650,6 +776,7 @@ packet_process_result dpi_desync_udp_packet(uint32_t fwmark, const char *ifout,
|
||||
ConntrackPoolPurge(¶ms.conntrack);
|
||||
if (ConntrackPoolFeed(¶ms.conntrack, ip, ip6hdr, NULL, udphdr, len_payload, &ctrack, &bReverse))
|
||||
maybe_cutoff(ctrack, IPPROTO_UDP);
|
||||
HostFailPoolPurgeRateLimited(¶ms.hostlist_auto_fail_counters);
|
||||
}
|
||||
|
||||
//ConntrackPoolDump(¶ms.conntrack);
|
||||
@ -674,7 +801,7 @@ packet_process_result dpi_desync_udp_packet(uint32_t fwmark, const char *ifout,
|
||||
}
|
||||
}
|
||||
|
||||
if (params.desync_mode==DESYNC_NONE) return res;
|
||||
if (params.desync_mode==DESYNC_NONE && !*params.hostlist_auto_filename) return res; // do not waste cpu
|
||||
|
||||
if (len_payload)
|
||||
{
|
||||
@ -688,6 +815,8 @@ packet_process_result dpi_desync_udp_packet(uint32_t fwmark, const char *ifout,
|
||||
if (IsQUICInitial(data_payload,len_payload))
|
||||
{
|
||||
DLOG("packet contains QUIC initial\n")
|
||||
|
||||
if (ctrack && !ctrack->l7proto) ctrack->l7proto = QUIC;
|
||||
fake = params.fake_quic;
|
||||
fake_size = params.fake_quic_size;
|
||||
|
||||
@ -732,6 +861,7 @@ packet_process_result dpi_desync_udp_packet(uint32_t fwmark, const char *ifout,
|
||||
else if (IsWireguardHandshakeInitiation(data_payload,len_payload))
|
||||
{
|
||||
DLOG("packet contains wireguard handshake initiation\n")
|
||||
if (ctrack && !ctrack->l7proto) ctrack->l7proto = WIREGUARD;
|
||||
fake = params.fake_wg;
|
||||
fake_size = params.fake_wg_size;
|
||||
bKnownProtocol = true;
|
||||
@ -739,12 +869,17 @@ packet_process_result dpi_desync_udp_packet(uint32_t fwmark, const char *ifout,
|
||||
else if (IsDhtD1(data_payload,len_payload))
|
||||
{
|
||||
DLOG("packet contains DHT d1...e\n")
|
||||
if (ctrack && !ctrack->l7proto) ctrack->l7proto = DHT;
|
||||
fake = params.fake_dht;
|
||||
fake_size = params.fake_dht_size;
|
||||
bKnownProtocol = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ctrack && *params.hostlist_auto_filename)
|
||||
// received unknown payload. it means we are out of the request retransmission phase. stop counter
|
||||
ctrack_stop_req_counter(ctrack);
|
||||
|
||||
if (!params.desync_any_proto) return res;
|
||||
DLOG("applying tampering to unknown protocol\n")
|
||||
fake = params.fake_unknown_udp;
|
||||
@ -754,9 +889,17 @@ packet_process_result dpi_desync_udp_packet(uint32_t fwmark, const char *ifout,
|
||||
if (bHaveHost)
|
||||
{
|
||||
DLOG("hostname: %s\n",host)
|
||||
if ((params.hostlist || params.hostlist_exclude) && !HostlistCheck(params.hostlist, params.hostlist_exclude, host))
|
||||
bool bExcluded;
|
||||
if ((params.hostlist || params.hostlist_exclude) && !HostlistCheck(params.hostlist, params.hostlist_exclude, host, &bExcluded))
|
||||
{
|
||||
DLOG("not applying tampering to this request\n")
|
||||
if (!bExcluded)
|
||||
{
|
||||
if (*params.hostlist_auto_filename && ctrack && !ctrack->hostname)
|
||||
ctrack->hostname=strdup(host);
|
||||
if (auto_hostlist_retrans(ctrack, IPPROTO_UDP, params.hostlist_auto_retrans_threshold))
|
||||
auto_hostlist_failed(host);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
@ -82,6 +82,14 @@ bool save_file(const char *filename, const void *buffer, size_t buffer_size)
|
||||
fclose(F);
|
||||
return true;
|
||||
}
|
||||
bool append_to_list_file(const char *filename, const char *s)
|
||||
{
|
||||
FILE *F = fopen(filename,"at");
|
||||
if (!F) return false;
|
||||
bool bOK = fprintf(F,"%s\n",s)>0;
|
||||
fclose(F);
|
||||
return bOK;
|
||||
}
|
||||
|
||||
|
||||
void ntop46(const struct sockaddr *sa, char *str, size_t len)
|
||||
|
@ -13,6 +13,7 @@ char *strncasestr(const char *s,const char *find, size_t slen);
|
||||
bool load_file(const char *filename,void *buffer,size_t *buffer_size);
|
||||
bool load_file_nonempty(const char *filename,void *buffer,size_t *buffer_size);
|
||||
bool save_file(const char *filename, const void *buffer, size_t buffer_size);
|
||||
bool append_to_list_file(const char *filename, const char *s);
|
||||
|
||||
void print_sockaddr(const struct sockaddr *sa);
|
||||
void ntop46(const struct sockaddr *sa, char *str, size_t len);
|
||||
|
@ -4,6 +4,7 @@
|
||||
#include "params.h"
|
||||
|
||||
|
||||
// inplace tolower() and add to pool
|
||||
static bool addpool(strpool **hostlist, char **s, const char *end)
|
||||
{
|
||||
char *p;
|
||||
@ -22,7 +23,6 @@ static bool addpool(strpool **hostlist, char **s, const char *end)
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool AppendHostList(strpool **hostlist, char *filename)
|
||||
{
|
||||
char *p, *e, s[256], *zbuf;
|
||||
@ -106,6 +106,12 @@ bool LoadHostLists(strpool **hostlist, struct str_list_head *file_list)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool NonEmptyHostlist(strpool **hostlist)
|
||||
{
|
||||
// add impossible hostname if the list is empty
|
||||
return *hostlist ? true : StrPoolAddStrLen(hostlist, "@&()", 4);
|
||||
}
|
||||
|
||||
|
||||
bool SearchHostList(strpool *hostlist, const char *host)
|
||||
{
|
||||
@ -126,12 +132,17 @@ bool SearchHostList(strpool *hostlist, const char *host)
|
||||
}
|
||||
|
||||
// return : true = apply fooling, false = do not apply
|
||||
bool HostlistCheck(strpool *hostlist, strpool *hostlist_exclude, const char *host)
|
||||
bool HostlistCheck(strpool *hostlist, strpool *hostlist_exclude, const char *host, bool *excluded)
|
||||
{
|
||||
if (excluded) *excluded = false;
|
||||
if (hostlist_exclude)
|
||||
{
|
||||
if (params.debug) printf("Checking exclude hostlist\n");
|
||||
if (SearchHostList(hostlist_exclude, host)) return false;
|
||||
if (SearchHostList(hostlist_exclude, host))
|
||||
{
|
||||
if (excluded) *excluded = true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (hostlist)
|
||||
{
|
||||
|
@ -1,10 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include "strpool.h"
|
||||
#include "pools.h"
|
||||
|
||||
bool AppendHostList(strpool **hostlist, char *filename);
|
||||
bool LoadHostLists(strpool **hostlist, struct str_list_head *file_list);
|
||||
bool NonEmptyHostlist(strpool **hostlist);
|
||||
bool SearchHostList(strpool *hostlist, const char *host);
|
||||
// return : true = apply fooling, false = do not apply
|
||||
bool HostlistCheck(strpool *hostlist, strpool *hostlist_exclude, const char *host);
|
||||
bool HostlistCheck(strpool *hostlist, strpool *hostlist_exclude, const char *host, bool *excluded);
|
||||
|
100
nfq/nfqws.c
100
nfq/nfqws.c
@ -8,6 +8,8 @@
|
||||
#include "params.h"
|
||||
#include "protocol.h"
|
||||
#include "hostlist.h"
|
||||
#include "gzip.h"
|
||||
#include "pools.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
@ -24,6 +26,7 @@
|
||||
#include <time.h>
|
||||
#include <sys/param.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/stat.h>
|
||||
#include <netinet/in.h>
|
||||
|
||||
#ifdef __linux__
|
||||
@ -73,6 +76,12 @@ static void onusr1(int sig)
|
||||
ConntrackPoolDump(¶ms.conntrack);
|
||||
printf("\n");
|
||||
}
|
||||
static void onusr2(int sig)
|
||||
{
|
||||
printf("\nHOSTFAIL POOL DUMP\n");
|
||||
HostFailPoolDump(params.hostlist_auto_fail_counters);
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -284,6 +293,7 @@ static int nfq_main(void)
|
||||
|
||||
signal(SIGHUP, onhup);
|
||||
signal(SIGUSR1, onusr1);
|
||||
signal(SIGUSR2, onusr2);
|
||||
|
||||
desync_init();
|
||||
|
||||
@ -553,7 +563,11 @@ static void exithelp(void)
|
||||
" --dpi-desync-udplen-pattern=<filename>|0xHEX\t; udp tail fill pattern\n"
|
||||
" --dpi-desync-cutoff=[n|d|s]N\t\t\t; apply dpi desync only to packet numbers (n, default), data packet numbers (d), relative sequence (s) less than N\n"
|
||||
" --hostlist=<filename>\t\t\t\t; apply dpi desync only to the listed hosts (one host per line, subdomains auto apply, gzip supported, multiple hostlists allowed)\n"
|
||||
" --hostlist-exclude=<filename>\t\t\t; do not apply dpi desync to the listed hosts (one host per line, subdomains auto apply, gzip supported, multiple hostlists allowed)\n",
|
||||
" --hostlist-exclude=<filename>\t\t\t; do not apply dpi desync to the listed hosts (one host per line, subdomains auto apply, gzip supported, multiple hostlists allowed)\n"
|
||||
" --hostlist-auto=<filename>\t\t\t; detect DPI blocks and build hostlist automatically\n"
|
||||
" --hostlist-auto-fail-threshold=<int>\t\t; how many failed attempts cause hostname to be added to auto hostlist (default : %d)\n"
|
||||
" --hostlist-auto-fail-time=<int>\t\t; all failed attemps must be within these seconds (default : %d)\n"
|
||||
" --hostlist-auto-retrans-threshold=<int>\t; how many request retransmissions cause attempt to fail (default : %d)\n",
|
||||
CTRACK_T_SYN, CTRACK_T_EST, CTRACK_T_FIN, CTRACK_T_UDP,
|
||||
#if defined(__linux__) || defined(SO_USER_COOKIE)
|
||||
DPI_DESYNC_FWMARK_DEFAULT,DPI_DESYNC_FWMARK_DEFAULT,
|
||||
@ -562,7 +576,8 @@ static void exithelp(void)
|
||||
DPI_DESYNC_MAX_FAKE_LEN, IPFRAG_UDP_DEFAULT,
|
||||
DPI_DESYNC_MAX_FAKE_LEN, IPFRAG_TCP_DEFAULT,
|
||||
BADSEQ_INCREMENT_DEFAULT, BADSEQ_ACK_INCREMENT_DEFAULT,
|
||||
UDPLEN_INCREMENT_DEFAULT
|
||||
UDPLEN_INCREMENT_DEFAULT,
|
||||
HOSTLIST_AUTO_FAIL_THRESHOLD_DEFAULT, HOSTLIST_AUTO_FAIL_TIME_DEFAULT, HOSTLIST_AUTO_RETRANS_THRESHOLD_DEFAULT
|
||||
);
|
||||
exit(1);
|
||||
}
|
||||
@ -573,16 +588,9 @@ static void cleanup_params(void)
|
||||
|
||||
strlist_destroy(¶ms.hostlist_files);
|
||||
strlist_destroy(¶ms.hostlist_exclude_files);
|
||||
if (params.hostlist_exclude)
|
||||
{
|
||||
StrPoolDestroy(¶ms.hostlist_exclude);
|
||||
params.hostlist_exclude = NULL;
|
||||
}
|
||||
if (params.hostlist)
|
||||
{
|
||||
StrPoolDestroy(¶ms.hostlist);
|
||||
params.hostlist = NULL;
|
||||
}
|
||||
HostFailPoolDestroy(¶ms.hostlist_auto_fail_counters);
|
||||
}
|
||||
static void exithelp_clean(void)
|
||||
{
|
||||
@ -674,6 +682,9 @@ int main(int argc, char **argv)
|
||||
params.desync_badseq_ack_increment = BADSEQ_ACK_INCREMENT_DEFAULT;
|
||||
params.wssize_cutoff_mode = params.desync_cutoff_mode = 'n'; // packet number by default
|
||||
params.udplen_increment = UDPLEN_INCREMENT_DEFAULT;
|
||||
params.hostlist_auto_fail_threshold = HOSTLIST_AUTO_FAIL_THRESHOLD_DEFAULT;
|
||||
params.hostlist_auto_fail_time = HOSTLIST_AUTO_FAIL_TIME_DEFAULT;
|
||||
params.hostlist_auto_retrans_threshold = HOSTLIST_AUTO_RETRANS_THRESHOLD_DEFAULT;
|
||||
|
||||
LIST_INIT(¶ms.hostlist_files);
|
||||
LIST_INIT(¶ms.hostlist_exclude_files);
|
||||
@ -737,9 +748,14 @@ int main(int argc, char **argv)
|
||||
{"dpi-desync-cutoff",required_argument,0,0},// optidx=37
|
||||
{"hostlist",required_argument,0,0}, // optidx=38
|
||||
{"hostlist-exclude",required_argument,0,0}, // optidx=39
|
||||
{"hostlist-auto",required_argument,0,0}, // optidx=40
|
||||
{"hostlist-auto-fail-threshold",required_argument,0,0}, // optidx=41
|
||||
{"hostlist-auto-fail-time",required_argument,0,0}, // optidx=42
|
||||
{"hostlist-auto-retrans-threshold",required_argument,0,0}, // optidx=43
|
||||
|
||||
#ifdef __linux__
|
||||
{"bind-fix4",no_argument,0,0}, // optidx=40
|
||||
{"bind-fix6",no_argument,0,0}, // optidx=41
|
||||
{"bind-fix4",no_argument,0,0}, // optidx=44
|
||||
{"bind-fix6",no_argument,0,0}, // optidx=45
|
||||
#endif
|
||||
{NULL,0,NULL,0}
|
||||
};
|
||||
@ -1069,11 +1085,66 @@ int main(int argc, char **argv)
|
||||
exit_clean(1);
|
||||
}
|
||||
break;
|
||||
case 40: /* hostlist-auto */
|
||||
if (*params.hostlist_auto_filename)
|
||||
{
|
||||
fprintf(stderr, "only one auto hostlist is supported\n");
|
||||
exit_clean(1);
|
||||
}
|
||||
{
|
||||
FILE *F = fopen(optarg,"a+t");
|
||||
if (!F)
|
||||
{
|
||||
fprintf(stderr, "cannot create %s\n", optarg);
|
||||
exit_clean(1);
|
||||
}
|
||||
bool bGzip = is_gzip(F);
|
||||
fclose(F);
|
||||
if (bGzip)
|
||||
{
|
||||
fprintf(stderr, "gzipped auto hostlists are not supported\n");
|
||||
exit_clean(1);
|
||||
}
|
||||
if (params.droproot && chown(optarg, params.uid, -1))
|
||||
fprintf(stderr, "could not chown %s. auto hostlist file may not be writable after privilege drop\n", optarg);
|
||||
}
|
||||
if (!strlist_add(¶ms.hostlist_files, optarg))
|
||||
{
|
||||
fprintf(stderr, "strlist_add failed\n");
|
||||
exit_clean(1);
|
||||
}
|
||||
strncpy(params.hostlist_auto_filename, optarg, sizeof(params.hostlist_auto_filename));
|
||||
params.hostlist_auto_filename[sizeof(params.hostlist_auto_filename) - 1] = '\0';
|
||||
break;
|
||||
case 41: /* hostlist-auto-fail-threshold */
|
||||
params.hostlist_auto_fail_threshold = (uint8_t)atoi(optarg);
|
||||
if (params.hostlist_auto_fail_threshold<1 || params.hostlist_auto_fail_threshold>20)
|
||||
{
|
||||
fprintf(stderr, "auto hostlist fail threshold must be within 1..20\n");
|
||||
exit_clean(1);
|
||||
}
|
||||
break;
|
||||
case 42: /* hostlist-auto-fail-time */
|
||||
params.hostlist_auto_fail_time = (uint8_t)atoi(optarg);
|
||||
if (params.hostlist_auto_fail_time<1)
|
||||
{
|
||||
fprintf(stderr, "auto hostlist fail time is not valid\n");
|
||||
exit_clean(1);
|
||||
}
|
||||
break;
|
||||
case 43: /* hostlist-auto-retrans-threshold */
|
||||
params.hostlist_auto_retrans_threshold = (uint8_t)atoi(optarg);
|
||||
if (params.hostlist_auto_retrans_threshold<2 || params.hostlist_auto_retrans_threshold>10)
|
||||
{
|
||||
fprintf(stderr, "auto hostlist fail threshold must be within 2..10\n");
|
||||
exit_clean(1);
|
||||
}
|
||||
break;
|
||||
#ifdef __linux__
|
||||
case 40: /* bind-fix4 */
|
||||
case 44: /* bind-fix4 */
|
||||
params.bind_fix4 = true;
|
||||
break;
|
||||
case 41: /* bind-fix6 */
|
||||
case 45: /* bind-fix6 */
|
||||
params.bind_fix6 = true;
|
||||
break;
|
||||
#endif
|
||||
@ -1094,6 +1165,7 @@ int main(int argc, char **argv)
|
||||
fprintf(stderr, "Include hostlist load failed\n");
|
||||
exit_clean(1);
|
||||
}
|
||||
if (*params.hostlist_auto_filename) NonEmptyHostlist(¶ms.hostlist);
|
||||
if (!LoadHostLists(¶ms.hostlist_exclude, ¶ms.hostlist_exclude_files))
|
||||
{
|
||||
fprintf(stderr, "Exclude hostlist load failed\n");
|
||||
|
10
nfq/params.h
10
nfq/params.h
@ -1,7 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "params.h"
|
||||
#include "strpool.h"
|
||||
#include "pools.h"
|
||||
#include "conntrack.h"
|
||||
#include "desync.h"
|
||||
|
||||
@ -26,6 +26,11 @@
|
||||
|
||||
#define UDPLEN_INCREMENT_DEFAULT 2
|
||||
|
||||
#define HOSTLIST_AUTO_FAIL_THRESHOLD_DEFAULT 2
|
||||
#define HOSTLIST_AUTO_FAIL_TIME_DEFAULT 60
|
||||
#define HOSTLIST_AUTO_RETRANS_THRESHOLD_DEFAULT 3
|
||||
|
||||
|
||||
struct params_s
|
||||
{
|
||||
bool debug;
|
||||
@ -59,6 +64,9 @@ struct params_s
|
||||
|
||||
strpool *hostlist, *hostlist_exclude;
|
||||
struct str_list_head hostlist_files, hostlist_exclude_files;
|
||||
char hostlist_auto_filename[PATH_MAX];
|
||||
int hostlist_auto_fail_threshold, hostlist_auto_fail_time, hostlist_auto_retrans_threshold;
|
||||
hostfail_pool *hostlist_auto_fail_counters;
|
||||
|
||||
unsigned int ctrack_t_syn, ctrack_t_est, ctrack_t_fin, ctrack_t_udp;
|
||||
t_conntrack conntrack;
|
||||
|
152
nfq/pools.c
Normal file
152
nfq/pools.c
Normal file
@ -0,0 +1,152 @@
|
||||
#define _GNU_SOURCE
|
||||
#include "pools.h"
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#define DESTROY_STR_POOL(etype, ppool) \
|
||||
etype *elem, *tmp; \
|
||||
HASH_ITER(hh, *ppool, elem, tmp) { \
|
||||
free(elem->str); \
|
||||
HASH_DEL(*ppool, elem); \
|
||||
free(elem); \
|
||||
}
|
||||
|
||||
#define ADD_STR_POOL(etype, ppool, keystr, keystr_len) \
|
||||
etype *elem; \
|
||||
if (!(elem = (etype*)malloc(sizeof(etype)))) \
|
||||
return false; \
|
||||
if (!(elem->str = malloc(keystr_len + 1))) \
|
||||
{ \
|
||||
free(elem); \
|
||||
return false; \
|
||||
} \
|
||||
memcpy(elem->str, keystr, keystr_len); \
|
||||
elem->str[keystr_len] = 0; \
|
||||
oom = false; \
|
||||
HASH_ADD_KEYPTR(hh, *ppool, elem->str, strlen(elem->str), elem); \
|
||||
if (oom) \
|
||||
{ \
|
||||
free(elem->str); \
|
||||
free(elem); \
|
||||
return false; \
|
||||
}
|
||||
|
||||
|
||||
#undef uthash_nonfatal_oom
|
||||
#define uthash_nonfatal_oom(elt) ut_oom_recover(elt)
|
||||
static bool oom = false;
|
||||
static void ut_oom_recover(void *elem)
|
||||
{
|
||||
oom = true;
|
||||
}
|
||||
|
||||
// for not zero terminated strings
|
||||
bool StrPoolAddStrLen(strpool **pp, const char *s, size_t slen)
|
||||
{
|
||||
ADD_STR_POOL(strpool, pp, s, slen)
|
||||
return true;
|
||||
}
|
||||
// for zero terminated strings
|
||||
bool StrPoolAddStr(strpool **pp, const char *s)
|
||||
{
|
||||
return StrPoolAddStrLen(pp, s, strlen(s));
|
||||
}
|
||||
|
||||
bool StrPoolCheckStr(strpool *p, const char *s)
|
||||
{
|
||||
strpool *elem;
|
||||
HASH_FIND_STR(p, s, elem);
|
||||
return elem != NULL;
|
||||
}
|
||||
|
||||
void StrPoolDestroy(strpool **pp)
|
||||
{
|
||||
DESTROY_STR_POOL(strpool, pp)
|
||||
}
|
||||
|
||||
|
||||
|
||||
void HostFailPoolDestroy(hostfail_pool **pp)
|
||||
{
|
||||
DESTROY_STR_POOL(hostfail_pool, pp)
|
||||
}
|
||||
hostfail_pool * HostFailPoolAdd(hostfail_pool **pp,const char *s,int fail_time)
|
||||
{
|
||||
size_t slen = strlen(s);
|
||||
ADD_STR_POOL(hostfail_pool, pp, s, slen)
|
||||
elem->expire = time(NULL) + fail_time;
|
||||
return elem;
|
||||
}
|
||||
hostfail_pool *HostFailPoolFind(hostfail_pool *p,const char *s)
|
||||
{
|
||||
hostfail_pool *elem;
|
||||
HASH_FIND_STR(p, s, elem);
|
||||
return elem;
|
||||
}
|
||||
void HostFailPoolDel(hostfail_pool **p, hostfail_pool *elem)
|
||||
{
|
||||
HASH_DEL(*p, elem);
|
||||
free(elem);
|
||||
}
|
||||
void HostFailPoolPurge(hostfail_pool **pp)
|
||||
{
|
||||
hostfail_pool *elem, *tmp;
|
||||
time_t now = time(NULL);
|
||||
HASH_ITER(hh, *pp, elem, tmp)
|
||||
{
|
||||
if (now >= elem->expire)
|
||||
{
|
||||
free(elem->str);
|
||||
HASH_DEL(*pp, elem);
|
||||
free(elem);
|
||||
}
|
||||
}
|
||||
}
|
||||
static time_t host_fail_purge_prev=0;
|
||||
void HostFailPoolPurgeRateLimited(hostfail_pool **pp)
|
||||
{
|
||||
time_t now = time(NULL);
|
||||
// do not purge too often to save resources
|
||||
if (host_fail_purge_prev != now)
|
||||
{
|
||||
HostFailPoolPurge(pp);
|
||||
host_fail_purge_prev = now;
|
||||
}
|
||||
}
|
||||
void HostFailPoolDump(hostfail_pool *p)
|
||||
{
|
||||
hostfail_pool *elem, *tmp;
|
||||
time_t now = time(NULL);
|
||||
HASH_ITER(hh, p, elem, tmp)
|
||||
printf("host=%s counter=%d time_left=%lld\n",elem->str,elem->counter,(long long int)elem->expire-now);
|
||||
}
|
||||
|
||||
|
||||
bool strlist_add(struct str_list_head *head, const char *filename)
|
||||
{
|
||||
struct str_list *entry = malloc(sizeof(struct str_list));
|
||||
if (!entry) return false;
|
||||
entry->str = strdup(filename);
|
||||
if (!entry->str)
|
||||
{
|
||||
free(entry);
|
||||
return false;
|
||||
}
|
||||
LIST_INSERT_HEAD(head, entry, next);
|
||||
return true;
|
||||
}
|
||||
static void strlist_entry_destroy(struct str_list *entry)
|
||||
{
|
||||
if (entry->str) free(entry->str);
|
||||
free(entry);
|
||||
}
|
||||
void strlist_destroy(struct str_list_head *head)
|
||||
{
|
||||
struct str_list *entry;
|
||||
while ((entry = LIST_FIRST(head)))
|
||||
{
|
||||
LIST_REMOVE(entry, next);
|
||||
strlist_entry_destroy(entry);
|
||||
}
|
||||
}
|
46
nfq/pools.h
Normal file
46
nfq/pools.h
Normal file
@ -0,0 +1,46 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <ctype.h>
|
||||
#include <sys/queue.h>
|
||||
#include <time.h>
|
||||
|
||||
//#define HASH_BLOOM 20
|
||||
#define HASH_NONFATAL_OOM 1
|
||||
#define HASH_FUNCTION HASH_BER
|
||||
#include "uthash.h"
|
||||
|
||||
typedef struct strpool {
|
||||
char *str; /* key */
|
||||
UT_hash_handle hh; /* makes this structure hashable */
|
||||
} strpool;
|
||||
|
||||
void StrPoolDestroy(strpool **pp);
|
||||
bool StrPoolAddStr(strpool **pp,const char *s);
|
||||
bool StrPoolAddStrLen(strpool **pp,const char *s,size_t slen);
|
||||
bool StrPoolCheckStr(strpool *p,const char *s);
|
||||
|
||||
struct str_list {
|
||||
char *str;
|
||||
LIST_ENTRY(str_list) next;
|
||||
};
|
||||
LIST_HEAD(str_list_head, str_list);
|
||||
|
||||
|
||||
typedef struct hostfail_pool {
|
||||
char *str; /* key */
|
||||
int counter; /* value */
|
||||
time_t expire; /* when to expire record (unixtime) */
|
||||
UT_hash_handle hh; /* makes this structure hashable */
|
||||
} hostfail_pool;
|
||||
|
||||
void HostFailPoolDestroy(hostfail_pool **pp);
|
||||
hostfail_pool *HostFailPoolAdd(hostfail_pool **pp,const char *s,int fail_time);
|
||||
hostfail_pool *HostFailPoolFind(hostfail_pool *p,const char *s);
|
||||
void HostFailPoolDel(hostfail_pool **pp, hostfail_pool *elem);
|
||||
void HostFailPoolPurge(hostfail_pool **pp);
|
||||
void HostFailPoolPurgeRateLimited(hostfail_pool **pp);
|
||||
void HostFailPoolDump(hostfail_pool *p);
|
||||
|
||||
bool strlist_add(struct str_list_head *head, const char *filename);
|
||||
void strlist_destroy(struct str_list_head *head);
|
168
nfq/protocol.c
168
nfq/protocol.c
@ -20,69 +20,95 @@ bool IsHttp(const uint8_t *data, size_t len)
|
||||
}
|
||||
return false;
|
||||
}
|
||||
bool HttpExtractHost(const uint8_t *data, size_t len, char *host, size_t len_host)
|
||||
bool IsHttpReply(const uint8_t *data, size_t len)
|
||||
{
|
||||
// HTTP/1.x 200\r\n
|
||||
return len>14 && !memcmp(data,"HTTP/1.",7) && (data[7]=='0' || data[7]=='1') && data[8]==' ' &&
|
||||
data[9]>='0' && data[9]<='9' &&
|
||||
data[10]>='0' && data[10]<='9' &&
|
||||
data[11]>='0' && data[11]<='9';
|
||||
}
|
||||
int HttpReplyCode(const uint8_t *data, size_t len)
|
||||
{
|
||||
return (data[9]-'0')*100 + (data[10]-'0')*10 + (data[11]-'0');
|
||||
}
|
||||
bool HttpExtractHeader(const uint8_t *data, size_t len, const char *header, char *buf, size_t len_buf)
|
||||
{
|
||||
const uint8_t *p, *s, *e = data + len;
|
||||
|
||||
p = (uint8_t*)strncasestr((char*)data, "\nHost:", len);
|
||||
p = (uint8_t*)strncasestr((char*)data, header, len);
|
||||
if (!p) return false;
|
||||
p += 6;
|
||||
p += strlen(header);
|
||||
while (p < e && (*p == ' ' || *p == '\t')) p++;
|
||||
s = p;
|
||||
while (s < e && (*s != '\r' && *s != '\n' && *s != ' ' && *s != '\t')) s++;
|
||||
if (s > p)
|
||||
{
|
||||
size_t slen = s - p;
|
||||
if (host && len_host)
|
||||
if (buf && len_buf)
|
||||
{
|
||||
if (slen >= len_host) slen = len_host - 1;
|
||||
for (size_t i = 0; i < slen; i++) host[i] = tolower(p[i]);
|
||||
host[slen] = 0;
|
||||
if (slen >= len_buf) slen = len_buf - 1;
|
||||
for (size_t i = 0; i < slen; i++) buf[i] = tolower(p[i]);
|
||||
buf[slen] = 0;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
bool HttpExtractHost(const uint8_t *data, size_t len, char *host, size_t len_host)
|
||||
{
|
||||
return HttpExtractHeader(data, len, "\nHost:", host, len_host);
|
||||
}
|
||||
const char *HttpFind2ndLevelDomain(const char *host)
|
||||
{
|
||||
const char *p=NULL;
|
||||
if (*host)
|
||||
{
|
||||
for (p = host + strlen(host)-1; p>host && *p!='.'; p--);
|
||||
if (*p=='.') for (p--; p>host && *p!='.'; p--);
|
||||
if (*p=='.') p++;
|
||||
}
|
||||
return p;
|
||||
}
|
||||
// DPI redirects are global redirects to another domain
|
||||
bool HttpReplyLooksLikeDPIRedirect(const uint8_t *data, size_t len, const char *host)
|
||||
{
|
||||
char loc[256],*redirect_host, *p;
|
||||
int code;
|
||||
|
||||
static uint8_t tvb_get_varint(const uint8_t *tvb, uint64_t *value)
|
||||
{
|
||||
switch (*tvb >> 6)
|
||||
{
|
||||
case 0: /* 0b00 => 1 byte length (6 bits Usable) */
|
||||
if (value) *value = *tvb & 0x3F;
|
||||
return 1;
|
||||
case 1: /* 0b01 => 2 bytes length (14 bits Usable) */
|
||||
if (value) *value = pntoh16(tvb) & 0x3FFF;
|
||||
return 2;
|
||||
case 2: /* 0b10 => 4 bytes length (30 bits Usable) */
|
||||
if (value) *value = pntoh32(tvb) & 0x3FFFFFFF;
|
||||
return 4;
|
||||
case 3: /* 0b11 => 8 bytes length (62 bits Usable) */
|
||||
if (value) *value = pntoh64(tvb) & 0x3FFFFFFFFFFFFFFF;
|
||||
return 8;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
static uint8_t tvb_get_size(uint8_t tvb)
|
||||
{
|
||||
return 1 << (tvb >> 6);
|
||||
if (!host || !*host) return false;
|
||||
|
||||
code = HttpReplyCode(data,len);
|
||||
|
||||
if (code!=302 && code!=307 || !HttpExtractHeader(data,len,"\nLocation:",loc,sizeof(loc))) return false;
|
||||
|
||||
// something like : https://censor.net/badpage.php?reason=denied&source=RKN
|
||||
|
||||
if (!strncmp(loc,"http://",7))
|
||||
redirect_host=loc+7;
|
||||
else if (!strncmp(loc,"https://",8))
|
||||
redirect_host=loc+8;
|
||||
else
|
||||
return false;
|
||||
|
||||
// somethinkg like : censor.net/badpage.php?reason=denied&source=RKN
|
||||
|
||||
for(p=redirect_host; *p && *p!='/' ; p++);
|
||||
*p=0;
|
||||
if (!*redirect_host) return false;
|
||||
|
||||
// somethinkg like : censor.net
|
||||
|
||||
// extract 2nd level domains
|
||||
|
||||
const char *dhost = HttpFind2ndLevelDomain(host);
|
||||
const char *drhost = HttpFind2ndLevelDomain(redirect_host);
|
||||
|
||||
return strcasecmp(dhost, drhost)!=0;
|
||||
}
|
||||
|
||||
bool IsQUICCryptoHello(const uint8_t *data, size_t len, size_t *hello_offset, size_t *hello_len)
|
||||
{
|
||||
size_t offset = 1;
|
||||
uint64_t coff, clen;
|
||||
if (len < 3 || *data != 6) return false;
|
||||
if ((offset+tvb_get_size(data[offset])) >= len) return false;
|
||||
offset += tvb_get_varint(data + offset, &coff);
|
||||
// offset must be 0 if it's a full segment, not just a chunk
|
||||
if (coff || (offset+tvb_get_size(data[offset])) >= len) return false;
|
||||
offset += tvb_get_varint(data + offset, &clen);
|
||||
if (data[offset] != 0x01 || (offset + clen) > len) return false;
|
||||
if (hello_offset) *hello_offset = offset;
|
||||
if (hello_len) *hello_len = (size_t)clen;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool IsTLSClientHello(const uint8_t *data, size_t len)
|
||||
{
|
||||
return len >= 6 && data[0] == 0x16 && data[1] == 0x03 && data[2] >= 0x01 && data[2] <= 0x03 && data[5] == 0x01 && (pntoh16(data + 3) + 5) <= len;
|
||||
@ -188,13 +214,45 @@ bool TLSHelloExtractHostFromHandshake(const uint8_t *data, size_t len, char *hos
|
||||
}
|
||||
|
||||
|
||||
bool IsWireguardHandshakeInitiation(const uint8_t *data, size_t len)
|
||||
|
||||
static uint8_t tvb_get_varint(const uint8_t *tvb, uint64_t *value)
|
||||
{
|
||||
return len==148 && data[0]==1 && data[1]==0 && data[2]==0 && data[3]==0;
|
||||
switch (*tvb >> 6)
|
||||
{
|
||||
case 0: /* 0b00 => 1 byte length (6 bits Usable) */
|
||||
if (value) *value = *tvb & 0x3F;
|
||||
return 1;
|
||||
case 1: /* 0b01 => 2 bytes length (14 bits Usable) */
|
||||
if (value) *value = pntoh16(tvb) & 0x3FFF;
|
||||
return 2;
|
||||
case 2: /* 0b10 => 4 bytes length (30 bits Usable) */
|
||||
if (value) *value = pntoh32(tvb) & 0x3FFFFFFF;
|
||||
return 4;
|
||||
case 3: /* 0b11 => 8 bytes length (62 bits Usable) */
|
||||
if (value) *value = pntoh64(tvb) & 0x3FFFFFFFFFFFFFFF;
|
||||
return 8;
|
||||
}
|
||||
bool IsDhtD1(const uint8_t *data, size_t len)
|
||||
return 0;
|
||||
}
|
||||
static uint8_t tvb_get_size(uint8_t tvb)
|
||||
{
|
||||
return len>=7 && data[0]=='d' && data[1]=='1' && data[len-1]=='e';
|
||||
return 1 << (tvb >> 6);
|
||||
}
|
||||
|
||||
bool IsQUICCryptoHello(const uint8_t *data, size_t len, size_t *hello_offset, size_t *hello_len)
|
||||
{
|
||||
size_t offset = 1;
|
||||
uint64_t coff, clen;
|
||||
if (len < 3 || *data != 6) return false;
|
||||
if ((offset+tvb_get_size(data[offset])) >= len) return false;
|
||||
offset += tvb_get_varint(data + offset, &coff);
|
||||
// offset must be 0 if it's a full segment, not just a chunk
|
||||
if (coff || (offset+tvb_get_size(data[offset])) >= len) return false;
|
||||
offset += tvb_get_varint(data + offset, &clen);
|
||||
if (data[offset] != 0x01 || (offset + clen) > len) return false;
|
||||
if (hello_offset) *hello_offset = offset;
|
||||
if (hello_len) *hello_len = (size_t)clen;
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Returns the QUIC draft version or 0 if not applicable. */
|
||||
@ -258,7 +316,6 @@ static bool is_quic_v2(uint32_t version)
|
||||
return version == 0x709A50C4;
|
||||
}
|
||||
|
||||
|
||||
static bool quic_hkdf_expand_label(const uint8_t *secret, uint8_t secret_len, const char *label, uint8_t *out, size_t out_len)
|
||||
{
|
||||
uint8_t hkdflabel[64];
|
||||
@ -275,7 +332,6 @@ static bool quic_hkdf_expand_label(const uint8_t *secret, uint8_t secret_len, co
|
||||
return !hkdfExpand(SHA256, secret, secret_len, hkdflabel, hkdflabel_size, out, out_len);
|
||||
}
|
||||
|
||||
|
||||
static bool quic_derive_initial_secret(const quic_cid_t *cid, uint8_t *client_initial_secret, uint32_t version)
|
||||
{
|
||||
/*
|
||||
@ -505,7 +561,6 @@ bool QUICDefragCrypto(const uint8_t *clean,size_t clean_len, uint8_t *defrag,siz
|
||||
return found;
|
||||
}
|
||||
|
||||
|
||||
bool QUICExtractHostFromInitial(const uint8_t *data, size_t data_len, char *host, size_t len_host, bool *bDecryptOK, bool *bIsCryptoHello)
|
||||
{
|
||||
if (bIsCryptoHello) *bIsCryptoHello=false;
|
||||
@ -565,3 +620,14 @@ bool IsQUICInitial(const uint8_t *data, size_t len)
|
||||
// client hello cannot be too small. likely ACK
|
||||
return sz>=96;
|
||||
}
|
||||
|
||||
|
||||
|
||||
bool IsWireguardHandshakeInitiation(const uint8_t *data, size_t len)
|
||||
{
|
||||
return len==148 && data[0]==1 && data[1]==0 && data[2]==0 && data[3]==0;
|
||||
}
|
||||
bool IsDhtD1(const uint8_t *data, size_t len)
|
||||
{
|
||||
return len>=7 && data[0]=='d' && data[1]=='1' && data[len-1]=='e';
|
||||
}
|
||||
|
@ -7,7 +7,15 @@
|
||||
#include "crypto/aes-gcm.h"
|
||||
|
||||
bool IsHttp(const uint8_t *data, size_t len);
|
||||
// header must be passed like this : "\nHost:"
|
||||
bool HttpExtractHeader(const uint8_t *data, size_t len, const char *header, char *buf, size_t len_buf);
|
||||
bool HttpExtractHost(const uint8_t *data, size_t len, char *host, size_t len_host);
|
||||
bool IsHttpReply(const uint8_t *data, size_t len);
|
||||
const char *HttpFind2ndLevelDomain(const char *host);
|
||||
// must be pre-checked by IsHttpReply
|
||||
int HttpReplyCode(const uint8_t *data, size_t len);
|
||||
// must be pre-checked by IsHttpReply
|
||||
bool HttpReplyLooksLikeDPIRedirect(const uint8_t *data, size_t len, const char *host);
|
||||
|
||||
bool IsTLSClientHello(const uint8_t *data, size_t len);
|
||||
bool TLSFindExt(const uint8_t *data, size_t len, uint16_t type, const uint8_t **ext, size_t *len_ext);
|
||||
|
107
nfq/strpool.c
107
nfq/strpool.c
@ -1,107 +0,0 @@
|
||||
#define _GNU_SOURCE
|
||||
#include "strpool.h"
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#undef uthash_nonfatal_oom
|
||||
#define uthash_nonfatal_oom(elt) ut_oom_recover(elt)
|
||||
|
||||
static bool oom = false;
|
||||
static void ut_oom_recover(strpool *elem)
|
||||
{
|
||||
oom = true;
|
||||
}
|
||||
|
||||
// for zero terminated strings
|
||||
bool StrPoolAddStr(strpool **pp, const char *s)
|
||||
{
|
||||
strpool *elem;
|
||||
if (!(elem = (strpool*)malloc(sizeof(strpool))))
|
||||
return false;
|
||||
if (!(elem->str = strdup(s)))
|
||||
{
|
||||
free(elem);
|
||||
return false;
|
||||
}
|
||||
oom = false;
|
||||
HASH_ADD_KEYPTR(hh, *pp, elem->str, strlen(elem->str), elem);
|
||||
if (oom)
|
||||
{
|
||||
free(elem->str);
|
||||
free(elem);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
// for not zero terminated strings
|
||||
bool StrPoolAddStrLen(strpool **pp, const char *s, size_t slen)
|
||||
{
|
||||
strpool *elem;
|
||||
if (!(elem = (strpool*)malloc(sizeof(strpool))))
|
||||
return false;
|
||||
if (!(elem->str = malloc(slen + 1)))
|
||||
{
|
||||
free(elem);
|
||||
return false;
|
||||
}
|
||||
memcpy(elem->str, s, slen);
|
||||
elem->str[slen] = 0;
|
||||
oom = false;
|
||||
HASH_ADD_KEYPTR(hh, *pp, elem->str, strlen(elem->str), elem);
|
||||
if (oom)
|
||||
{
|
||||
free(elem->str);
|
||||
free(elem);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool StrPoolCheckStr(strpool *p, const char *s)
|
||||
{
|
||||
strpool *elem;
|
||||
HASH_FIND_STR(p, s, elem);
|
||||
return elem != NULL;
|
||||
}
|
||||
|
||||
void StrPoolDestroy(strpool **p)
|
||||
{
|
||||
strpool *elem, *tmp;
|
||||
HASH_ITER(hh, *p, elem, tmp) {
|
||||
free(elem->str);
|
||||
HASH_DEL(*p, elem);
|
||||
free(elem);
|
||||
}
|
||||
*p = NULL;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
bool strlist_add(struct str_list_head *head, const char *filename)
|
||||
{
|
||||
struct str_list *entry = malloc(sizeof(struct str_list));
|
||||
if (!entry) return false;
|
||||
entry->str = strdup(filename);
|
||||
if (!entry->str)
|
||||
{
|
||||
free(entry);
|
||||
return false;
|
||||
}
|
||||
LIST_INSERT_HEAD(head, entry, next);
|
||||
return true;
|
||||
}
|
||||
static void strlist_entry_destroy(struct str_list *entry)
|
||||
{
|
||||
if (entry->str) free(entry->str);
|
||||
free(entry);
|
||||
}
|
||||
void strlist_destroy(struct str_list_head *head)
|
||||
{
|
||||
struct str_list *entry;
|
||||
while ((entry = LIST_FIRST(head)))
|
||||
{
|
||||
LIST_REMOVE(entry, next);
|
||||
strlist_entry_destroy(entry);
|
||||
}
|
||||
}
|
@ -1,29 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <ctype.h>
|
||||
#include <sys/queue.h>
|
||||
|
||||
//#define HASH_BLOOM 20
|
||||
#define HASH_NONFATAL_OOM 1
|
||||
#define HASH_FUNCTION HASH_BER
|
||||
#include "uthash.h"
|
||||
|
||||
typedef struct strpool {
|
||||
char *str; /* key */
|
||||
UT_hash_handle hh; /* makes this structure hashable */
|
||||
} strpool;
|
||||
|
||||
void StrPoolDestroy(strpool **p);
|
||||
bool StrPoolAddStr(strpool **pp,const char *s);
|
||||
bool StrPoolAddStrLen(strpool **pp,const char *s,size_t slen);
|
||||
bool StrPoolCheckStr(strpool *p,const char *s);
|
||||
|
||||
struct str_list {
|
||||
char *str;
|
||||
LIST_ENTRY(str_list) next;
|
||||
};
|
||||
LIST_HEAD(str_list_head, str_list);
|
||||
|
||||
bool strlist_add(struct str_list_head *head, const char *filename);
|
||||
void strlist_destroy(struct str_list_head *head);
|
@ -30,6 +30,15 @@ char *strncasestr(const char *s,const char *find, size_t slen)
|
||||
return (char *)s;
|
||||
}
|
||||
|
||||
bool append_to_list_file(const char *filename, const char *s)
|
||||
{
|
||||
FILE *F = fopen(filename,"at");
|
||||
if (!F) return false;
|
||||
bool bOK = fprintf(F,"%s\n",s)>0;
|
||||
fclose(F);
|
||||
return bOK;
|
||||
}
|
||||
|
||||
void ntop46(const struct sockaddr *sa, char *str, size_t len)
|
||||
{
|
||||
if (!len) return;
|
||||
|
@ -8,6 +8,8 @@
|
||||
|
||||
char *strncasestr(const char *s,const char *find, size_t slen);
|
||||
|
||||
bool append_to_list_file(const char *filename, const char *s);
|
||||
|
||||
void ntop46(const struct sockaddr *sa, char *str, size_t len);
|
||||
void ntop46_port(const struct sockaddr *sa, char *str, size_t len);
|
||||
void print_sockaddr(const struct sockaddr *sa);
|
||||
|
@ -4,6 +4,7 @@
|
||||
#include "params.h"
|
||||
|
||||
|
||||
// inplace tolower() and add to pool
|
||||
static bool addpool(strpool **hostlist, char **s, const char *end)
|
||||
{
|
||||
char *p;
|
||||
@ -22,7 +23,6 @@ static bool addpool(strpool **hostlist, char **s, const char *end)
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool AppendHostList(strpool **hostlist, char *filename)
|
||||
{
|
||||
char *p, *e, s[256], *zbuf;
|
||||
@ -106,6 +106,12 @@ bool LoadHostLists(strpool **hostlist, struct str_list_head *file_list)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool NonEmptyHostlist(strpool **hostlist)
|
||||
{
|
||||
// add impossible hostname if the list is empty
|
||||
return *hostlist ? true : StrPoolAddStrLen(hostlist, "@&()", 4);
|
||||
}
|
||||
|
||||
|
||||
bool SearchHostList(strpool *hostlist, const char *host)
|
||||
{
|
||||
@ -126,12 +132,17 @@ bool SearchHostList(strpool *hostlist, const char *host)
|
||||
}
|
||||
|
||||
// return : true = apply fooling, false = do not apply
|
||||
bool HostlistCheck(strpool *hostlist, strpool *hostlist_exclude, const char *host)
|
||||
bool HostlistCheck(strpool *hostlist, strpool *hostlist_exclude, const char *host, bool *excluded)
|
||||
{
|
||||
if (excluded) *excluded = false;
|
||||
if (hostlist_exclude)
|
||||
{
|
||||
if (params.debug) printf("Checking exclude hostlist\n");
|
||||
if (SearchHostList(hostlist_exclude, host)) return false;
|
||||
if (SearchHostList(hostlist_exclude, host))
|
||||
{
|
||||
if (excluded) *excluded = true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (hostlist)
|
||||
{
|
||||
|
@ -1,10 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include "strpool.h"
|
||||
#include "pools.h"
|
||||
|
||||
bool AppendHostList(strpool **hostlist, char *filename);
|
||||
bool LoadHostLists(strpool **hostlist, struct str_list_head *file_list);
|
||||
bool NonEmptyHostlist(strpool **hostlist);
|
||||
bool SearchHostList(strpool *hostlist, const char *host);
|
||||
// return : true = apply fooling, false = do not apply
|
||||
bool HostlistCheck(strpool *hostlist, strpool *hostlist_exclude, const char *host);
|
||||
bool HostlistCheck(strpool *hostlist, strpool *hostlist_exclude, const char *host, bool *excluded);
|
||||
|
@ -5,7 +5,10 @@
|
||||
#include <stdint.h>
|
||||
#include <sys/param.h>
|
||||
#include <sys/queue.h>
|
||||
#include "strpool.h"
|
||||
#include "pools.h"
|
||||
|
||||
#define HOSTLIST_AUTO_FAIL_THRESHOLD_DEFAULT 2
|
||||
#define HOSTLIST_AUTO_FAIL_TIME_DEFAULT 60
|
||||
|
||||
enum splithttpreq { split_none = 0, split_method, split_host };
|
||||
enum tlsrec { tlsrec_none = 0, tlsrec_sni, tlsrec_pos };
|
||||
@ -53,6 +56,9 @@ struct params_s
|
||||
|
||||
strpool *hostlist, *hostlist_exclude;
|
||||
struct str_list_head hostlist_files, hostlist_exclude_files;
|
||||
char hostlist_auto_filename[PATH_MAX];
|
||||
int hostlist_auto_fail_threshold, hostlist_auto_fail_time;
|
||||
hostfail_pool *hostlist_auto_fail_counters;
|
||||
|
||||
int debug;
|
||||
|
||||
|
152
tpws/pools.c
Normal file
152
tpws/pools.c
Normal file
@ -0,0 +1,152 @@
|
||||
#define _GNU_SOURCE
|
||||
#include "pools.h"
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#define DESTROY_STR_POOL(etype, ppool) \
|
||||
etype *elem, *tmp; \
|
||||
HASH_ITER(hh, *ppool, elem, tmp) { \
|
||||
free(elem->str); \
|
||||
HASH_DEL(*ppool, elem); \
|
||||
free(elem); \
|
||||
}
|
||||
|
||||
#define ADD_STR_POOL(etype, ppool, keystr, keystr_len) \
|
||||
etype *elem; \
|
||||
if (!(elem = (etype*)malloc(sizeof(etype)))) \
|
||||
return false; \
|
||||
if (!(elem->str = malloc(keystr_len + 1))) \
|
||||
{ \
|
||||
free(elem); \
|
||||
return false; \
|
||||
} \
|
||||
memcpy(elem->str, keystr, keystr_len); \
|
||||
elem->str[keystr_len] = 0; \
|
||||
oom = false; \
|
||||
HASH_ADD_KEYPTR(hh, *ppool, elem->str, strlen(elem->str), elem); \
|
||||
if (oom) \
|
||||
{ \
|
||||
free(elem->str); \
|
||||
free(elem); \
|
||||
return false; \
|
||||
}
|
||||
|
||||
|
||||
#undef uthash_nonfatal_oom
|
||||
#define uthash_nonfatal_oom(elt) ut_oom_recover(elt)
|
||||
static bool oom = false;
|
||||
static void ut_oom_recover(void *elem)
|
||||
{
|
||||
oom = true;
|
||||
}
|
||||
|
||||
// for not zero terminated strings
|
||||
bool StrPoolAddStrLen(strpool **pp, const char *s, size_t slen)
|
||||
{
|
||||
ADD_STR_POOL(strpool, pp, s, slen)
|
||||
return true;
|
||||
}
|
||||
// for zero terminated strings
|
||||
bool StrPoolAddStr(strpool **pp, const char *s)
|
||||
{
|
||||
return StrPoolAddStrLen(pp, s, strlen(s));
|
||||
}
|
||||
|
||||
bool StrPoolCheckStr(strpool *p, const char *s)
|
||||
{
|
||||
strpool *elem;
|
||||
HASH_FIND_STR(p, s, elem);
|
||||
return elem != NULL;
|
||||
}
|
||||
|
||||
void StrPoolDestroy(strpool **pp)
|
||||
{
|
||||
DESTROY_STR_POOL(strpool, pp)
|
||||
}
|
||||
|
||||
|
||||
|
||||
void HostFailPoolDestroy(hostfail_pool **pp)
|
||||
{
|
||||
DESTROY_STR_POOL(hostfail_pool, pp)
|
||||
}
|
||||
hostfail_pool * HostFailPoolAdd(hostfail_pool **pp,const char *s,int fail_time)
|
||||
{
|
||||
size_t slen = strlen(s);
|
||||
ADD_STR_POOL(hostfail_pool, pp, s, slen)
|
||||
elem->expire = time(NULL) + fail_time;
|
||||
return elem;
|
||||
}
|
||||
hostfail_pool *HostFailPoolFind(hostfail_pool *p,const char *s)
|
||||
{
|
||||
hostfail_pool *elem;
|
||||
HASH_FIND_STR(p, s, elem);
|
||||
return elem;
|
||||
}
|
||||
void HostFailPoolDel(hostfail_pool **p, hostfail_pool *elem)
|
||||
{
|
||||
HASH_DEL(*p, elem);
|
||||
free(elem);
|
||||
}
|
||||
void HostFailPoolPurge(hostfail_pool **pp)
|
||||
{
|
||||
hostfail_pool *elem, *tmp;
|
||||
time_t now = time(NULL);
|
||||
HASH_ITER(hh, *pp, elem, tmp)
|
||||
{
|
||||
if (now >= elem->expire)
|
||||
{
|
||||
free(elem->str);
|
||||
HASH_DEL(*pp, elem);
|
||||
free(elem);
|
||||
}
|
||||
}
|
||||
}
|
||||
static time_t host_fail_purge_prev=0;
|
||||
void HostFailPoolPurgeRateLimited(hostfail_pool **pp)
|
||||
{
|
||||
time_t now = time(NULL);
|
||||
// do not purge too often to save resources
|
||||
if (host_fail_purge_prev != now)
|
||||
{
|
||||
HostFailPoolPurge(pp);
|
||||
host_fail_purge_prev = now;
|
||||
}
|
||||
}
|
||||
void HostFailPoolDump(hostfail_pool *p)
|
||||
{
|
||||
hostfail_pool *elem, *tmp;
|
||||
time_t now = time(NULL);
|
||||
HASH_ITER(hh, p, elem, tmp)
|
||||
printf("host=%s counter=%d time_left=%lld\n",elem->str,elem->counter,(long long int)elem->expire-now);
|
||||
}
|
||||
|
||||
|
||||
bool strlist_add(struct str_list_head *head, const char *filename)
|
||||
{
|
||||
struct str_list *entry = malloc(sizeof(struct str_list));
|
||||
if (!entry) return false;
|
||||
entry->str = strdup(filename);
|
||||
if (!entry->str)
|
||||
{
|
||||
free(entry);
|
||||
return false;
|
||||
}
|
||||
LIST_INSERT_HEAD(head, entry, next);
|
||||
return true;
|
||||
}
|
||||
static void strlist_entry_destroy(struct str_list *entry)
|
||||
{
|
||||
if (entry->str) free(entry->str);
|
||||
free(entry);
|
||||
}
|
||||
void strlist_destroy(struct str_list_head *head)
|
||||
{
|
||||
struct str_list *entry;
|
||||
while ((entry = LIST_FIRST(head)))
|
||||
{
|
||||
LIST_REMOVE(entry, next);
|
||||
strlist_entry_destroy(entry);
|
||||
}
|
||||
}
|
46
tpws/pools.h
Normal file
46
tpws/pools.h
Normal file
@ -0,0 +1,46 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <ctype.h>
|
||||
#include <sys/queue.h>
|
||||
#include <time.h>
|
||||
|
||||
//#define HASH_BLOOM 20
|
||||
#define HASH_NONFATAL_OOM 1
|
||||
#define HASH_FUNCTION HASH_BER
|
||||
#include "uthash.h"
|
||||
|
||||
typedef struct strpool {
|
||||
char *str; /* key */
|
||||
UT_hash_handle hh; /* makes this structure hashable */
|
||||
} strpool;
|
||||
|
||||
void StrPoolDestroy(strpool **pp);
|
||||
bool StrPoolAddStr(strpool **pp,const char *s);
|
||||
bool StrPoolAddStrLen(strpool **pp,const char *s,size_t slen);
|
||||
bool StrPoolCheckStr(strpool *p,const char *s);
|
||||
|
||||
struct str_list {
|
||||
char *str;
|
||||
LIST_ENTRY(str_list) next;
|
||||
};
|
||||
LIST_HEAD(str_list_head, str_list);
|
||||
|
||||
|
||||
typedef struct hostfail_pool {
|
||||
char *str; /* key */
|
||||
int counter; /* value */
|
||||
time_t expire; /* when to expire record (unixtime) */
|
||||
UT_hash_handle hh; /* makes this structure hashable */
|
||||
} hostfail_pool;
|
||||
|
||||
void HostFailPoolDestroy(hostfail_pool **pp);
|
||||
hostfail_pool *HostFailPoolAdd(hostfail_pool **pp,const char *s,int fail_time);
|
||||
hostfail_pool *HostFailPoolFind(hostfail_pool *p,const char *s);
|
||||
void HostFailPoolDel(hostfail_pool **pp, hostfail_pool *elem);
|
||||
void HostFailPoolPurge(hostfail_pool **pp);
|
||||
void HostFailPoolPurgeRateLimited(hostfail_pool **pp);
|
||||
void HostFailPoolDump(hostfail_pool *p);
|
||||
|
||||
bool strlist_add(struct str_list_head *head, const char *filename);
|
||||
void strlist_destroy(struct str_list_head *head);
|
@ -21,29 +21,95 @@ bool IsHttp(const uint8_t *data, size_t len)
|
||||
}
|
||||
return false;
|
||||
}
|
||||
bool HttpExtractHost(const uint8_t *data, size_t len, char *host, size_t len_host)
|
||||
bool IsHttpReply(const uint8_t *data, size_t len)
|
||||
{
|
||||
// HTTP/1.x 200\r\n
|
||||
return len>14 && !memcmp(data,"HTTP/1.",7) && (data[7]=='0' || data[7]=='1') && data[8]==' ' &&
|
||||
data[9]>='0' && data[9]<='9' &&
|
||||
data[10]>='0' && data[10]<='9' &&
|
||||
data[11]>='0' && data[11]<='9';
|
||||
}
|
||||
int HttpReplyCode(const uint8_t *data, size_t len)
|
||||
{
|
||||
return (data[9]-'0')*100 + (data[10]-'0')*10 + (data[11]-'0');
|
||||
}
|
||||
bool HttpExtractHeader(const uint8_t *data, size_t len, const char *header, char *buf, size_t len_buf)
|
||||
{
|
||||
const uint8_t *p, *s, *e = data + len;
|
||||
|
||||
p = (uint8_t*)strncasestr((char*)data, "\nHost:", len);
|
||||
p = (uint8_t*)strncasestr((char*)data, header, len);
|
||||
if (!p) return false;
|
||||
p+=6;
|
||||
p += strlen(header);
|
||||
while (p < e && (*p == ' ' || *p == '\t')) p++;
|
||||
s = p;
|
||||
while (s < e && (*s != '\r' && *s != '\n' && *s != ' ' && *s != '\t')) s++;
|
||||
if (s > p)
|
||||
{
|
||||
size_t slen = s - p;
|
||||
if (host && len_host)
|
||||
if (buf && len_buf)
|
||||
{
|
||||
if (slen>=len_host) slen=len_host-1;
|
||||
for(size_t i=0;i<slen;i++) host[i]=tolower(p[i]);
|
||||
host[slen]=0;
|
||||
if (slen >= len_buf) slen = len_buf - 1;
|
||||
for (size_t i = 0; i < slen; i++) buf[i] = tolower(p[i]);
|
||||
buf[slen] = 0;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
bool HttpExtractHost(const uint8_t *data, size_t len, char *host, size_t len_host)
|
||||
{
|
||||
return HttpExtractHeader(data, len, "\nHost:", host, len_host);
|
||||
}
|
||||
const char *HttpFind2ndLevelDomain(const char *host)
|
||||
{
|
||||
const char *p=NULL;
|
||||
if (*host)
|
||||
{
|
||||
for (p = host + strlen(host)-1; p>host && *p!='.'; p--);
|
||||
if (*p=='.') for (p--; p>host && *p!='.'; p--);
|
||||
if (*p=='.') p++;
|
||||
}
|
||||
return p;
|
||||
}
|
||||
// DPI redirects are global redirects to another domain
|
||||
bool HttpReplyLooksLikeDPIRedirect(const uint8_t *data, size_t len, const char *host)
|
||||
{
|
||||
char loc[256],*redirect_host, *p;
|
||||
int code;
|
||||
|
||||
if (!host || !*host) return false;
|
||||
|
||||
code = HttpReplyCode(data,len);
|
||||
|
||||
if (code!=302 && code!=307 || !HttpExtractHeader(data,len,"\nLocation:",loc,sizeof(loc))) return false;
|
||||
|
||||
// something like : https://censor.net/badpage.php?reason=denied&source=RKN
|
||||
|
||||
if (!strncmp(loc,"http://",7))
|
||||
redirect_host=loc+7;
|
||||
else if (!strncmp(loc,"https://",8))
|
||||
redirect_host=loc+8;
|
||||
else
|
||||
return false;
|
||||
|
||||
// somethinkg like : censor.net/badpage.php?reason=denied&source=RKN
|
||||
|
||||
for(p=redirect_host; *p && *p!='/' ; p++);
|
||||
*p=0;
|
||||
if (!*redirect_host) return false;
|
||||
|
||||
// somethinkg like : censor.net
|
||||
|
||||
// extract 2nd level domains
|
||||
|
||||
const char *dhost = HttpFind2ndLevelDomain(host);
|
||||
const char *drhost = HttpFind2ndLevelDomain(redirect_host);
|
||||
|
||||
return strcasecmp(dhost, drhost)!=0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
bool IsTLSClientHello(const uint8_t *data, size_t len)
|
||||
{
|
||||
return len>=6 && data[0]==0x16 && data[1]==0x03 && data[2]>=0x01 && data[2]<=0x03 && data[5]==0x01 && (pntoh16(data+3)+5)<=len;
|
||||
|
@ -5,7 +5,16 @@
|
||||
#include <stdbool.h>
|
||||
|
||||
bool IsHttp(const uint8_t *data, size_t len);
|
||||
// header must be passed like this : "\nHost:"
|
||||
bool HttpExtractHeader(const uint8_t *data, size_t len, const char *header, char *buf, size_t len_buf);
|
||||
bool HttpExtractHost(const uint8_t *data, size_t len, char *host, size_t len_host);
|
||||
bool IsHttpReply(const uint8_t *data, size_t len);
|
||||
const char *HttpFind2ndLevelDomain(const char *host);
|
||||
// must be pre-checked by IsHttpReply
|
||||
int HttpReplyCode(const uint8_t *data, size_t len);
|
||||
// must be pre-checked by IsHttpReply
|
||||
bool HttpReplyLooksLikeDPIRedirect(const uint8_t *data, size_t len, const char *host);
|
||||
|
||||
bool IsTLSClientHello(const uint8_t *data, size_t len);
|
||||
bool TLSFindExt(const uint8_t *data, size_t len, uint16_t type, const uint8_t **ext, size_t *len_ext);
|
||||
bool TLSHelloExtractHost(const uint8_t *data, size_t len, char *host, size_t len_host);
|
||||
|
107
tpws/strpool.c
107
tpws/strpool.c
@ -1,107 +0,0 @@
|
||||
#define _GNU_SOURCE
|
||||
#include "strpool.h"
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#undef uthash_nonfatal_oom
|
||||
#define uthash_nonfatal_oom(elt) ut_oom_recover(elt)
|
||||
|
||||
static bool oom = false;
|
||||
static void ut_oom_recover(strpool *elem)
|
||||
{
|
||||
oom = true;
|
||||
}
|
||||
|
||||
// for zero terminated strings
|
||||
bool StrPoolAddStr(strpool **pp, const char *s)
|
||||
{
|
||||
strpool *elem;
|
||||
if (!(elem = (strpool*)malloc(sizeof(strpool))))
|
||||
return false;
|
||||
if (!(elem->str = strdup(s)))
|
||||
{
|
||||
free(elem);
|
||||
return false;
|
||||
}
|
||||
oom = false;
|
||||
HASH_ADD_KEYPTR(hh, *pp, elem->str, strlen(elem->str), elem);
|
||||
if (oom)
|
||||
{
|
||||
free(elem->str);
|
||||
free(elem);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
// for not zero terminated strings
|
||||
bool StrPoolAddStrLen(strpool **pp, const char *s, size_t slen)
|
||||
{
|
||||
strpool *elem;
|
||||
if (!(elem = (strpool*)malloc(sizeof(strpool))))
|
||||
return false;
|
||||
if (!(elem->str = malloc(slen + 1)))
|
||||
{
|
||||
free(elem);
|
||||
return false;
|
||||
}
|
||||
memcpy(elem->str, s, slen);
|
||||
elem->str[slen] = 0;
|
||||
oom = false;
|
||||
HASH_ADD_KEYPTR(hh, *pp, elem->str, strlen(elem->str), elem);
|
||||
if (oom)
|
||||
{
|
||||
free(elem->str);
|
||||
free(elem);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool StrPoolCheckStr(strpool *p, const char *s)
|
||||
{
|
||||
strpool *elem;
|
||||
HASH_FIND_STR(p, s, elem);
|
||||
return elem != NULL;
|
||||
}
|
||||
|
||||
void StrPoolDestroy(strpool **p)
|
||||
{
|
||||
strpool *elem, *tmp;
|
||||
HASH_ITER(hh, *p, elem, tmp) {
|
||||
free(elem->str);
|
||||
HASH_DEL(*p, elem);
|
||||
free(elem);
|
||||
}
|
||||
*p = NULL;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
bool strlist_add(struct str_list_head *head, const char *filename)
|
||||
{
|
||||
struct str_list *entry = malloc(sizeof(struct str_list));
|
||||
if (!entry) return false;
|
||||
entry->str = strdup(filename);
|
||||
if (!entry->str)
|
||||
{
|
||||
free(entry);
|
||||
return false;
|
||||
}
|
||||
LIST_INSERT_HEAD(head, entry, next);
|
||||
return true;
|
||||
}
|
||||
static void strlist_entry_destroy(struct str_list *entry)
|
||||
{
|
||||
if (entry->str) free(entry->str);
|
||||
free(entry);
|
||||
}
|
||||
void strlist_destroy(struct str_list_head *head)
|
||||
{
|
||||
struct str_list *entry;
|
||||
while ((entry = LIST_FIRST(head)))
|
||||
{
|
||||
LIST_REMOVE(entry, next);
|
||||
strlist_entry_destroy(entry);
|
||||
}
|
||||
}
|
@ -1,29 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <ctype.h>
|
||||
#include <sys/queue.h>
|
||||
|
||||
//#define HASH_BLOOM 20
|
||||
#define HASH_NONFATAL_OOM 1
|
||||
#define HASH_FUNCTION HASH_BER
|
||||
#include "uthash.h"
|
||||
|
||||
typedef struct strpool {
|
||||
char *str; /* key */
|
||||
UT_hash_handle hh; /* makes this structure hashable */
|
||||
} strpool;
|
||||
|
||||
void StrPoolDestroy(strpool **p);
|
||||
bool StrPoolAddStr(strpool **pp,const char *s);
|
||||
bool StrPoolAddStrLen(strpool **pp,const char *s,size_t slen);
|
||||
bool StrPoolCheckStr(strpool *p,const char *s);
|
||||
|
||||
struct str_list {
|
||||
char *str;
|
||||
LIST_ENTRY(str_list) next;
|
||||
};
|
||||
LIST_HEAD(str_list_head, str_list);
|
||||
|
||||
bool strlist_add(struct str_list_head *head, const char *filename);
|
||||
void strlist_destroy(struct str_list_head *head);
|
135
tpws/tamper.c
135
tpws/tamper.c
@ -25,14 +25,16 @@ bool find_host(uint8_t **pHost,uint8_t *buf,size_t bs)
|
||||
|
||||
static const char *http_methods[] = { "GET /","POST /","HEAD /","OPTIONS /","PUT /","DELETE /","CONNECT /","TRACE /",NULL };
|
||||
// segment buffer has at least 5 extra bytes to extend data block
|
||||
void modify_tcp_segment(uint8_t *segment,size_t segment_buffer_size,size_t *size,size_t *split_pos)
|
||||
void tamper_out(t_ctrack *ctrack, uint8_t *segment,size_t segment_buffer_size,size_t *size, size_t *split_pos)
|
||||
{
|
||||
uint8_t *p, *pp, *pHost = NULL;
|
||||
size_t method_len = 0, pos;
|
||||
const char **method;
|
||||
bool bIsHttp = false, bBypass = false;
|
||||
bool bIsHttp = false, bBypass = false, bHaveHost = false, bHostExcluded = false;
|
||||
char bRemovedHostSpace = 0;
|
||||
char *pc, Host[128];
|
||||
char *pc, Host[256];
|
||||
|
||||
DBGPRINT("tamper_out")
|
||||
|
||||
*split_pos=0;
|
||||
|
||||
@ -49,6 +51,7 @@ void modify_tcp_segment(uint8_t *segment,size_t segment_buffer_size,size_t *size
|
||||
if (bIsHttp)
|
||||
{
|
||||
VPRINT("Data block looks like http request start : %s", *method)
|
||||
if (!ctrack->l7proto) ctrack->l7proto=HTTP;
|
||||
// cpu saving : we search host only if and when required. we do not research host every time we need its position
|
||||
if ((params.hostlist || params.hostlist_exclude) && find_host(&pHost,segment,*size))
|
||||
{
|
||||
@ -58,9 +61,10 @@ void modify_tcp_segment(uint8_t *segment,size_t segment_buffer_size,size_t *size
|
||||
while (pp < (segment + *size) && (pp - p) < (sizeof(Host) - 1) && *pp != '\r' && *pp != '\n') pp++;
|
||||
memcpy(Host, p, pp - p);
|
||||
Host[pp - p] = '\0';
|
||||
bHaveHost = true;
|
||||
VPRINT("Requested Host is : %s", Host)
|
||||
for(pc = Host; *pc; pc++) *pc=tolower(*pc);
|
||||
bBypass = !HostlistCheck(params.hostlist, params.hostlist_exclude, Host);
|
||||
bBypass = !HostlistCheck(params.hostlist, params.hostlist_exclude, Host, &bHostExcluded);
|
||||
}
|
||||
if (!bBypass)
|
||||
{
|
||||
@ -210,26 +214,28 @@ void modify_tcp_segment(uint8_t *segment,size_t segment_buffer_size,size_t *size
|
||||
{
|
||||
VPRINT("Not acting on this request")
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (IsTLSClientHello(segment,*size))
|
||||
else if (IsTLSClientHello(segment,*size))
|
||||
{
|
||||
char host[256];
|
||||
size_t tpos=0,elen;
|
||||
const uint8_t *ext;
|
||||
|
||||
if (!ctrack->l7proto) ctrack->l7proto=TLS;
|
||||
|
||||
VPRINT("packet contains TLS ClientHello")
|
||||
// we need host only if hostlist is present
|
||||
if ((params.hostlist || params.hostlist_exclude) && TLSHelloExtractHost((uint8_t*)segment,*size,host,sizeof(host)))
|
||||
if ((params.hostlist || params.hostlist_exclude) && TLSHelloExtractHost((uint8_t*)segment,*size,Host,sizeof(Host)))
|
||||
{
|
||||
VPRINT("hostname: %s",host)
|
||||
if (!HostlistCheck(params.hostlist, params.hostlist_exclude, host))
|
||||
VPRINT("hostname: %s",Host)
|
||||
bHaveHost = true;
|
||||
bBypass = !HostlistCheck(params.hostlist, params.hostlist_exclude, Host, &bHostExcluded);
|
||||
}
|
||||
if (bBypass)
|
||||
{
|
||||
VPRINT("Not acting on this request")
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch(params.tlsrec)
|
||||
{
|
||||
case tlsrec_sni:
|
||||
@ -263,9 +269,106 @@ void modify_tcp_segment(uint8_t *segment,size_t segment_buffer_size,size_t *size
|
||||
|
||||
if (params.split_pos < *size)
|
||||
*split_pos = params.split_pos;
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (params.split_any_protocol && params.split_pos < *size)
|
||||
*split_pos = params.split_pos;
|
||||
|
||||
if (bHaveHost && bBypass && !bHostExcluded && !ctrack->hostname && *params.hostlist_auto_filename)
|
||||
{
|
||||
DBGPRINT("tamper_out put hostname : %s", Host)
|
||||
ctrack->hostname=strdup(Host);
|
||||
}
|
||||
|
||||
if (params.split_any_protocol && params.split_pos < *size)
|
||||
*split_pos = params.split_pos;
|
||||
}
|
||||
|
||||
|
||||
static void auto_hostlist_failed(const char *hostname)
|
||||
{
|
||||
hostfail_pool *fail_counter;
|
||||
|
||||
fail_counter = HostFailPoolFind(params.hostlist_auto_fail_counters, hostname);
|
||||
if (!fail_counter)
|
||||
{
|
||||
fail_counter = HostFailPoolAdd(¶ms.hostlist_auto_fail_counters, hostname, params.hostlist_auto_fail_time);
|
||||
if (!fail_counter)
|
||||
{
|
||||
fprintf(stderr, "HostFailPoolAdd: out of memory\n");
|
||||
return;
|
||||
}
|
||||
}
|
||||
fail_counter->counter++;
|
||||
VPRINT("auto hostlist : %s : fail counter %d/%d", hostname, fail_counter->counter, params.hostlist_auto_fail_threshold);
|
||||
if (fail_counter->counter >= params.hostlist_auto_fail_threshold)
|
||||
{
|
||||
VPRINT("auto hostlist : fail threshold reached. adding %s to auto hostlist", hostname);
|
||||
HostFailPoolDel(¶ms.hostlist_auto_fail_counters, fail_counter);
|
||||
if (!StrPoolAddStr(¶ms.hostlist, hostname))
|
||||
{
|
||||
fprintf(stderr, "StrPoolAddStr out of memory\n");
|
||||
return;
|
||||
}
|
||||
if (!append_to_list_file(params.hostlist_auto_filename, hostname))
|
||||
{
|
||||
perror("write to auto hostlist:");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void tamper_in(t_ctrack *ctrack, uint8_t *segment,size_t segment_buffer_size,size_t *size)
|
||||
{
|
||||
bool bFail=false;
|
||||
|
||||
DBGPRINT("tamper_in hostname=%s", ctrack->hostname)
|
||||
|
||||
HostFailPoolPurgeRateLimited(¶ms.hostlist_auto_fail_counters);
|
||||
|
||||
if (ctrack->l7proto==HTTP && ctrack->hostname)
|
||||
{
|
||||
if (IsHttpReply(segment,*size))
|
||||
{
|
||||
VPRINT("incoming HTTP reply detected for hostname %s", ctrack->hostname);
|
||||
bFail = HttpReplyLooksLikeDPIRedirect(segment, *size, ctrack->hostname);
|
||||
if (bFail)
|
||||
VPRINT("redirect to another domain detected. possibly DPI redirect.")
|
||||
else
|
||||
VPRINT("local or in-domain redirect detected. it's not a DPI redirect.")
|
||||
}
|
||||
else
|
||||
{
|
||||
// received not http reply. do not monitor this connection anymore
|
||||
VPRINT("incoming unknown HTTP data detected for hostname %s", ctrack->hostname);
|
||||
}
|
||||
if (bFail)
|
||||
auto_hostlist_failed(ctrack->hostname);
|
||||
|
||||
}
|
||||
ctrack->bTamperInCutoff = true;
|
||||
}
|
||||
|
||||
void rst_in(t_ctrack *ctrack)
|
||||
{
|
||||
DBGPRINT("rst_in hostname=%s", ctrack->hostname)
|
||||
|
||||
HostFailPoolPurgeRateLimited(¶ms.hostlist_auto_fail_counters);
|
||||
|
||||
if (!ctrack->bTamperInCutoff && ctrack->hostname)
|
||||
{
|
||||
VPRINT("incoming RST detected for hostname %s", ctrack->hostname);
|
||||
auto_hostlist_failed(ctrack->hostname);
|
||||
}
|
||||
}
|
||||
void hup_out(t_ctrack *ctrack)
|
||||
{
|
||||
DBGPRINT("hup_out hostname=%s", ctrack->hostname)
|
||||
|
||||
HostFailPoolPurgeRateLimited(¶ms.hostlist_auto_fail_counters);
|
||||
|
||||
if (!ctrack->bTamperInCutoff && ctrack->hostname)
|
||||
{
|
||||
// local leg dropped connection after first request. probably due to timeout.
|
||||
VPRINT("local leg closed connection after first request (timeout ?). hostname: %s", ctrack->hostname);
|
||||
auto_hostlist_failed(ctrack->hostname);
|
||||
}
|
||||
}
|
||||
|
@ -4,5 +4,19 @@
|
||||
#include <stdint.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
bool find_host(uint8_t **pHost,uint8_t *buf,size_t bs);
|
||||
void modify_tcp_segment(uint8_t *segment,size_t segment_buffer_size,size_t *size,size_t *split_pos);
|
||||
typedef enum {UNKNOWN=0, HTTP, TLS} t_l7proto;
|
||||
typedef struct
|
||||
{
|
||||
// common state
|
||||
t_l7proto l7proto;
|
||||
bool bFirstReplyChecked;
|
||||
bool bTamperInCutoff;
|
||||
char *hostname;
|
||||
} t_ctrack;
|
||||
|
||||
void tamper_out(t_ctrack *ctrack, uint8_t *segment,size_t segment_buffer_size,size_t *size, size_t *split_pos);
|
||||
void tamper_in(t_ctrack *ctrack, uint8_t *segment,size_t segment_buffer_size,size_t *size);
|
||||
// connection reset by remote leg
|
||||
void rst_in(t_ctrack *ctrack);
|
||||
// local leg closed connection (timeout waiting response ?)
|
||||
void hup_out(t_ctrack *ctrack);
|
||||
|
201
tpws/tpws.c
201
tpws/tpws.c
@ -36,6 +36,8 @@
|
||||
#include "sec.h"
|
||||
#include "redirect.h"
|
||||
#include "helpers.h"
|
||||
#include "gzip.h"
|
||||
#include "pools.h"
|
||||
|
||||
struct params_s params;
|
||||
|
||||
@ -62,6 +64,12 @@ void dohup(void)
|
||||
}
|
||||
}
|
||||
|
||||
static void onusr2(int sig)
|
||||
{
|
||||
printf("\nHOSTFAIL POOL DUMP\n");
|
||||
HostFailPoolDump(params.hostlist_auto_fail_counters);
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
|
||||
static int8_t block_sigpipe(void)
|
||||
@ -120,62 +128,66 @@ static int get_default_ttl(void)
|
||||
static void exithelp(void)
|
||||
{
|
||||
printf(
|
||||
" --bind-addr=<v4_addr>|<v6_addr>; for v6 link locals append %%interface_name\n"
|
||||
" --bind-iface4=<interface_name>\t; bind to the first ipv4 addr of interface\n"
|
||||
" --bind-iface6=<interface_name>\t; bind to the first ipv6 addr of interface\n"
|
||||
" --bind-linklocal=no|unwanted|prefer|force\n"
|
||||
"\t\t\t\t; prohibit, accept, prefer or force ipv6 link local bind\n"
|
||||
" --bind-wait-ifup=<sec>\t\t; wait for interface to appear and up\n"
|
||||
" --bind-wait-ip=<sec>\t\t; after ifup wait for ip address to appear up to N seconds\n"
|
||||
" --bind-wait-ip-linklocal=<sec>\t; (prefer) accept only LL first N seconds then any (unwanted) accept only globals first N seconds then LL\n"
|
||||
" --bind-wait-only\t\t; wait for bind conditions satisfaction then exit. return code 0 if success.\n"
|
||||
" --bind-addr=<v4_addr>|<v6_addr>\t; for v6 link locals append %%interface_name\n"
|
||||
" --bind-iface4=<interface_name>\t\t; bind to the first ipv4 addr of interface\n"
|
||||
" --bind-iface6=<interface_name>\t\t; bind to the first ipv6 addr of interface\n"
|
||||
" --bind-linklocal=no|unwanted|prefer|force ; prohibit, accept, prefer or force ipv6 link local bind\n"
|
||||
" --bind-wait-ifup=<sec>\t\t\t; wait for interface to appear and up\n"
|
||||
" --bind-wait-ip=<sec>\t\t\t; after ifup wait for ip address to appear up to N seconds\n"
|
||||
" --bind-wait-ip-linklocal=<sec>\t\t; (prefer) accept only LL first N seconds then any (unwanted) accept only globals first N seconds then LL\n"
|
||||
" --bind-wait-only\t\t\t; wait for bind conditions satisfaction then exit. return code 0 if success.\n"
|
||||
" * multiple binds are supported. each bind-addr, bind-iface* start new bind\n"
|
||||
" --port=<port>\t\t\t; only one port number for all binds is supported\n"
|
||||
" --socks\t\t\t; implement socks4/5 proxy instead of transparent proxy\n"
|
||||
" --no-resolve\t\t\t; disable socks5 remote dns ability (resolves are not async, they block all activity)\n"
|
||||
" --port=<port>\t\t\t\t; only one port number for all binds is supported\n"
|
||||
" --socks\t\t\t\t; implement socks4/5 proxy instead of transparent proxy\n"
|
||||
" --no-resolve\t\t\t\t; disable socks5 remote dns ability (resolves are not async, they block all activity)\n"
|
||||
" --local-rcvbuf=<bytes>\n"
|
||||
" --local-sndbuf=<bytes>\n"
|
||||
" --remote-rcvbuf=<bytes>\n"
|
||||
" --remote-sndbuf=<bytes>\n"
|
||||
" --skip-nodelay\t\t\t; do not set TCP_NODELAY option for outgoing connections (incompatible with split options)\n"
|
||||
" --skip-nodelay\t\t\t\t; do not set TCP_NODELAY option for outgoing connections (incompatible with split options)\n"
|
||||
" --maxconn=<max_connections>\n"
|
||||
#ifdef SPLICE_PRESENT
|
||||
" --maxfiles=<max_open_files>\t; should be at least (X*connections+16), where X=6 in tcp proxy mode, X=4 in tampering mode\n"
|
||||
" --maxfiles=<max_open_files>\t\t; should be at least (X*connections+16), where X=6 in tcp proxy mode, X=4 in tampering mode\n"
|
||||
#else
|
||||
" --maxfiles=<max_open_files>\t; should be at least (connections*2+16)\n"
|
||||
" --maxfiles=<max_open_files>\t\t; should be at least (connections*2+16)\n"
|
||||
#endif
|
||||
" --max-orphan-time=<sec>\t; if local leg sends something and closes and remote leg is still connecting then cancel connection attempt after N seconds\n"
|
||||
" --daemon\t\t\t; daemonize\n"
|
||||
" --pidfile=<filename>\t\t; write pid to file\n"
|
||||
" --user=<username>\t\t; drop root privs\n"
|
||||
" --uid=uid[:gid]\t\t; drop root privs\n"
|
||||
" --max-orphan-time=<sec>\t\t; if local leg sends something and closes and remote leg is still connecting then cancel connection attempt after N seconds\n"
|
||||
" --daemon\t\t\t\t; daemonize\n"
|
||||
" --pidfile=<filename>\t\t\t; write pid to file\n"
|
||||
" --user=<username>\t\t\t; drop root privs\n"
|
||||
" --uid=uid[:gid]\t\t\t; drop root privs\n"
|
||||
#if defined(BSD) && !defined(__OpenBSD__) && !defined(__APPLE__)
|
||||
" --enable-pf\t\t\t; enable PF redirector support. required in FreeBSD when used with PF firewall.\n"
|
||||
" --enable-pf\t\t\t\t; enable PF redirector support. required in FreeBSD when used with PF firewall.\n"
|
||||
#endif
|
||||
" --debug=0|1|2\t\t\t; 0(default)=silent 1=verbose 2=debug\n"
|
||||
"\nTAMPERING:\n"
|
||||
" --hostlist=<filename>\t\t; only act on hosts in the list (one host per line, subdomains auto apply, gzip supported, multiple hostlists allowed)\n"
|
||||
" --hostlist-exclude=<filename>\t; do not act on hosts in the list (one host per line, subdomains auto apply, gzip supported, multiple hostlists allowed)\n"
|
||||
" --split-http-req=method|host\t; split at specified logical part of plain http request\n"
|
||||
" --split-pos=<numeric_offset>\t; split at specified pos. split-http-req takes precedence for http.\n"
|
||||
" --split-any-protocol\t\t; split not only http and https\n"
|
||||
" --debug=0|1|2\t\t\t\t; 0(default)=silent 1=verbose 2=debug\n"
|
||||
"\nFILTER:\n"
|
||||
" --hostlist=<filename>\t\t\t; only act on hosts in the list (one host per line, subdomains auto apply, gzip supported, multiple hostlists allowed)\n"
|
||||
" --hostlist-exclude=<filename>\t\t; do not act on hosts in the list (one host per line, subdomains auto apply, gzip supported, multiple hostlists allowed)\n"
|
||||
" --hostlist-auto=<filename>\t\t; detect DPI blocks and build hostlist automatically\n"
|
||||
" --hostlist-auto-fail-threshold=<int>\t; how many failed attempts cause hostname to be added to auto hostlist (default : %d)\n"
|
||||
" --hostlist-auto-fail-time=<int>\t; all failed attemps must be within these seconds (default : %d)\n"
|
||||
"\nTAMPER:\n"
|
||||
" --split-http-req=method|host\t\t; split at specified logical part of plain http request\n"
|
||||
" --split-pos=<numeric_offset>\t\t; split at specified pos. split-http-req takes precedence for http.\n"
|
||||
" --split-any-protocol\t\t\t; split not only http and https\n"
|
||||
#if defined(BSD) && !defined(__APPLE__)
|
||||
" --disorder\t\t\t; when splitting simulate sending second fragment first (BSD sends entire message instead of first fragment, this is not good)\n"
|
||||
" --disorder\t\t\t\t; when splitting simulate sending second fragment first (BSD sends entire message instead of first fragment, this is not good)\n"
|
||||
#else
|
||||
" --disorder\t\t\t; when splitting simulate sending second fragment first\n"
|
||||
" --disorder\t\t\t\t; when splitting simulate sending second fragment first\n"
|
||||
#endif
|
||||
" --hostcase\t\t\t; change Host: => host:\n"
|
||||
" --hostspell\t\t\t; exact spelling of \"Host\" header. must be 4 chars. default is \"host\"\n"
|
||||
" --hostdot\t\t\t; add \".\" after Host: name\n"
|
||||
" --hosttab\t\t\t; add tab after Host: name\n"
|
||||
" --hostnospace\t\t\t; remove space after Host:\n"
|
||||
" --hostpad=<bytes>\t\t; add dummy padding headers before Host:\n"
|
||||
" --domcase\t\t\t; mix domain case : Host: TeSt.cOm\n"
|
||||
" --methodspace\t\t\t; add extra space after method\n"
|
||||
" --methodeol\t\t\t; add end-of-line before method\n"
|
||||
" --unixeol\t\t\t; replace 0D0A to 0A\n"
|
||||
" --tlsrec=sni\t\t\t; make 2 TLS records. split at SNI. don't split if SNI is not present\n"
|
||||
" --tlsrec-pos=<pos>\t\t; make 2 TLS records. split at specified pos\n"
|
||||
" --hostcase\t\t\t\t; change Host: => host:\n"
|
||||
" --hostspell\t\t\t\t; exact spelling of \"Host\" header. must be 4 chars. default is \"host\"\n"
|
||||
" --hostdot\t\t\t\t; add \".\" after Host: name\n"
|
||||
" --hosttab\t\t\t\t; add tab after Host: name\n"
|
||||
" --hostnospace\t\t\t\t; remove space after Host:\n"
|
||||
" --hostpad=<bytes>\t\t\t; add dummy padding headers before Host:\n"
|
||||
" --domcase\t\t\t\t; mix domain case : Host: TeSt.cOm\n"
|
||||
" --methodspace\t\t\t\t; add extra space after method\n"
|
||||
" --methodeol\t\t\t\t; add end-of-line before method\n"
|
||||
" --unixeol\t\t\t\t; replace 0D0A to 0A\n"
|
||||
" --tlsrec=sni\t\t\t\t; make 2 TLS records. split at SNI. don't split if SNI is not present\n"
|
||||
" --tlsrec-pos=<pos>\t\t\t; make 2 TLS records. split at specified pos\n",
|
||||
HOSTLIST_AUTO_FAIL_THRESHOLD_DEFAULT, HOSTLIST_AUTO_FAIL_TIME_DEFAULT
|
||||
);
|
||||
exit(1);
|
||||
}
|
||||
@ -183,16 +195,9 @@ static void cleanup_params(void)
|
||||
{
|
||||
strlist_destroy(¶ms.hostlist_files);
|
||||
strlist_destroy(¶ms.hostlist_exclude_files);
|
||||
if (params.hostlist_exclude)
|
||||
{
|
||||
StrPoolDestroy(¶ms.hostlist_exclude);
|
||||
params.hostlist_exclude = NULL;
|
||||
}
|
||||
if (params.hostlist)
|
||||
{
|
||||
StrPoolDestroy(¶ms.hostlist);
|
||||
params.hostlist = NULL;
|
||||
}
|
||||
HostFailPoolDestroy(¶ms.hostlist_auto_fail_counters);
|
||||
}
|
||||
static void exithelp_clean(void)
|
||||
{
|
||||
@ -246,6 +251,8 @@ void parse_params(int argc, char *argv[])
|
||||
params.maxconn = DEFAULT_MAX_CONN;
|
||||
params.max_orphan_time = DEFAULT_MAX_ORPHAN_TIME;
|
||||
params.binds_last = -1;
|
||||
params.hostlist_auto_fail_threshold = HOSTLIST_AUTO_FAIL_THRESHOLD_DEFAULT;
|
||||
params.hostlist_auto_fail_time = HOSTLIST_AUTO_FAIL_TIME_DEFAULT;
|
||||
LIST_INIT(¶ms.hostlist_files);
|
||||
LIST_INIT(¶ms.hostlist_exclude_files);
|
||||
|
||||
@ -294,18 +301,22 @@ void parse_params(int argc, char *argv[])
|
||||
{ "tlsrec-pos",required_argument,0,0 },// optidx=32
|
||||
{ "hostlist",required_argument,0,0 },// optidx=33
|
||||
{ "hostlist-exclude",required_argument,0,0 },// optidx=34
|
||||
{ "pidfile",required_argument,0,0 },// optidx=35
|
||||
{ "debug",optional_argument,0,0 },// optidx=36
|
||||
{ "local-rcvbuf",required_argument,0,0 },// optidx=37
|
||||
{ "local-sndbuf",required_argument,0,0 },// optidx=38
|
||||
{ "remote-rcvbuf",required_argument,0,0 },// optidx=39
|
||||
{ "remote-sndbuf",required_argument,0,0 },// optidx=40
|
||||
{ "socks",no_argument,0,0 },// optidx=41
|
||||
{ "no-resolve",no_argument,0,0 },// optidx=42
|
||||
{ "skip-nodelay",no_argument,0,0 },// optidx=43
|
||||
{ "hostlist-auto",required_argument,0,0}, // optidx=35
|
||||
{ "hostlist-auto-fail-threshold",required_argument,0,0}, // optidx=36
|
||||
{ "hostlist-auto-fail-time",required_argument,0,0}, // optidx=37
|
||||
{ "pidfile",required_argument,0,0 },// optidx=38
|
||||
{ "debug",optional_argument,0,0 },// optidx=39
|
||||
{ "local-rcvbuf",required_argument,0,0 },// optidx=40
|
||||
{ "local-sndbuf",required_argument,0,0 },// optidx=41
|
||||
{ "remote-rcvbuf",required_argument,0,0 },// optidx=42
|
||||
{ "remote-sndbuf",required_argument,0,0 },// optidx=43
|
||||
{ "socks",no_argument,0,0 },// optidx=44
|
||||
{ "no-resolve",no_argument,0,0 },// optidx=45
|
||||
{ "skip-nodelay",no_argument,0,0 },// optidx=46
|
||||
#if defined(BSD) && !defined(__OpenBSD__) && !defined(__APPLE__)
|
||||
{ "enable-pf",no_argument,0,0 },// optidx=44
|
||||
{ "enable-pf",no_argument,0,0 },// optidx=47
|
||||
#endif
|
||||
{ "hostlist-auto-retrans-threshold",optional_argument,0,0}, // ignored. for nfqws command line compatibility
|
||||
{ NULL,0,NULL,0 }
|
||||
};
|
||||
while ((v = getopt_long_only(argc, argv, "", long_options, &option_index)) != -1)
|
||||
@ -544,36 +555,84 @@ void parse_params(int argc, char *argv[])
|
||||
}
|
||||
params.tamper = true;
|
||||
break;
|
||||
case 35: /* pidfile */
|
||||
case 35: /* hostlist-auto */
|
||||
if (*params.hostlist_auto_filename)
|
||||
{
|
||||
fprintf(stderr, "only one auto hostlist is supported\n");
|
||||
exit_clean(1);
|
||||
}
|
||||
{
|
||||
FILE *F = fopen(optarg,"a+t");
|
||||
if (!F)
|
||||
{
|
||||
fprintf(stderr, "cannot create %s\n", optarg);
|
||||
exit_clean(1);
|
||||
}
|
||||
bool bGzip = is_gzip(F);
|
||||
fclose(F);
|
||||
if (bGzip)
|
||||
{
|
||||
fprintf(stderr, "gzipped auto hostlists are not supported\n");
|
||||
exit_clean(1);
|
||||
}
|
||||
if (params.droproot && chown(optarg, params.uid, -1))
|
||||
fprintf(stderr, "could not chown %s. auto hostlist file may not be writable after privilege drop\n", optarg);
|
||||
}
|
||||
if (!strlist_add(¶ms.hostlist_files, optarg))
|
||||
{
|
||||
fprintf(stderr, "strlist_add failed\n");
|
||||
exit_clean(1);
|
||||
}
|
||||
strncpy(params.hostlist_auto_filename, optarg, sizeof(params.hostlist_auto_filename));
|
||||
params.hostlist_auto_filename[sizeof(params.hostlist_auto_filename) - 1] = '\0';
|
||||
params.tamper = true; // need to detect blocks and update autohostlist. cannot just slice.
|
||||
break;
|
||||
case 36: /* hostlist-auto-fail-threshold */
|
||||
params.hostlist_auto_fail_threshold = (uint8_t)atoi(optarg);
|
||||
if (params.hostlist_auto_fail_threshold<1 || params.hostlist_auto_fail_threshold>20)
|
||||
{
|
||||
fprintf(stderr, "auto hostlist fail threshold must be within 1..20\n");
|
||||
exit_clean(1);
|
||||
}
|
||||
break;
|
||||
case 37: /* hostlist-auto-fail-time */
|
||||
params.hostlist_auto_fail_time = (uint8_t)atoi(optarg);
|
||||
if (params.hostlist_auto_fail_time<1)
|
||||
{
|
||||
fprintf(stderr, "auto hostlist fail time is not valid\n");
|
||||
exit_clean(1);
|
||||
}
|
||||
break;
|
||||
case 38: /* pidfile */
|
||||
strncpy(params.pidfile,optarg,sizeof(params.pidfile));
|
||||
params.pidfile[sizeof(params.pidfile)-1]='\0';
|
||||
break;
|
||||
case 36:
|
||||
case 39:
|
||||
params.debug = optarg ? atoi(optarg) : 1;
|
||||
break;
|
||||
case 37: /* local-rcvbuf */
|
||||
case 40: /* local-rcvbuf */
|
||||
params.local_rcvbuf = atoi(optarg)/2;
|
||||
break;
|
||||
case 38: /* local-sndbuf */
|
||||
case 41: /* local-sndbuf */
|
||||
params.local_sndbuf = atoi(optarg)/2;
|
||||
break;
|
||||
case 39: /* remote-rcvbuf */
|
||||
case 42: /* remote-rcvbuf */
|
||||
params.remote_rcvbuf = atoi(optarg)/2;
|
||||
break;
|
||||
case 40: /* remote-sndbuf */
|
||||
case 43: /* remote-sndbuf */
|
||||
params.remote_sndbuf = atoi(optarg)/2;
|
||||
break;
|
||||
case 41: /* socks */
|
||||
case 44: /* socks */
|
||||
params.proxy_type = CONN_TYPE_SOCKS;
|
||||
break;
|
||||
case 42: /* no-resolve */
|
||||
case 45: /* no-resolve */
|
||||
params.no_resolve = true;
|
||||
break;
|
||||
case 43: /* skip-nodelay */
|
||||
case 46: /* skip-nodelay */
|
||||
params.skip_nodelay = true;
|
||||
break;
|
||||
#if defined(BSD) && !defined(__OpenBSD__) && !defined(__APPLE__)
|
||||
case 44: /* enable-pf */
|
||||
case 47: /* enable-pf */
|
||||
params.pf_enable = true;
|
||||
break;
|
||||
#endif
|
||||
@ -599,6 +658,7 @@ void parse_params(int argc, char *argv[])
|
||||
fprintf(stderr, "Include hostlist load failed\n");
|
||||
exit_clean(1);
|
||||
}
|
||||
if (*params.hostlist_auto_filename) NonEmptyHostlist(¶ms.hostlist);
|
||||
if (!LoadHostLists(¶ms.hostlist_exclude, ¶ms.hostlist_exclude_files))
|
||||
{
|
||||
fprintf(stderr, "Exclude hostlist load failed\n");
|
||||
@ -1020,6 +1080,7 @@ int main(int argc, char *argv[])
|
||||
if (!params.tamper) printf("TCP proxy mode (no tampering)\n");
|
||||
|
||||
signal(SIGHUP, onhup);
|
||||
signal(SIGUSR2, onusr2);
|
||||
|
||||
retval = event_loop(listen_fd,params.binds_last+1);
|
||||
exit_v = retval < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
|
||||
|
@ -24,6 +24,7 @@
|
||||
#include "socks.h"
|
||||
#include "helpers.h"
|
||||
|
||||
|
||||
// keep separate legs counter. counting every time thousands of legs can consume cpu
|
||||
static int legs_local, legs_remote;
|
||||
/*
|
||||
@ -376,6 +377,7 @@ static void free_conn(tproxy_conn_t *conn)
|
||||
}
|
||||
conn_free_buffers(conn);
|
||||
if (conn->partner) conn->partner->partner=NULL;
|
||||
if (conn->track.hostname) free(conn->track.hostname);
|
||||
free(conn);
|
||||
}
|
||||
static tproxy_conn_t *new_conn(int fd, bool remote)
|
||||
@ -890,6 +892,25 @@ static bool handle_proxy_mode(tproxy_conn_t *conn, struct tailhead *conn_list)
|
||||
return false;
|
||||
}
|
||||
|
||||
static void tamper(tproxy_conn_t *conn, uint8_t *segment, size_t segment_buffer_size, size_t *segment_size, size_t *split_pos)
|
||||
{
|
||||
*split_pos=0;
|
||||
if (params.tamper)
|
||||
{
|
||||
if (conn->remote)
|
||||
{
|
||||
if (conn_partner_alive(conn) && !conn->partner->track.bTamperInCutoff)
|
||||
{
|
||||
tamper_in(&conn->partner->track,segment,segment_buffer_size,segment_size);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
tamper_out(&conn->track,segment,segment_buffer_size,segment_size,split_pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#define RD_BLOCK_SIZE 65536
|
||||
#define MAX_WASTE (1024*1024)
|
||||
static bool handle_epoll(tproxy_conn_t *conn, struct tailhead *conn_list, uint32_t evt)
|
||||
@ -942,7 +963,7 @@ static bool handle_epoll(tproxy_conn_t *conn, struct tailhead *conn_list, uint32
|
||||
if (numbytes>0)
|
||||
{
|
||||
#ifdef SPLICE_PRESENT
|
||||
if (!params.tamper || conn->remote)
|
||||
if (!params.tamper || conn->remote && conn->track.bTamperInCutoff)
|
||||
{
|
||||
// incoming data from remote leg we splice without touching
|
||||
// pipe is in the local leg, so its in conn->partner->splice_pipe
|
||||
@ -978,13 +999,11 @@ static bool handle_epoll(tproxy_conn_t *conn, struct tailhead *conn_list, uint32
|
||||
{
|
||||
conn->trd+=rd;
|
||||
|
||||
size_t split_pos=0;
|
||||
size_t split_pos;
|
||||
|
||||
bs = rd;
|
||||
#ifndef SPLICE_PRESENT
|
||||
if (!conn->remote && params.tamper)
|
||||
#endif
|
||||
modify_tcp_segment(buf,sizeof(buf),&bs,&split_pos);
|
||||
|
||||
tamper(conn, buf, sizeof(buf), &bs, &split_pos);
|
||||
|
||||
if (split_pos)
|
||||
{
|
||||
@ -1070,8 +1089,13 @@ static bool read_all_and_buffer(tproxy_conn_t *conn, int buffer_number)
|
||||
conn->partner->wr_buf[buffer_number].len = rd;
|
||||
|
||||
conn->partner->bFlowOut = true;
|
||||
|
||||
size_t split_pos;
|
||||
tamper(conn, conn->partner->wr_buf[buffer_number].data, numbytes, &conn->partner->wr_buf[buffer_number].len, &split_pos);
|
||||
|
||||
if (epoll_update_flow(conn->partner))
|
||||
return true;
|
||||
|
||||
}
|
||||
send_buffer_free(conn->partner->wr_buf+buffer_number);
|
||||
}
|
||||
@ -1254,6 +1278,8 @@ int event_loop(const int *listen_fd, size_t listen_fd_ct)
|
||||
VPRINT("Socket fd=%d (partner_fd=%d, remote=%d) %s so_error=%d (%s)",conn->fd,conn->partner ? conn->partner->fd : 0,conn->remote,se,errn,strerror(errn));
|
||||
proxy_remote_conn_ack(conn,errn);
|
||||
read_all_and_buffer(conn,3);
|
||||
if (errn==ECONNRESET && conn->remote && conn_partner_alive(conn)) rst_in(&conn->partner->track);
|
||||
|
||||
conn_close_with_partner_check(&conn_list,&close_list,conn);
|
||||
continue;
|
||||
}
|
||||
@ -1270,6 +1296,7 @@ int event_loop(const int *listen_fd, size_t listen_fd_ct)
|
||||
{
|
||||
DBGPRINT("EPOLLRDHUP")
|
||||
read_all_and_buffer(conn,2);
|
||||
if (!conn->remote) hup_out(&conn->track);
|
||||
|
||||
if (conn_has_unsent(conn))
|
||||
{
|
||||
|
@ -3,6 +3,7 @@
|
||||
#include <stdbool.h>
|
||||
#include <sys/queue.h>
|
||||
#include <time.h>
|
||||
#include "tamper.h"
|
||||
|
||||
#define BACKLOG 10
|
||||
#define MAX_EPOLL_EVENTS 64
|
||||
@ -27,7 +28,7 @@ typedef uint8_t conn_state_t;
|
||||
// when pos==len its time to free buffer
|
||||
struct send_buffer
|
||||
{
|
||||
char *data;
|
||||
uint8_t *data;
|
||||
size_t len,pos;
|
||||
int ttl;
|
||||
};
|
||||
@ -84,6 +85,8 @@ struct tproxy_conn
|
||||
// buffer cannot be sent if there is unsent data in a lower buffer
|
||||
struct send_buffer wr_buf[4];
|
||||
|
||||
t_ctrack track;
|
||||
|
||||
//Create the struct which contains ptrs to next/prev element
|
||||
TAILQ_ENTRY(tproxy_conn) conn_ptrs;
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user