autohostlist mode

This commit is contained in:
bol-van 2023-10-26 15:12:32 +03:00
parent 6f3a814f73
commit ac574ce2ce
63 changed files with 1584 additions and 573 deletions

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.

View File

@ -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

View File

@ -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

View File

@ -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\""
}
}

View File

@ -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

View File

@ -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

View File

@ -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 :

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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
}

View File

@ -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}"

View File

@ -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()

View File

@ -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));
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);
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",
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("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));
};
}

View File

@ -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
@ -51,9 +53,15 @@ typedef struct
uint32_t seq0, ack0; // starting seq and ack
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

View File

@ -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(&params.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(&params.hostlist_auto_fail_counters, fail_counter);
if (!StrPoolAddStr(&params.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(&params.conntrack);
if (ConntrackPoolFeed(&params.conntrack, ip, ip6hdr, tcphdr, NULL, len_payload, &ctrack, &bReverse))
maybe_cutoff(ctrack, IPPROTO_TCP);
HostFailPoolPurgeRateLimited(&params.hostlist_auto_fail_counters);
}
//ConntrackPoolDump(&params.conntrack);
@ -199,11 +269,48 @@ packet_process_result dpi_desync_tcp_packet(uint32_t fwmark, const char *ifout,
tcp_rewrite_winsize(tcphdr, params.wsize, params.wscale);
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;
if (params.wssize)
{
if (ctrack)
@ -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");
wssize_cutoff(ctrack);
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");
wssize_cutoff(ctrack);
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,12 +776,13 @@ packet_process_result dpi_desync_udp_packet(uint32_t fwmark, const char *ifout,
ConntrackPoolPurge(&params.conntrack);
if (ConntrackPoolFeed(&params.conntrack, ip, ip6hdr, NULL, udphdr, len_payload, &ctrack, &bReverse))
maybe_cutoff(ctrack, IPPROTO_UDP);
HostFailPoolPurgeRateLimited(&params.hostlist_auto_fail_counters);
}
//ConntrackPoolDump(&params.conntrack);
if (bReverse) return res; // nothing to do. do not waste cpu
if (params.desync_cutoff)
{
if (ctrack)
@ -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;
}
}

View File

@ -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)

View File

@ -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);

View File

@ -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)
{

View File

@ -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);

View File

@ -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(&params.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(&params.hostlist_files);
strlist_destroy(&params.hostlist_exclude_files);
if (params.hostlist_exclude)
{
StrPoolDestroy(&params.hostlist_exclude);
params.hostlist_exclude = NULL;
}
if (params.hostlist)
{
StrPoolDestroy(&params.hostlist);
params.hostlist = NULL;
}
StrPoolDestroy(&params.hostlist_exclude);
StrPoolDestroy(&params.hostlist);
HostFailPoolDestroy(&params.hostlist_auto_fail_counters);
}
static void exithelp_clean(void)
{
@ -641,7 +649,7 @@ int main(int argc, char **argv)
int option_index = 0;
bool daemon = false;
char pidfile[256];
srandom(time(NULL));
memset(&params, 0, sizeof(params));
@ -674,10 +682,13 @@ 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(&params.hostlist_files);
LIST_INIT(&params.hostlist_exclude_files);
if (can_drop_root()) // are we root ?
{
params.uid = params.gid = 0x7FFFFFFF; // default uid:gid
@ -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(&params.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(&params.hostlist);
if (!LoadHostLists(&params.hostlist_exclude, &params.hostlist_exclude_files))
{
fprintf(stderr, "Exclude hostlist load failed\n");

View File

@ -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
View 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
View 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);

View File

@ -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;
}
static uint8_t tvb_get_varint(const uint8_t *tvb, uint64_t *value)
bool HttpExtractHost(const uint8_t *data, size_t len, char *host, size_t len_host)
{
switch (*tvb >> 6)
return HttpExtractHeader(data, len, "\nHost:", host, len_host);
}
const char *HttpFind2ndLevelDomain(const char *host)
{
const char *p=NULL;
if (*host)
{
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;
for (p = host + strlen(host)-1; p>host && *p!='.'; p--);
if (*p=='.') for (p--; p>host && *p!='.'; p--);
if (*p=='.') p++;
}
return 0;
return p;
}
static uint8_t tvb_get_size(uint8_t tvb)
// DPI redirects are global redirects to another domain
bool HttpReplyLooksLikeDPIRedirect(const uint8_t *data, size_t len, const char *host)
{
return 1 << (tvb >> 6);
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 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;
}
return 0;
}
bool IsDhtD1(const uint8_t *data, size_t len)
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';
}

View File

@ -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);

View File

@ -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);
}
}

View File

@ -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);

View File

@ -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;

View File

@ -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);

View File

@ -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)
{

View File

@ -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);

View File

@ -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
View 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
View 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);

View File

@ -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)
{
const uint8_t *p, *s, *e=data+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;
while(p<e && (*p==' ' || *p=='\t')) p++;
s=p;
while(s<e && (*s!='\r' && *s!='\n' && *s!=' ' && *s!='\t')) s++;
if (s>p)
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)
size_t slen = s - p;
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;

View File

@ -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);

View File

@ -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);
}
}

View File

@ -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);

View File

@ -25,15 +25,17 @@ 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;
for (method = http_methods; *method; method++)
@ -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,62 +214,161 @@ 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")
}
else
{
switch(params.tlsrec)
{
VPRINT("Not acting on this request")
return;
case tlsrec_sni:
if (TLSFindExt(segment,*size,0,&ext,&elen))
tpos = ext-segment+1; // between typical 1st and 2nd char of hostname
break;
case tlsrec_pos:
tpos = params.tlsrec_pos;
break;
default:
break;
}
}
switch(params.tlsrec)
{
case tlsrec_sni:
if (TLSFindExt(segment,*size,0,&ext,&elen))
tpos = ext-segment+1; // between typical 1st and 2nd char of hostname
break;
case tlsrec_pos:
tpos = params.tlsrec_pos;
break;
default:
break;
}
if (tpos)
{
// construct 2 TLS records from one
uint16_t l = pntoh16(segment+3); // length
if (l>=2)
if (tpos)
{
// length is checked in IsTLSClientHello and cannot exceed buffer size
if (tpos>=l) tpos=1;
VPRINT("making 2 TLS records at pos %zu",tpos)
memmove(segment+5+tpos+5,segment+5+tpos,*size-(5+tpos));
segment[5+tpos] = segment[0];
segment[5+tpos+1] = segment[1];
segment[5+tpos+2] = segment[2];
phton16(segment+5+tpos+3,l-tpos);
phton16(segment+3,tpos);
*size += 5;
// construct 2 TLS records from one
uint16_t l = pntoh16(segment+3); // length
if (l>=2)
{
// length is checked in IsTLSClientHello and cannot exceed buffer size
if (tpos>=l) tpos=1;
VPRINT("making 2 TLS records at pos %zu",tpos)
memmove(segment+5+tpos+5,segment+5+tpos,*size-(5+tpos));
segment[5+tpos] = segment[0];
segment[5+tpos+1] = segment[1];
segment[5+tpos+2] = segment[2];
phton16(segment+5+tpos+3,l-tpos);
phton16(segment+3,tpos);
*size += 5;
}
}
}
if (params.split_pos < *size)
*split_pos = params.split_pos;
return;
if (params.split_pos < *size)
*split_pos = params.split_pos;
}
}
if (params.split_any_protocol && params.split_pos < *size)
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);
}
}
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(&params.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(&params.hostlist_auto_fail_counters, fail_counter);
if (!StrPoolAddStr(&params.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(&params.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(&params.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(&params.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);
}
}

View File

@ -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);

View File

@ -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(&params.hostlist_files);
strlist_destroy(&params.hostlist_exclude_files);
if (params.hostlist_exclude)
{
StrPoolDestroy(&params.hostlist_exclude);
params.hostlist_exclude = NULL;
}
if (params.hostlist)
{
StrPoolDestroy(&params.hostlist);
params.hostlist = NULL;
}
StrPoolDestroy(&params.hostlist_exclude);
StrPoolDestroy(&params.hostlist);
HostFailPoolDestroy(&params.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(&params.hostlist_files);
LIST_INIT(&params.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(&params.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(&params.hostlist);
if (!LoadHostLists(&params.hostlist_exclude, &params.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;

View File

@ -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))
{

View File

@ -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;
};