history purge

This commit is contained in:
bol-van 2021-03-04 14:30:38 +03:00
commit 3703918a4b
179 changed files with 22082 additions and 0 deletions

48
Makefile Normal file
View File

@ -0,0 +1,48 @@
DIRS := nfq tpws ip2net mdig
DIRS_MAC := tpws ip2net mdig
TGT := binaries/my
all: clean
@mkdir -p "$(TGT)"; \
for dir in $(DIRS); do \
find "$$dir" -type f \( -name "*.c" -o -name "*.h" -o -name "*akefile" \) -exec chmod -x {} \; ; \
$(MAKE) -C "$$dir" || exit; \
for exe in "$$dir/"*; do \
if [ -f "$$exe" ] && [ -x "$$exe" ]; then \
mv -f "$$exe" "${TGT}" ; \
ln -fs "../${TGT}/$$(basename "$$exe")" "$$exe" ; \
fi \
done \
done
bsd: clean
@mkdir -p "$(TGT)"; \
for dir in $(DIRS); do \
find "$$dir" -type f \( -name "*.c" -o -name "*.h" -o -name "*akefile" \) -exec chmod -x {} \; ; \
$(MAKE) -C "$$dir" bsd || exit; \
for exe in "$$dir/"*; do \
if [ -f "$$exe" ] && [ -x "$$exe" ]; then \
mv -f "$$exe" "${TGT}" ; \
ln -fs "../${TGT}/$$(basename "$$exe")" "$$exe" ; \
fi \
done \
done
mac: clean
@mkdir -p "$(TGT)"; \
for dir in $(DIRS_MAC); do \
find "$$dir" -type f \( -name "*.c" -o -name "*.h" -o -name "*akefile" \) -exec chmod -x {} \; ; \
$(MAKE) -C "$$dir" mac || exit; \
for exe in "$$dir/"*; do \
if [ -f "$$exe" ] && [ -x "$$exe" ]; then \
mv -f "$$exe" "${TGT}" ; \
ln -fs "../${TGT}/$$(basename "$$exe")" "$$exe" ; \
fi \
done \
done
clean:
@[ -d "$(TGT)" ] && rm -rf "$(TGT)" ; \
for dir in $(DIRS); do \
$(MAKE) -C "$$dir" clean; \
done

BIN
binaries/aarch64/ip2net Executable file

Binary file not shown.

BIN
binaries/aarch64/mdig Executable file

Binary file not shown.

BIN
binaries/aarch64/nfqws Executable file

Binary file not shown.

BIN
binaries/aarch64/tpws Executable file

Binary file not shown.

BIN
binaries/arm/ip2net Executable file

Binary file not shown.

BIN
binaries/arm/mdig Executable file

Binary file not shown.

BIN
binaries/arm/nfqws Executable file

Binary file not shown.

BIN
binaries/arm/tpws Executable file

Binary file not shown.

BIN
binaries/mac64/ip2net Executable file

Binary file not shown.

BIN
binaries/mac64/mdig Executable file

Binary file not shown.

BIN
binaries/mac64/tpws Executable file

Binary file not shown.

BIN
binaries/mips32r1-lsb/ip2net Executable file

Binary file not shown.

BIN
binaries/mips32r1-lsb/mdig Executable file

Binary file not shown.

BIN
binaries/mips32r1-lsb/nfqws Executable file

Binary file not shown.

BIN
binaries/mips32r1-lsb/tpws Executable file

Binary file not shown.

BIN
binaries/mips32r1-msb/ip2net Executable file

Binary file not shown.

BIN
binaries/mips32r1-msb/mdig Executable file

Binary file not shown.

BIN
binaries/mips32r1-msb/nfqws Executable file

Binary file not shown.

BIN
binaries/mips32r1-msb/tpws Executable file

Binary file not shown.

BIN
binaries/mips64r2-msb/ip2net Executable file

Binary file not shown.

BIN
binaries/mips64r2-msb/mdig Executable file

Binary file not shown.

BIN
binaries/mips64r2-msb/nfqws Executable file

Binary file not shown.

BIN
binaries/mips64r2-msb/tpws Executable file

Binary file not shown.

BIN
binaries/ppc/ip2net Executable file

Binary file not shown.

BIN
binaries/ppc/mdig Executable file

Binary file not shown.

BIN
binaries/ppc/nfqws Executable file

Binary file not shown.

BIN
binaries/ppc/tpws Executable file

Binary file not shown.

BIN
binaries/x86/ip2net Executable file

Binary file not shown.

BIN
binaries/x86/mdig Executable file

Binary file not shown.

BIN
binaries/x86/nfqws Executable file

Binary file not shown.

BIN
binaries/x86/tpws Executable file

Binary file not shown.

BIN
binaries/x86_64/ip2net Executable file

Binary file not shown.

BIN
binaries/x86_64/mdig Executable file

Binary file not shown.

BIN
binaries/x86_64/nfqws Executable file

Binary file not shown.

BIN
binaries/x86_64/tpws Executable file

Binary file not shown.

Binary file not shown.

69
config Normal file
View File

@ -0,0 +1,69 @@
# this file is included from init scripts
# change values here
# can help in case /tmp has not enough space
#TMPDIR=/opt/zapret/tmp
# options for ipsets
# too low hashsize can cause memory allocation errors on low RAM systems , even if RAM is enough
# too large hashsize will waste lots of RAM
IPSET_OPT="hashsize 262144 maxelem 2097152"
# options for ip2net. "-4" or "-6" auto added by ipset create script
IP2NET_OPT4="--prefix-length=22-30 --v4-threshold=3/4"
IP2NET_OPT6="--prefix-length=56-64 --v6-threshold=5"
# ipset/*.sh can compress large lists
GZIP_LISTS=1
# command to reload ip/host lists after update
# comment or leave empty for auto backend selection : ipset or ipfw if present
# on BSD systems with PF no auto reloading happens. you must provide your own command
# set to "-" to disable reload
#LISTS_RELOAD="pfctl -f /etc/pf.conf"
# CHOOSE OPERATION MODE
# MODE : nfqws,tpws,filter,custom
# nfqws : use nfqws
# tpws : use tpws
# filter : no daemon, just create ipset or download hostlist
# custom : custom mode. should modify custom init script and add your own code
MODE=tpws
# apply fooling to http
MODE_HTTP=1
# for nfqws only. support http keep alives. enable only if DPI checks for http request in any outgoing packet
MODE_HTTP_KEEPALIVE=0
# apply fooling to https
MODE_HTTPS=1
# none,ipset,hostlist
MODE_FILTER=none
# CHOOSE NFQWS DAEMON OPTIONS for DPI desync mode. run "nfq/nfqws --help" for option list
DESYNC_MARK=0x40000000
NFQWS_OPT_DESYNC="--dpi-desync=fake --dpi-desync-ttl=0 --dpi-desync-fooling=badsum --dpi-desync-fwmark=$DESYNC_MARK"
# CHOOSE TPWS DAEMON OPTIONS. run "tpws/tpws --help" for option list
TPWS_OPT="--hostspell=HOST --split-http-req=method --split-pos=3"
# openwrt only : donttouch,none,software,hardware
FLOWOFFLOAD=donttouch
# for routers based on desktop linux and macos. has no effect in openwrt.
# CHOOSE LAN and optinally WAN NETWORK INTERFACES
# or leave them commented if its not router
#IFACE_LAN=eth0
#IFACE_WAN=eth1
# should init scripts apply firewall rules ?
# set to 0 if firewall control system is present
# openwrt uses fw3 firewall , init never touch fw
INIT_APPLY_FW=1
# do not work with ipv4
#DISABLE_IPV4=1
# do not work with ipv6
DISABLE_IPV6=1
# select which init script will be used to get ip or host list
# possible values : get_user.sh get_antizapret.sh get_combined.sh get_reestr.sh get_hostlist.sh
# comment if not required
GETLIST=get_antifilter_ipsmart.sh

346
docs/bsd.eng.txt Normal file
View File

@ -0,0 +1,346 @@
Supported versions
------------------
FreeBSD 11.x+ , OpenBSD 6.x+, partially MacOS Sierra+
Older versions may work or not. pfSense is not supported.
BSD features
------------
BSD does not have NFQUEUE. Similar mechanism - divert sockets.
In BSD compiling the source from nfq directory result in dvtws binary instead of nfqws.
dvtws shares most of the code with nfqws and offers almost identical parameters.
FreeBSD has 2 firewalls : IPFilter (ipfw) and Packet Filter (PF). OpenBSD has only PF.
To compile sources in FreeBSD use 'make', in OpenBSD - use 'make bsd', in MacOS - use 'make mac'.
Compile all programs : make -C /opt/zapret
Compile all programs with PF support : make -C /opt/zapret CFLAGS=-DUSE_PF
In FreeBSD enable PF only if you use it. Its undesirable if you don't.
PF is enabled automatically in OpenBSD and MacOS.
Divert sockets are internal type sockets in the BSD kernel. They have no relation to network addresses
or network packet exchange. They are identified by a port number 1..65535. Its like queue number in NFQUEUE.
Traffic can be diverted to a divert socket using firewall rule.
If nobody listens on the specified divert port packets are dropped. Its similar to NFQUEUE without --queue-bypass.
ipset/*.sh scripts work with ipfw lookup tables if ipfw is present.
ipfw table is analog to linux ipset. Unlike ipsets ipfw tables share v4 an v6 addresses and subnets.
If ipfw is absent scripts check LISTS_RELOAD config variable.
If its present then scripts execute a command from LISTS_RELOAD.
If LISTS_RELOAD=- scripts do not load tables even if ipfw exists.
PF can load ip tables from a file. To use this feature with ipset/*.sh scripts disable gzip file creation
using "GZIP_LISTS=0" directive in the /opt/zapret/config file.
BSD kernel doesn't implement splice syscall. tpws uses regular recv/send operations with data copying to user space.
Its slower but not critical.
tpws uses nonblocking sockets with linux specific epoll feature.
In BSD systems epoll is emulated by epoll-shim library on top of kqueue.
dvtws uses some programming HACKs, assumptions and knowledge of discovered bugs and limitations.
BSD systems have many limitations, version specific features and bugs in low level networking, especially for ipv6.
Many years have passed but BSD code still has 15-20 year artificial limiters in the code.
dvtws uses additinal divert socket(s) for layer 3 packet injection if raw sockets do not allow it.
It works for the moment but who knows. Such a usage is not very documented.
mdig and ip2net are fully compatible with BSD.
FreeBSD
-------
Divert sockets require special kernel module 'ipdivert'.
Write the following to config files :
/boot/loader.conf (create if absent) :
-----------
ipdivert_load="YES"
net.inet.ip.fw.default_to_accept=1
-----------
/etc/rc.conf :
-----------
firewall_enable="YES"
firewall_script="/etc/rc.firewall.my"
-----------
/etc/rc.firewall.my :
-----------
ipfw -q -f flush
-----------
Later you will add ipfw commands to /etc/rc.firewall.my to be reapplied after reboot.
You can also run zapret daemons from there. Start them with "--daemon" options, for example :
-----------
pkill ^dvtws$
/opt/zapret/nfq/dvtws --port=989 --daemon --dpi-desync=split2
-----------
To restart firewall and daemons run : /etc/rc.d/ipfw restart
Assume LAN='em1', WAN="em0".
tpws transparent mode quick start.
For all traffic:
ipfw delete 100
ipfw add 100 fwd 127.0.0.1,988 tcp from me to any 80,443 proto ip4 xmit em0 not uid daemon
ipfw add 100 fwd ::1,988 tcp from me to any 80,443 proto ip6 xmit em0 not uid daemon
ipfw add 100 fwd 127.0.0.1,988 tcp from any to any 80,443 proto ip4 recv em1
ipfw add 100 fwd ::1,988 tcp from any to any 80,443 proto ip6 recv em1
/opt/zapret/tpws/tpws --port=988 --user=daemon --bind-addr=::1 --bind-addr=127.0.0.1
Process only table zapret with the exception of table nozapret :
ipfw delete 100
ipfw add 100 allow tcp from me to table\(nozapret\) 80,443
ipfw add 100 fwd 127.0.0.1,988 tcp from me to table\(zapret\) 80,443 proto ip4 xmit em0 not uid daemon
ipfw add 100 fwd ::1,988 tcp from me to table\(zapret\) 80,443 proto ip6 xmit em0 not uid daemon
ipfw add 100 allow tcp from any to table\(nozapret\) 80,443 recv em1
ipfw add 100 fwd 127.0.0.1,988 tcp from any to any 80,443 proto ip4 recv em1
ipfw add 100 fwd ::1,988 tcp from any to any 80,443 proto ip6 recv em1
/opt/zapret/tpws/tpws --port=988 --user=daemon --bind-addr=::1 --bind-addr=127.0.0.1
Tables zapret, nozapret, ipban are created by ipset/*.sh scripts the same way as in Linux.
Its a good idea to update tables periodically :
crontab -e
write the line : 0 12 */2 * * /opt/zapret/ipset/get_config.sh
When using ipfw tpws does not require special permissions for transparent mode.
However without root its not possible to bind to ports <1024 and change UID/GID. Without changing UID tpws
will run into recursive loop, and that's why its necessary to write ipfw rules with the right UID.
Redirecting to ports >=1024 is dangerous. If tpws is not running any unprivileged process can
listen to that port and intercept traffic.
dvtws quick start.
For all traffic:
ipfw delete 100
ipfw add 100 divert 989 tcp from any to any 80,443 out not diverted not sockarg xmit em0
/opt/zapret/nfq/dvtws --port=989 --dpi-desync=split2
Process only table zapret with the exception of table nozapret :
ipfw delete 100
ipfw add 100 allow tcp from me to table\(nozapret\) 80,443
ipfw add 100 divert 989 tcp from any to table\(zapret\) 80,443 out not diverted not sockarg xmit em0
/opt/zapret/nfq/dvtws --port=989 --dpi-desync=split2
Reinjection loop avoidance.
FreeBSD artificially ignores sockarg for ipv6 in the kernel.
This limitation is coming from the ipv6 early age. Code is still in "testing" state. 10-20 years. Everybody forgot about it.
dvtws sends ipv6 forged frames using another divert socket (HACK). they can be filtered out using 'diverted'.
ipv4 frames are filtered using 'sockarg'.
PF in FreeBSD:
The setup is similar to OpenBSD, but there are important nuances.
1) Don't forget to build special PF-enabled version of tpws : make CFLAGS=-DUSE_PF
2) It's not possible to redirect to ::1. Need to redirect to the link-local address of the incoming interface.
Look for fe80:... address in ifconfig and use it for redirection target.
3) pf.conf syntax is a bit different from OpenBSD.
4) How to set maximum table size : sysctl net.pf.request_maxcount=2000000
5) The word 'divert-packet' is absent in the pfctl binary, divert-packet rules are not working.
'divert-to' is not the same thing. Looks like its not possible to use dvtws with PF in FreeBSD.
/etc/pf.conf
-----------
rdr pass on em1 inet6 proto tcp to port {80,443} -> fe80::31c:29ff:dee2:1c4d port 988
rdr pass on em1 inet proto tcp to port {80,443} -> 127.0.0.1 port 988
-----------
/opt/zapret/tpws/tpws --port=988 --bind-addr=127.0.0.1 --bind-iface6=em1 --bind-linklocal=force
Its not clear how to do rdr-to outgoing traffic. I could not make route-to scheme work.
OpenBSD
-------
In OpenBSD default tpws bind is ipv6 only. to bind to ipv4 specify --bind-addr=0.0.0.0
Use --bind-addr=0.0.0.0 --bind-addr=:: to achieve the same default bind as in others OSes.
tpws for forwarded traffic only :
/etc/pf.conf
------------
pass in quick on em1 inet proto tcp to port {80,443} rdr-to 127.0.0.1 port 988
pass in quick on em1 inet6 proto tcp to port {80,443} rdr-to ::1 port 988
------------
pfctl -f /etc/pf.conf
tpws --port=988 --user=daemon --bind-addr=::1 --bind-addr=127.0.0.1
Its not clear how to do rdr-to outgoing traffic. I could not make route-to scheme work.
rdr-to support is done using /dev/pf, that's why transparent mode requires root.
dvtws for all traffic:
/etc/pf.conf
------------
pass out quick on em0 proto tcp to port {80,443} divert-packet port 989
------------
pfctl -f /etc/pf.conf
./dvtws --port=989 --dpi-desync=split2
dwtws only for table zapret with the exception of table nozapret :
/etc/pf.conf
------------
table <zapret> file "/opt/zapret/ipset/zapret-ip.txt"
table <zapret-user> file "/opt/zapret/ipset/zapret-ip-user.txt"
table <nozapret> file "/opt/zapret/ipset/zapret-ip-exclude.txt"
pass out quick on em0 inet proto tcp to <nozapret> port {80,443}
pass out quick on em0 inet proto tcp to <zapret> port {80,443} divert-packet port 989
pass out quick on em0 inet proto tcp to <zapret-user> port {80,443} divert-packet port 989
table <zapret6> file "/opt/zapret/ipset/zapret-ip6.txt"
table <zapret6-user> file "/opt/zapret/ipset/zapret-ip-user6.txt"
table <nozapret6> file "/opt/zapret/ipset/zapret-ip-exclude6.txt"
pass out quick on em0 inet6 proto tcp to <nozapret6> port {80,443}
pass out quick on em0 inet6 proto tcp to <zapret6> port {80,443} divert-packet port 989
pass out quick on em0 inet6 proto tcp to <zapret6-user> port {80,443} divert-packet port 989
------------
pfctl -f /etc/pf.conf
./dvtws --port=989 --dpi-desync=split2
dvtws in OpenBSD sends all fakes through a divert socket because raw sockets have critical artificial limitations.
Looks like pf automatically prevent reinsertion of diverted frames. Loop problem does not exist.
Sadly PF auto applies return rule to divert-packet.
Not only outgoing packets go through dvtws but also incoming.
This adds great unneeded overhead that will be the most noticable on http/https downloads.
I could not figure out how to disable this feature.
Thats why you are encouraged to use table filters with your personal blocked site lists.
OpenBSD forcibly recomputes tcp checksum after divert. Thats why most likely
dpi-desync-fooling=badsum will not work. dvtws will warn if you specify this parameter.
ipset scripts do not reload PF by default. To enable reload specify command in /opt/zapret/config :
LISTS_RELOAD="pfctl -f /etc/pf.conf"
Newer pfctl versions can reload tables only : pfctl -Tl -f /etc/pf.conf
But OpenBSD 6.8 pfctl is old enough and does not support that. Newer FreeBSD do.
Don't forget to disable gzip compression :
GZIP_LISTS=0
If some list files do not exist and have references in pf.conf it leads to error.
You need to exclude those tables from pf.conf and referencing them rules.
After configuration is done you can put ipset script :
crontab -e
write the line : 0 12 */2 * * /opt/zapret/ipset/get_config.sh
MacOS
-----
Initially, the kernel of this OS was based on BSD. That's why it is still BSD but a lot was modified by Apple.
As usual a mass commercial project priorities differ from their free counterparts.
Apple guys do what they want.
What everyone have updated long ago they keep old like a mammoth. But who cares ?
MacOS used to have ipfw but it was removed later and replaced by PF.
It looks like divert sockets are internally replaced with raw. Its possible to request a divert socket
but it behaves exactly as raw socket with all its BSD inherited + apple specific bugs and feature.
The fact is that divert-packet in /etc/pf.conf does not work. pfctl binary does not contain the word 'divert'.
dvtws does compile but is useless.
After some efforts tpws works. Apple has removed some important stuff from their newer SDKs (DIOCNATLOOK) making
them undocumented and unsupported. With important definitions copied from an older SDK it was possible to make
transparent mode working again. But this is not guaranteed to work in the future versions.
Another MacOS unique feature is root requirement while polling /dev/pf.
By default tpws drops root. Its necessary to specify --user=root to stay with root.
In other aspects PF behaves very similar to FreeBSD and shares the same pf.conf syntax.
In MacOS redirection works both for passthrough and outgoing traffic. Outgoing redirection requires route-to rule.
Because tpws is forced to run as root to avoid loop its necessary to exempt root from the redirection.
That's why DPI bypass will not work for local requests from root.
If you do ipv6 routing you have to get rid of "secured" ipv6 address assignment.
"secured" addresses are designed to be permanent and not related to the MAC address.
And they really are. Except for link-locals.
If you just reboot the system link-locals will not change. But next day they will change. Not necessary to wait so long.
Just change the system time to tomorrow and reboot. Link-locals will change. (at least they change in vmware guest)
Looks like its a kernel bug. Link locals should not change. Its useless and can be harmful. Cant use LL as a gateway.
The easiest solution is to disable "secured" addresses.
Outgoing connections prefer randomly generated temporary addressesas like in other systems.
Put the string "net.inet6.send.opmode=0" to /etc/sysctl.conf. If not present - create it.
Then reboot the system.
If you dont like this solution you can assign an additional static ipv6 address from fd00::/8 range with /128 prefix
to your LAN interface and use it as the gateway address.
tpws transparent mode only for outgoing connections. en0 - WAN.
/etc/pf.conf
------------
rdr pass on lo0 inet proto tcp from !127.0.0.0/8 to any port {80,443} -> 127.0.0.1 port 988
rdr pass on lo0 inet6 proto tcp from !::1 to any port {80,443} -> fe80::1 port 988
pass out route-to (lo0 127.0.0.1) inet proto tcp from any to any port {80,443} user { >root }
pass out route-to (lo0 fe80::1) inet6 proto tcp from any to any port {80,443} user { >root }
------------
pfctl -ef /etc/pf.conf
/opt/zapret/tpws/tpws --user=root --port=988 --bind-addr=127.0.0.1 --bind-iface6=en0 --bind-linklocal=force
tpws transparent mode for both passthrough and outgoing connections. en0 - WAN, en1 - WAN.
ifconfig en1 | grep fe80
inet6 fe80::bbbb:bbbb:bbbb:bbbb%en1 prefixlen 64 scopeid 0x8
/etc/pf.conf
------------
rdr pass on en1 inet proto tcp from any to any port {80,443} -> 127.0.0.1 port 988
rdr pass on en1 inet6 proto tcp from any to any port {80,443} -> fe80::bbbb:bbbb:bbbb:bbbb port 988
rdr pass on lo0 inet proto tcp from !127.0.0.0/8 to any port {80,443} -> 127.0.0.1 port 988
rdr pass on lo0 inet6 proto tcp from !::1 to any port {80,443} -> fe80::1 port 988
pass out route-to (lo0 127.0.0.1) inet proto tcp from any to any port {80,443} user { >root }
pass out route-to (lo0 fe80::1) inet6 proto tcp from any to any port {80,443} user { >root }
------------
pfctl -ef /etc/pf.conf
/opt/zapret/tpws/tpws --user=root --port=988 --bind-addr=127.0.0.1 --bind-iface6=en0 --bind-linklocal=force --bind-iface6=en1 --bind-linklocal=force
Build from source : make -C /opt/zapret mac
ipset/*.sh scripts work.
MacOS easy install
------------------
install_easy.sh supports MacOS
Shipped precompiled binaries are built for 64-bit MacOS with -mmacosx-version-min=10.8 option.
They should run on all supported MacOS versions.
If no - its easy to build your own. Running 'make' automatically installs developer tools.
!! Internet sharing is not supported !!
Routing is supported but only manually configured through PF.
If you enable internet sharing tpws stops functioning. When you disable internet sharing you may lose web site access.
To fix : pfctl -f /etc/pf.conf
If you need internet sharing use tpws socks mode.
launchd is used for autostart (/Library/LaunchDaemons/zapret.plist)
Control script : /opt/zapret/init.d/macos/zapret
The following commands fork with both tpws and firewall (if INIT_APPLY_FW=1 in config)
/opt/zapret/init.d/macos/zapret start
/opt/zapret/init.d/macos/zapret stop
/opt/zapret/init.d/macos/zapret restart
Work with tpws only :
/opt/zapret/init.d/macos/zapret start-daemons
/opt/zapret/init.d/macos/zapret stop-daemons
/opt/zapret/init.d/macos/zapret restart-daemons
Work with PF only :
/opt/zapret/init.d/macos/zapret start-fw
/opt/zapret/init.d/macos/zapret stop-fw
/opt/zapret/init.d/macos/zapret restart-fw
Reloading PF tables :
/opt/zapret/init.d/macos/zapret reload-fw-tables
Installer configures LISTS_RELOAD in the config so ipset/*.sh scripts automatically reload PF tables.
Installer creates cron job for ipset/get_config.sh, as in OpenWRT.
start-fw script automatically patches /etc/pf.conf inserting there "zapret" anchors.
Auto patching requires pf.conf with apple anchors preserved.
If your pf.conf is highly customized and patching fails you will see the warning. Do not ignore it.
In that case you need to manually insert "zapret" anchors to your pf.conf (keeping the right rule type ordering) :
rdr-anchor "zapret"
anchor "zapret"
unistall_easy.sh unpatches pf.conf
start-fw creates 3 anchor files in /etc/pf.anchors : zapret,zapret-v4,zapret-v6.
Last 2 are referenced by anchor "zapret".
Tables nozapret,nozapret6 belong to anchor "zapret".
Tables zapret,zapret-user belong to anchor "zapret-v4".
Tables zapret6,zapret6-user belong to anchor "zapret-v6".
If an ip version is disabled then corresponding anchor is empty and is not referenced from the anchor "zapret".
Tables are only created for existing list files in the ipset directory.

375
docs/bsd.txt Normal file
View File

@ -0,0 +1,375 @@
Поддерживаемые версии
---------------------
FreeBSD 11.x+ , OpenBSD 6.x+, частично MacOS Sierra+
На более старых может собираться, может не собираться, может работать или не работать.
На FreeBSD 10 собирается и работает dvtws. С tpws есть проблемы из-за слишком старой версии компилятора clang.
Вероятно, будет работать, если обновить компилятор.
На pfSense если и можно завести, то это не просто. Собранные на FreeBSD с той же версией ядра бинарики не работают.
Статические бинарики тоже. Модуль ipdivert отсутствует.
Особенности BSD систем
----------------------
В BSD нет nfqueue. Похожий механизм - divert sockets.
Из каталога "nfq" под BSD собирается dvtws вместо nfqws.
Он разделяет с nfqws большую часть кода и почти совпадает по параметрам командной строки.
FreeBSD содержит 2 фаервола : IPFilter (ipfw) и Packet Filter (PF). OpenBSD содержит только PF.
Под FreeBSD tpws и dvtws собираются через "make", под OpenBSD - "make bsd", под MacOS - "make mac".
FreeBSD make распознает BSDmakefile , OpenBSD и MacOS - нет. Поэтому там используется отдельный target в Makefile.
Сборка всех исходников : make -C /opt/zapret
Сборка всех исходников с поддержкой PF : make -C /opt/zapret CFLAGS=-DUSE_PF
В FreeBSD поддержку PF нужно включать только, если вы его используете. Иначе это нежелательно !
В OpenBSD и MacOS PF при сборке включается автоматически.
divert сокет - внутренний тип сокета ядра BSD. Он не привязывается ни к какому сетевому адресу, не участвует
в обмене данными через сеть и идентифицируется по номеру порта 1..65535. Аналогия с номером очереди NFQUEUE.
На divert сокеты заворачивается трафик посредством правил ipfw или PF.
Если в фаерволе есть правило divert, но на divert порту никто не слушает, то пакеты дропаются.
Это поведение аналогично правилам NFQUEUE без параметра --queue-bypass.
На FreeBSD divert сокеты могут быть только ipv4, хотя на них принимаются и ipv4, и ipv6 фреймы.
На OpenBSD divert сокеты создаются отдельно для ipv4 и ipv6 и работают только с одной версией ip каждый.
На MacOS похоже, что divert сокеты из ядра вырезаны. См подробнее раздел про MacOS.
Отсылка в divert сокет работает аналогично отсылке через raw socket на linux. Передается полностью IP фрейм, начиная
с ip загловка . Эти особенности учитываются в dvtws.
Скрипты ipset/*.sh при наличии ipfw работают с ipfw lookup tables.
Это прямой аналог ipset. lookup tables не разделены на v4 и v6. Они могут содержать v4 и v6 адреса и подсети одновременно.
Если ipfw отсутствует, то действие зависит от переменной LISTS_RELOAD в config.
Если она задана, то выполняется команда из LISTS_RELOAD. В противном случае не делается ничего.
Если LISTS_RELOAD=-, то заполнение таблиц отключается даже при наличии ipfw.
PF может загружать ip таблицы из файла. Чтобы использовать эту возможность следует отключить сжатие gzip для листов
через параметр файла config "GZIP_LISTS=0".
BSD не содержит системного вызова splice. tpws работает через переброску данных в user mode в оба конца.
Это медленнее, но не критически.
Управление асинхронными сокетами в tpws основано на linux-specific механизме epoll.
В BSD для его эмуляции используется epoll-shim - прослойка для эмуляции epoll на базе kqueue.
Некоторые функции dvtws пришлось реализовывать через хаки.
В BSD много ограничений, особенностей и багов при работе с низкоуровневой сетью, в особенности в области ipv6.
Казалось бы столько лет прошло, а в коде все еще сидят ограничители 15-20 летней давности.
Прямая отсылка ipv6 фреймов с измененным source address и вовсе невозможна через raw sockets.
OpenBSD не дает отсылать через raw sockets tcp фреймы.
Там, где функции нельзя было реализовать напрямую, либо их реализация привела бы к залезанию в низкоуровневые дебри,
используются те же divert сокеты. Оказывается через них можно скармливать ядру любые пакеты, обходя ограничения
raw sockets. Не знаю насколько это легально, но пока это работает. Однако, имейте в виду. Что-то может сломаться.
mdig и ip2net полностью работоспособны в BSD. В них нет ничего системо-зависимого.
FreeBSD
-------
divert сокеты требуют специального модуля ядра ipdivert.
Поместите следующие строки в /boot/loader.conf (создать, если отсутствует) :
-----------
ipdivert_load="YES"
net.inet.ip.fw.default_to_accept=1
-----------
В /etc/rc.conf :
-----------
firewall_enable="YES"
firewall_script="/etc/rc.firewall.my"
-----------
/etc/rc.firewall.my :
-----------
ipfw -q -f flush
-----------
В /etc/rc.firewall.my можно дописывать правила ipfw, чтобы они восстанавливались после перезагрузки.
Оттуда же можно запускать и демоны zapret, добавив в параметры "--daemon". Например так :
-----------
pkill ^dvtws$
/opt/zapret/nfq/dvtws --port=989 --daemon --dpi-desync=split2
-----------
Для перезапуска фаервола и демонов достаточно будет сделать : /etc/rc.d/ipfw restart
Краткая инструкция по запуску tpws в прозрачном режиме.
Предполагается, что интерфейс LAN называется em1, WAN - em0.
Для всего трафика :
ipfw delete 100
ipfw add 100 fwd 127.0.0.1,988 tcp from me to any 80,443 proto ip4 xmit em0 not uid daemon
ipfw add 100 fwd ::1,988 tcp from me to any 80,443 proto ip6 xmit em0 not uid daemon
ipfw add 100 fwd 127.0.0.1,988 tcp from any to any 80,443 proto ip4 recv em1
ipfw add 100 fwd ::1,988 tcp from any to any 80,443 proto ip6 recv em1
/opt/zapret/tpws/tpws --port=988 --user=daemon --bind-addr=::1 --bind-addr=127.0.0.1
Для трафика только на таблицу zapret, за исключением таблицы nozapret :
ipfw delete 100
ipfw add 100 allow tcp from me to table\(nozapret\) 80,443
ipfw add 100 fwd 127.0.0.1,988 tcp from me to table\(zapret\) 80,443 proto ip4 xmit em0 not uid daemon
ipfw add 100 fwd ::1,988 tcp from me to table\(zapret\) 80,443 proto ip6 xmit em0 not uid daemon
ipfw add 100 allow tcp from any to table\(nozapret\) 80,443 recv em1
ipfw add 100 fwd 127.0.0.1,988 tcp from any to any 80,443 proto ip4 recv em1
ipfw add 100 fwd ::1,988 tcp from any to any 80,443 proto ip6 recv em1
/opt/zapret/tpws/tpws --port=988 --user=daemon --bind-addr=::1 --bind-addr=127.0.0.1
Таблицы zapret, nozapret, ipban создаются скриптами из ipset по аналогии с Linux.
Обновление скриптов можно забить в cron под root :
crontab -e
Создать строчку "0 12 */2 * * /opt/zapret/ipset/get_config.sh"
При использовании ipfw tpws не требует повышенных привилегий для реализации прозрачного режима.
Однако, без рута невозможен бинд на порты <1024 и смена UID/GID. Без смены UID будет рекурсия,
поэтому правила ipfw нужно создавать с учетом UID, под которым работает tpws.
Переадресация на порты >=1024 может создать угрозу перехвата трафика непривилегированным
процессом, если вдруг tpws не запущен.
Краткая инструкция по запуску dvtws.
Для всего трафика :
ipfw delete 100
ipfw add 100 divert 989 tcp from any to any 80,443 out not diverted not sockarg xmit em0
/opt/zapret/nfq/dvtws --port=989 ---dpi-desync=split2
Для трафика только на таблицу zapret, за исключением таблицы nozapret :
ipfw delete 100
ipfw add 100 allow tcp from me to table\(nozapret\) 80,443
ipfw add 100 divert 989 tcp from any to table\(zapret\) 80,443 out not diverted not sockarg xmit em0
/opt/zapret/nfq/dvtws --port=989 --dpi-desync=split2
Недопущение зацикливания - повторного вхождения фейк пакетов на обработку.
FreeBSD игнорирует sockarg в ipv6.
Это искусственное ограничение в коде ядра, которое тянется уже лет 10-20.
Кто-то в свое время посчитал код сырым, и до сих пор никто не удосужился поправить.
dvtws в FreeBSD отсылает ipv4 фреймы через raw socket. Такие пакеты не 'diverted'. Они отсекаются по 'sockarg'.
Для отсылки ipv6 фейков используется divert socket, потому что ipv6 raw сокеты в BSD не дают самому
формировать IP заголовок и подменять source address. Фейки в ipv6 'diverted'. Они отсекаются по 'diverted'.
В linux nfqws для недопущения зацикливания используется fwmark.
PF в FreeBSD:
Настройка аналогична OpenBSD, но есть важные нюансы.
1) Не забыть собрать специальную версию под PF : make CFLAGS=-DUSE_PF
2) Нельзя сделать ipv6 rdr на ::1. Нужно делать на link-local адрес входящего интерфейса.
Смотрите через ifconfig адрес fe80:... и добавляете в правило
3) Синтаксис pf.conf немного отличается. Более новая версия PF.
4) Лимит на количество элементов таблиц задается так : sysctl net.pf.request_maxcount=2000000
5) Слово 'divert-packet' отсутствует в бинарике pfctl, правила divert-packet выдают ошибку.
'divert-to' - это не то. Не похоже, что в FreeBSD можно завести dvtws через PF.
/etc/pf.conf
-----------
rdr pass on em1 inet6 proto tcp to port {80,443} -> fe80::31c:29ff:dee2:1c4d port 988
rdr pass on em1 inet proto tcp to port {80,443} -> 127.0.0.1 port 988
-----------
/opt/zapret/tpws/tpws --port=988 --bind-addr=127.0.0.1 --bind-iface6=em1 --bind-linklocal=force
В PF непонятно как делать rdr-to с той же системы, где работает proxy. Вариант с route-to у меня не заработал.
OpenBSD
-------
В tpws бинд по умолчанию только на ipv6. для бинда на ipv4 указать "--bind-addr=0.0.0.0"
Используйте --bind-addr=0.0.0.0 --bind-addr=:: для достижения того же результата, как в других ОС по умолчанию.
(лучше все же так не делать, а сажать на определенные внутренние адреса или интерфейсы)
tpws для проходящего трафика :
/etc/pf.conf
------------
pass in quick on em1 inet proto tcp to port {80,443} rdr-to 127.0.0.1 port 988
pass in quick on em1 inet6 proto tcp to port {80,443} rdr-to ::1 port 988
------------
pfctl -f /etc/pf.conf
tpws --port=988 --user=daemon --bind-addr=::1 --bind-addr=127.0.0.1
В PF непонятно как делать rdr-to с той же системы, где работает proxy. Вариант с route-to у меня не заработал.
Поддержка rdr-to реализована через /dev/pf, поэтому прозрачный режим требует root.
dvtws для всего трафика :
/etc/pf.conf
------------
pass out quick on em0 proto tcp to port {80,443} divert-packet port 989
------------
pfctl -f /etc/pf.conf
./dvtws --port=989 --dpi-desync=split2
dvtws для трафика только на таблицу zapret, за исключением таблицы nozapret :
/etc/pf.conf
------------
set limit table-entries 2000000
table <zapret> file "/opt/zapret/ipset/zapret-ip.txt"
table <zapret-user> file "/opt/zapret/ipset/zapret-ip-user.txt"
table <nozapret> file "/opt/zapret/ipset/zapret-ip-exclude.txt"
pass out quick on em0 inet proto tcp to <nozapret> port {80,443}
pass out quick on em0 inet proto tcp to <zapret> port {80,443} divert-packet port 989
pass out quick on em0 inet proto tcp to <zapret-user> port {80,443} divert-packet port 989
table <zapret6> file "/opt/zapret/ipset/zapret-ip6.txt"
table <zapret6-user> file "/opt/zapret/ipset/zapret-ip-user6.txt"
table <nozapret6> file "/opt/zapret/ipset/zapret-ip-exclude6.txt"
pass out quick on em0 inet6 proto tcp to <nozapret6> port {80,443}
pass out quick on em0 inet6 proto tcp to <zapret6> port {80,443} divert-packet port 989
pass out quick on em0 inet6 proto tcp to <zapret6-user> port {80,443} divert-packet port 989
------------
pfctl -f /etc/pf.conf
./dvtws --port=989 --dpi-desync=split2
В OpenBSD dvtws все фейки отсылает через divert socket, поскольку эта возможность через raw sockets заблокирована.
Видимо pf автоматически предотвращает повторный заворот diverted фреймов, поэтому проблемы зацикливания нет.
К сожалению, в PF присутствует "удобная" функция, которая автоматически применяет к правилу divert-packet
обратный трафик. Через divert пойдет все соединение, а не только исходящие пакеты.
Это добавит огромный ненужный overhead по процессингу входящих пакетов в dvtws, который будет наиболее заметен
на скачивании по http/https. Мне не удалось понять как этого избежать.
Поэтому использование фильтр-таблиц крайне рекомендовано !
OpenBSD принудительно пересчитывает tcp checksum после divert, поэтому скорее всего
dpi-desync-fooling=badsum у вас не заработает. При использовании этого параметра
dvtws предупредит о возможной проблеме.
Скрипты из ipset не перезагружают таблицы в PF по умолчанию.
Чтобы они это делали, добавьте параметр в /opt/zapret/config :
LISTS_RELOAD="pfctl -f /etc/pf.conf"
Более новые версии pfctl понимают команду перезагрузить только таблицы : pfctl -Tl -f /etc/pf.conf
Но это не относится к OpenBSD 6.8. В новых FreeBSD есть.
Не забудьте выключить сжатие gzip :
GZIP_LISTS=0
Если в вашей конфигурации какого-то файла листа нет, то его необходимо исключить из правил PF.
Если вдруг листа нет, и он задан в pf.conf, будет ошибка перезагрузки фаервола.
После настройки обновление листов можно поместить в cron :
crontab -e
дописать строчку : 0 12 */2 * * /opt/zapret/ipset/get_config.sh
Если будете пользоваться скриптом ipset/get_combined.sh, установите GNU grep : pkg_add ggrep.
Родной древний как мамонт, безумно медленный с опцией -f.
MacOS
-----
Иначально ядро этой ОС "darwin" основывалось на BSD, потому в ней много похожего на другие версии BSD.
Однако, как и в других массовых коммерческих проектах, приоритеты смещаются в сторону от оригинала.
Яблочники что хотят, то и творят. Меняют, убирают, оставляют какие-то безумно старые версии API и утилит.
То, что уже давно везде обновили, может быть еще древним как мамонт в самой последней версии MacOS.
Но кого это волнует ?
Раньше был ipfw, потом его убрали, заменили на PF.
Есть сомнения, что divert сокеты в ядре остались. Попытка создать divert socket не выдает ошибок,
но полученный сокет ведет себя точно так же, как raw, со всеми его унаследованными косяками + еще яблочно специфическими.
В PF divert-packet не работает. Простой grep бинарика pfctl показывает, что там нет слова "divert",
а в других версиях BSD оно есть. dvtws собирается, но совершенно бесполезен.
tpws удалось адаптировать, он работоспособен. Получение адреса назначения для прозрачного прокси в PF (DIOCNATLOOK)
убрали из заголовков в новых SDK, сделав фактически недокументированным.
В tpws перенесены некоторые определения из более старых версий яблочных SDK. С ними удалось завести прозрачный режим.
Однако, что будет в следующих версиях угадать сложно. Гарантий нет.
Еще одной особенностью PF в MacOS является проверка на рута в момент обращения к /dev/pf, чего нет в остальных BSD.
tpws по умолчанию сбрасывает рутовые привилегии. Необходимо явно указать параметр --user=root.
В остальном PF себя ведет похоже на FreeBSD. Синтаксис pf.conf тот же.
На MacOS работает редирект как с проходящего трафика, так и с локальной системы через route-to.
Поскольку tpws вынужден работать под root, для исключения рекурсии приходится пускать исходящий от root трафик напрямую.
Отсюда имеем недостаток : обход DPI для рута работать не будет.
Если вы пользуетесь MaсOS в качестве ipv6 роутера, то нужно будет решить вопрос с регулярно изменяемым link-local адресом.
С некоторых версий MacOS использует по умолчанию постоянные "secured" ipv6 адреса вместо генерируемых на базе MAC адреса.
Все замечательно, но есть одна проблема. Постоянными остаются только global scope адреса.
Link locals периодически меняются. Смена завязана на системное время. Перезагрузки адрес не меняют,
Но если перевести время на день вперед и перезагрузиться - link local станет другим. (по крайней мере в vmware это так)
Информации по вопросу крайне мало, но тянет на баг. Не должен меняться link local. Скрывать link local не имеет смысла,
а динамический link local нельзя использовать в качестве адреса шлюза.
Проще всего отказаться от "secured" адресов.
Поместите строчку "net.inet6.send.opmode=0" в /etc/sysctl.conf. Затем перезагрузите систему.
Все равно для исходящих соединений будут использоваться temporary адреса, как и в других системах.
Или вам идея не по вкусу, можно прописать дополнительный статический ipv6 из диапазона fd00::/8 -
выберите любой с длиной префикса 128. Это можно сделать в системных настройках, создав дополнительный адаптер на базе
того же сетевого интерфейса, отключить в нем ipv4 и вписать статический ipv6. Он добавится к автоматически настраеваемым.
Настройка tpws на macos в прозрачном режиме только для исходящих запросов, где en0 - WAN :
/etc/pf.conf
------------
rdr pass on lo0 inet proto tcp from !127.0.0.0/8 to any port {80,443} -> 127.0.0.1 port 988
rdr pass on lo0 inet6 proto tcp from !::1 to any port {80,443} -> fe80::1 port 988
pass out route-to (lo0 127.0.0.1) inet proto tcp from any to any port {80,443} user { >root }
pass out route-to (lo0 fe80::1) inet6 proto tcp from any to any port {80,443} user { >root }
------------
pfctl -ef /etc/pf.conf
/opt/zapret/tpws/tpws --user=root --port=988 --bind-addr=127.0.0.1 --bind-iface6=en0 --bind-linklocal=force
Настройка tpws на macos роутере в прозрачном режиме, где en0 - WAN, en1 - LAN.
ifconfig en1 | grep fe80
inet6 fe80::bbbb:bbbb:bbbb:bbbb%en1 prefixlen 64 scopeid 0x8
/etc/pf.conf
------------
rdr pass on en1 inet proto tcp from any to any port {80,443} -> 127.0.0.1 port 988
rdr pass on en1 inet6 proto tcp from any to any port {80,443} -> fe80::bbbb:bbbb:bbbb:bbbb port 988
rdr pass on lo0 inet proto tcp from !127.0.0.0/8 to any port {80,443} -> 127.0.0.1 port 988
rdr pass on lo0 inet6 proto tcp from !::1 to any port {80,443} -> fe80::1 port 988
pass out route-to (lo0 127.0.0.1) inet proto tcp from any to any port {80,443} user { >root }
pass out route-to (lo0 fe80::1) inet6 proto tcp from any to any port {80,443} user { >root }
------------
pfctl -ef /etc/pf.conf
/opt/zapret/tpws/tpws --user=root --port=988 --bind-addr=127.0.0.1 --bind-iface6=en0 --bind-linklocal=force --bind-iface6=en1 --bind-linklocal=force
Сборка : make -C /opt/zapret mac
Скрипты получения листов ipset/*.sh работают.
Если будете пользоваться ipset/get_combined.sh, нужно установить gnu grep через brew.
Имеющийся очень старый и безумно медленный с оцией -f.
MacOS простая установка
-----------------------
В MacOS поддерживается install_easy.sh
В комплекте идут бинарики, собраные под 64-bit с опцией -mmacosx-version-min=10.8.
Они должны работать на всех поддерживаемых версиях macos.
Если вдруг не работают - можно собрать свои. Developer tools ставятся автоматом при запуске make.
!! Internet sharing средствами системы НЕ ПОДДЕРЖИВАЕТСЯ !!
Поддерживается только роутер, настроенный своими силами через PF.
Если вы вдруг включили шаринг, а потом выключили, то доступ к сайтам может пропасть совсем.
Лечение : pfctl -f /etc/pf.conf
Если вам нужен шаринг интернета, лучше отказаться от прозрачного режима и использовать socks.
Для автостарта используется launchd (/Library/LaunchDaemons/zapret.plist)
Управляющий скрипт : /opt/zapret/init.d/macos/zapret
Следующие команды работают с tpws и фаерволом одновременно (если INIT_APPLY_FW=1 в config)
/opt/zapret/init.d/macos/zapret start
/opt/zapret/init.d/macos/zapret stop
/opt/zapret/init.d/macos/zapret restart
Работа только с tpws :
/opt/zapret/init.d/macos/zapret start-daemons
/opt/zapret/init.d/macos/zapret stop-daemons
/opt/zapret/init.d/macos/zapret restart-daemons
Работа только с PF :
/opt/zapret/init.d/macos/zapret start-fw
/opt/zapret/init.d/macos/zapret stop-fw
/opt/zapret/init.d/macos/zapret restart-fw
Перезагрузка всех IP таблиц из файлов :
/opt/zapret/init.d/macos/zapret reload-fw-tables
Инсталятор настраивает LISTS_RELOAD в config, так что скрипты ipset/*.sh автоматически перезагружают IP таблицы в PF.
Автоматически создается cron job на ipset/get_config.sh, по аналогии с openwrt.
При start-fw скрипт автоматически модицифирует /etc/pf.conf, вставляя туда anchors "zapret".
Модификация расчитана на pf.conf, в котором сохранены дефолтные anchors от apple.
Если у вас измененный pf.conf и модификация не удалась, об этом будет предупреждение. Не игнорируйте его.
В этом случае вам нужно вставить в свой pf.conf (в соответствии с порядком типов правил) :
rdr-anchor "zapret"
anchor "zapret"
При деинсталяции через uninstall_easy.sh модификации pf.conf убираются.
start-fw создает 3 файла anchors в /etc/pf.anchors : zapret,zapret-v4,zapret-v6.
Последние 2 подключаются из anchor "zapret".
Таблицы nozapret,nozapret6 принадлежат anchor "zapret".
Таблицы zapret,zapret-user - в anchor "zapret-v4".
Таблицы zapret6,zapret6-user - в anchor "zapret-v6".
Если какая-то версия протокола отключена - соответствующий anchor пустой и не упоминается в anchor "zapret".
Таблицы и правила создаются только на те листы, которые фактически есть в директории ipset.

89
docs/bsdfw.txt Normal file
View File

@ -0,0 +1,89 @@
WAN=em0 LAN=em1
FreeBSD IPFW :
ipfw delete 100
ipfw add 100 fwd 127.0.0.1,988 tcp from me to any 80,443 proto ip4 xmit em0 not uid daemon
ipfw add 100 fwd ::1,988 tcp from me to any 80,443 proto ip6 xmit em0 not uid daemon
ipfw add 100 fwd 127.0.0.1,988 tcp from any to any 80,443 proto ip4 recv em1
ipfw add 100 fwd ::1,988 tcp from any to any 80,443 proto ip6 recv em1
ipfw delete 100
ipfw add 100 allow tcp from me to table\(nozapret\) 80,443
ipfw add 100 fwd 127.0.0.1,988 tcp from me to table\(zapret\) 80,443 proto ip4 xmit em0 not uid daemon
ipfw add 100 fwd ::1,988 tcp from me to table\(zapret\) 80,443 proto ip6 xmit em0 not uid daemon
ipfw add 100 allow tcp from any to table\(nozapret\) 80,443 recv em1
ipfw add 100 fwd 127.0.0.1,988 tcp from any to any 80,443 proto ip4 recv em1
ipfw add 100 fwd ::1,988 tcp from any to any 80,443 proto ip6 recv em1
/opt/zapret/tpws/tpws --port=988 --user=daemon --bind-addr=::1 --bind-addr=127.0.0.1
; Loop avoidance.
; FreeBSD artificially ignores sockarg for ipv6 in the kernel.
; This limitation is coming from the ipv6 early age. Code is still in "testing" state. 10-20 years. Everybody forgot about it.
; dvtws sends ipv6 forged frames using another divert socket (HACK). they can be filtered out using 'diverted'.
ipfw delete 100
ipfw add 100 divert 989 tcp from any to any 80,443 out not diverted not sockarg xmit em0
ipfw delete 100
ipfw add 100 allow tcp from me to table\(nozapret\) 80,443
ipfw add 100 divert 989 tcp from any to table\(zapret\) 80,443 out not diverted not sockarg xmit em0
/opt/zapret/nfq/dvtws --port=989 --debug --dpi-desync=split
sample ipfw NAT setup :
WAN=em0
LAN=em1
ipfw -q flush
ipfw -q nat 1 config if $WAN unreg_only reset
ipfw -q add 10 allow ip from any to any via $LAN
ipfw -q add 20 allow ip from any to any via lo0
ipfw -q add 300 nat 1 ip4 from any to any in recv $WAN
ipfw -q add 301 check-state
ipfw -q add 350 skipto 390 tcp from any to any out xmit $WAN setup keep-state
ipfw -q add 350 skipto 390 udp from any to any out xmit $WAN keep-state
ipfw -q add 360 allow all from any to me in recv $WAN
ipfw -q add 390 nat 1 ip4 from any to any out xmit $WAN
ipfw -q add 10000 allow ip from any to any
Forwarding :
sysctl net.inet.ip.forwarding=1
sysctl net.inet6.ip6.forwarding=1
OpenBSD PF :
; dont know how to rdr-to from local system. doesn't seem to work. only works for routed traffic.
/etc/pf.conf
pass in quick on em1 inet proto tcp to port {80,443} rdr-to 127.0.0.1 port 988
pass in quick on em1 inet6 proto tcp to port {80,443} rdr-to ::1 port 988
pfctl -f /etc/pf.conf
/opt/zapret/tpws/tpws --port=988 --user=daemon --bind-addr=::1 --bind-addr=127.0.0.1
; dvtws works both for routed and local
pass out quick on em0 proto tcp to port {80,443} divert-packet port 989
pfctl -f /etc/pf.conf
./dvtws --port=989 --dpi-desync=split2
; dvtws with table limitations : to zapret,zapret6 but not to nozapret,nozapret6
; reload tables : pfctl -f /etc/pf.conf
set limit table-entries 2000000
table <zapret> file "/opt/zapret/ipset/zapret-ip.txt"
table <zapret-user> file "/opt/zapret/ipset/zapret-ip-user.txt"
table <nozapret> file "/opt/zapret/ipset/zapret-ip-exclude.txt"
pass out quick on em0 inet proto tcp to <nozapret> port {80,443}
pass out quick on em0 inet proto tcp to <zapret> port {80,443} divert-packet port 989
pass out quick on em0 inet proto tcp to <zapret-user> port {80,443} divert-packet port 989
table <zapret6> file "/opt/zapret/ipset/zapret-ip6.txt"
table <zapret6-user> file "/opt/zapret/ipset/zapret-ip-user6.txt"
table <nozapret6> file "/opt/zapret/ipset/zapret-ip-exclude6.txt"
pass out quick on em0 inet6 proto tcp to <nozapret6> port {80,443}
pass out quick on em0 inet6 proto tcp to <zapret6> port {80,443} divert-packet port 989
pass out quick on em0 inet6 proto tcp to <zapret6-user> port {80,443} divert-packet port 989

191
docs/changes.txt Normal file
View File

@ -0,0 +1,191 @@
v1
Initial release
v2
nfqws : command line options change. now using standard getopt.
nfqws : added options for window size changing and "Host:" case change
ISP support : tested on mns.ru and beeline (corbina)
init scripts : rewritten init scripts for simple choise of ISP
create_ipset : now using 'ipset restore', it works much faster
readme : updated. now using UTF-8 charset.
v3
tpws : added transparent proxy (supports TPROXY and DNAT).
can help when ISP tracks whole HTTP session, not only the beginning
ipset : added zapret-hosts-user.txt which contain user defined host names to be resolved
and added to zapret ip list
ISP support : dom.ru support via TPROXY/DNAT
ISP support : successfully tested sknt.ru on 'domru' configuration
other configs will probably also work, but cannot test
compile : openwrt compile howto
v4
tpws : added ability to insert extra space after http method : "GET /" => "GET /"
ISP support : TKT support
v5
nfqws : ipv6 support in nfqws
v6
ipset : added "get_antizapret.sh"
v7
tpws : added ability to insert "." after Host: name
v8
openwrt init : removed hotplug.d/firewall because of race conditions. now only use /etc/firewall.user
v9
ipban : added ipban ipset. place domains banned by ip to zapret-hosts-user-ipban.txt
these IPs must be soxified for both http and https
ISP support : tiera support
ISP support : added DNS filtering to ubuntu and debian scripts
v10
tpws : added split-pos option. split every message at specified position
v11
ipset : scripts optimizations
v12
nfqws : fix wrong tcp checksum calculation if packet length is odd and platform is big-endian
v13
added binaries
v14
change get_antizapret script to work with https://github.com/zapret-info/z-i/raw/master/dump.csv
filter out 192.168.*, 127.*, 10.* from blocked ips
v15
added --hostspell option to nfqws and tpws
ISP support : beeline now catches "host" but other spellings still work
openwrt/LEDE : changed init script to work with procd
tpws, nfqws : minor cosmetic fixes
v16
tpws: split-http-req=method : split inside method name, not after
ISP support : mns.ru changed split pos to 3 (got redirect page with HEAD req : curl -I ej.ru)
v17
ISP support : athome moved from nfqws to tpws because of instability and http request hangs
tpws : added options unixeol,methodeol,hosttab
v18
tpws,nfqws : added hostnospace option
v19
tpws : added hostlist option
v20
added ip2net. ip2net groups ips from iplist into subnets and reduces ipset size twice
v21
added mdig. get_reestr.sh is *real* again
v22
total review of init script logic
dropped support of older debian 7 and ubuntu 12/14 systems
install_bin.sh : auto binaries preparation
docs: readme review. some new topics added, others deleted
docs: VPN setup with policy based routing using wireguard
docs: wireguard modding guide
v23
major init system rewrite
openwrt : separate firewall include /etc/firewall.zapret
install_easy.sh : easy setup on openwrt, debian, ubuntu, centos, fedora, opensuse
v24
separate config from init scripts
gzip support in ipset/*.sh and tpws
v25
init : move to native systemd units
use links to units, init scripts and firewall includes, no more copying
v26
ipv6 support
tpws : advanced bind options
v27
tpws : major connection code rewrite. originally it was derived from not top quality example , with many bugs and potential problems.
next generation connection code uses nonblocking sockets. now its in EXPERIMENTAL state.
v28
tpws : added socks5 support
ipset : major RKN getlist rewrite. added antifilter.network support
v29
nfqws : DPI desync attack
ip exclude system
v30
nfqws : DPI desync attack modes : fake,rst
v31
nfqws : DPI desync attack modes : disorder,disorder2,split,split2.
nfqws : DPI desync fooling mode : badseq. multiple modes supported
v32
tpws : multiple binds
init scripts : run only one instance of tpws in any case
v33
openwrt : flow offloading support
config : MODE refactoring
v34
nfqws : dpi-desync 2 mode combos
nfqws : dpi-desync without parameter no more supported. previously it meant "fake"
nfqws : custom fake http request and tls client hello
v35
limited FreeBSD and OpenBSD support
v36
full FreeBSD and OpenBSD support
v37
limited MacOS support
v38
MacOS easy install

View File

@ -0,0 +1,42 @@
How to compile native programs for use in openwrt
-------------------------------------------------
1) <fetch correct version of openwrt>
cd ~
<chaos calmer>
git clone git://git.openwrt.org/15.05/openwrt.git
<barrier breaker>
git clone git://git.openwrt.org/14.07/openwrt.git
<trunk>
git clone git://git.openwrt.org/openwrt.git
cd openwrt
2) ./scripts/feeds update -a
./scripts/feeds install -a
3) #add zapret packages to build root
#copy package descriptions
copy compile/openwrt/* to ~/openwrt
#copy source code of tpws
copy tpws to ~/openwrt/package/zapret/tpws
#copy source code of nfq
copy nfq to ~/openwrt/package/zapret/nfq
#copy source code of ip2net
copy ip2net to ~/openwrt/package/zapret/ip2net
4) make menuconfig
#select your target architecture
#select packages Network/Zapret/* as "M"
5) make toolchain/compile
6) make package/tpws/compile
make package/nfqws/compile
make package/ip2net/compile
make package/mdig/compile
7) find bin -name tpws*.ipk
#take your tpws*.ipk , nfqws*.ipk , ip2net*.ipk, mdig*.ipk from there

View File

@ -0,0 +1,32 @@
#
include $(TOPDIR)/rules.mk
PKG_NAME:=ip2net
PKG_RELEASE:=1
include $(INCLUDE_DIR)/package.mk
define Package/ip2net
SECTION:=net
CATEGORY:=Network
TITLE:=ip2net
SUBMENU:=Zapret
endef
define Build/Prepare
mkdir -p $(PKG_BUILD_DIR)
$(CP) ./ip2net/* $(PKG_BUILD_DIR)/
endef
define Build/Compile
$(MAKE) -C $(PKG_BUILD_DIR) $(TARGET_CONFIGURE_OPTS)
endef
define Package/ip2net/install
$(INSTALL_DIR) $(1)/opt/zapret/ip2net
$(INSTALL_BIN) $(PKG_BUILD_DIR)/ip2net $(1)/opt/zapret/ip2net
endef
$(eval $(call BuildPackage,ip2net))

View File

@ -0,0 +1 @@
Copy "ip2net" folder here !

View File

@ -0,0 +1,32 @@
#
include $(TOPDIR)/rules.mk
PKG_NAME:=mdig
PKG_RELEASE:=1
include $(INCLUDE_DIR)/package.mk
define Package/mdig
SECTION:=net
CATEGORY:=Network
TITLE:=mdig
SUBMENU:=Zapret
endef
define Build/Prepare
mkdir -p $(PKG_BUILD_DIR)
$(CP) ./mdig/* $(PKG_BUILD_DIR)/
endef
define Build/Compile
$(MAKE) -C $(PKG_BUILD_DIR) $(TARGET_CONFIGURE_OPTS)
endef
define Package/mdig/install
$(INSTALL_DIR) $(1)/opt/zapret/mdig
$(INSTALL_BIN) $(PKG_BUILD_DIR)/mdig $(1)/opt/zapret/mdig
endef
$(eval $(call BuildPackage,mdig))

View File

@ -0,0 +1 @@
Copy "mdig" folder here !

View File

@ -0,0 +1,34 @@
#
include $(TOPDIR)/rules.mk
PKG_NAME:=nfqws
PKG_RELEASE:=1
include $(INCLUDE_DIR)/package.mk
define Package/nfqws
SECTION:=net
CATEGORY:=Network
TITLE:=nfqws
SUBMENU:=Zapret
DEPENDS:=+libnetfilter-queue +libcap +zlib
endef
define Build/Prepare
mkdir -p $(PKG_BUILD_DIR)
$(CP) ./nfq/* $(PKG_BUILD_DIR)/
endef
define Build/Compile
$(MAKE) -C $(PKG_BUILD_DIR) $(TARGET_CONFIGURE_OPTS)
endef
define Package/nfqws/install
$(INSTALL_DIR) $(1)/opt/zapret/nfq
$(INSTALL_BIN) $(PKG_BUILD_DIR)/nfqws $(1)/opt/zapret/nfq
endef
$(eval $(call BuildPackage,nfqws))

View File

@ -0,0 +1 @@
Copy "nfq" folder here !

View File

@ -0,0 +1,33 @@
#
include $(TOPDIR)/rules.mk
PKG_NAME:=tpws
PKG_RELEASE:=1
include $(INCLUDE_DIR)/package.mk
define Package/tpws
SECTION:=net
CATEGORY:=Network
TITLE:=tpws
SUBMENU:=Zapret
DEPENDS:=+zlib +libcap
endef
define Build/Prepare
mkdir -p $(PKG_BUILD_DIR)
$(CP) ./tpws/* $(PKG_BUILD_DIR)/
endef
define Build/Compile
$(MAKE) -C $(PKG_BUILD_DIR) $(TARGET_CONFIGURE_OPTS)
endef
define Package/tpws/install
$(INSTALL_DIR) $(1)/opt/zapret/tpws
$(INSTALL_BIN) $(PKG_BUILD_DIR)/tpws $(1)/opt/zapret/tpws
endef
$(eval $(call BuildPackage,tpws))

View File

@ -0,0 +1 @@
Copy "tpws" folder here !

159
docs/https.txt Normal file
View File

@ -0,0 +1,159 @@
Расскажу как я решал вопрос с блокировкой https на роутере.
На тех провайдерах, что мне доступны, все, кроме одного либо банили https по IP (вообще нет конекта), либо захватывали TLS сессию и она намертво зависала - пакеты больше не приходили. На домру удалось выяснить, что DPI цепляется к SNI (Server Name Indication) в TLS, но сплит TLS запроса не помог. Я пришел к выводу, что https самым разумным будет прозрачно заворачивать в socks.
Tor поддерживает "из коробки" режим transparent proxy. Это можно использовать в теории, но практически - только на роутерах с 128 мб памяти и выше. Таких роутеров не так много. В основном объем памяти 32 или 64 мб. И тор еще и тормозной.
Другой вариант напрашивается, если у вас есть доступ к какой-нибудь unix системе с SSH, где сайты не блокируются. Например, у вас есть VPS вне России. Именно так и поступил.
Понятийно требуются следующие шаги :
1) Выделять IP, на которые надо проксировать трафик. У нас уже имеется ipset "zapret", технология создания которого отработана.
2) Сделать так, чтобы все время при загрузке системы на некотором порту возникал socks.
3) Установить transparent соксификатор. Redsocks прекрасно подошел на эту роль.
4) Завернуть через iptables трафик с порта назначения 443 и на ip адреса из ipset 'zapret' на соксификатор
Буду рассматривать систему на базе openwrt, где уже установлена система обхода dpi "zapret".
По крайней мере нужно иметь заполненный ipset 'zapret', устанавливать tpws или nfqws не обязательно.
Более того, если они на вашей системе не срабатывают, то можно соксифицировать не только https, но и http.
* Сделать так, чтобы все время при загрузке системы на некотором порту возникал socks
Т.к. дефолтный dropbear клиент не поддерживает создание socks, то для начала придется заменить dropbear ssh client на openssh : пакеты openssh-client и openssh-client-utils.
Устанавливать их нужно с опцией opkg --force-overwrite, поскольку они перепишут ssh клиент от dropbear.
После установки пакетов расслабим неоправданно жестокие права : chmod 755 /etc/ssh.
Следует создать пользователя, под которым будем крутить ssh client. Допустим, это будет 'proxy'.
Сначала установить пакет shadow-useradd.
------------------
useradd -d /home/proxy proxy
mkdir -p /home/proxy
chown proxy:proxy /home/proxy
------------------
Openssh ловит разные глюки, если у него нет доступа к /dev/tty.
Добавим в /etc/rc.local строчку : "chmod 666 /dev/tty"
Сгенерируем для него ключ RSA для доступа к ssh серверу.
------------------
su proxy
cd
mkdir -m 700 .ssh
cd .ssh
ssh-keygen
ls
exit
------------------
Должны получиться файлы id_rsa и id_rsa.pub.
Строчку из id_rsa.pub следует добавить на ssh сервер в файл $HOME/.ssh/authorized_keys.
Более подробно о доступе к ssh через авторизацию по ключам : https://beget.com/ru/articles/ssh_by_key
Предположим, ваш ssh сервер - vps.mydomain.com, пользователь называется 'proxy'.
Проверить подключение можно так : ssh -N -D 1098 -l proxy vps.mydomain.com.
Сделайте это под пользователем "proxy", поскольку при первом подключении ssh спросит о правильности hostkey.
Соединение может отвалиться в любой момент, поэтому нужно зациклить запуск ssh.
Для этого лучший вариант - использовать procd - упрощенная замена systemd на openwrt версий BB и выше.
--- /etc/init.d/socks_vps ---
#!/bin/sh /etc/rc.common
START=50
STOP=50
USE_PROCD=1
USERNAME=proxy
COMMAND="ssh -N -D 1098 -l proxy vps.mydomain.com"
start_service() {
procd_open_instance
procd_set_param user $USERNAME
procd_set_param respawn 10 10 0
procd_set_param command $COMMAND
procd_close_instance
}
-----------------------------
Этому файлу нужно дать права : chmod +x /etc/init.d/socks_vps
Запуск : /etc/init.d/socks_vps start
Останов : /etc/init.d/socks_vps stop
Включить автозагрузку : /etc/init.d/socks_vps enable
Проверка : curl -4 --socks5 127.0.0.1:1098 https://rutracker.org
* Организовать прозрачную соксификацию
Установить пакет redsocks.
Конфиг :
-- /etc/redsocks.conf : ---
base {
log_debug = off;
log_info = on;
log = "syslog:local7";
daemon = on;
user = nobody;
group = nogroup;
redirector = iptables;
}
redsocks {
local_ip = 127.0.0.1;
local_port = 1099;
ip = 127.0.0.1;
port = 1098;
type = socks5;
}
---------------------------
После чего перезапускаем : /etc/init.d/redsocks restart
Смотрим появился ли листенер : netstat -tnlp | grep 1099
Автостарт redsocks при таком конфиге не работает, потому что на момент запуска сеть не инициализирована, и у нас даже нет 127.0.0.1.
Вместо штатного автостарта будем вешаться на события поднятия интерфейса. Разберем это позже.
Пока что отключим автостарт : /etc/init.d/redsocks disable
* Завертывание соединений через iptables
Будем завертывать любые tcp соединения на ip из ipset "ipban" и https на ip из ipset "zapret".
--- /etc/firewall.user -----
SOXIFIER_PORT=1099
. /opt/zapret/init.d/openwrt/functions
create_ipset no-update
network_find_wan_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 -j REDIRECT --to-port $SOXIFIER_PORT
ipt OUTPUT -t nat -o $ext_device -p tcp -m set --match-set ipban dst -j REDIRECT --to-port $SOXIFIER_PORT
done
network_get_device DEVICE lan
sysctl -w net.ipv4.conf.$DEVICE.route_localnet=1
ipt prerouting_lan_rule -t nat -p tcp --dport 443 -m set --match-set zapret dst -j DNAT --to 127.0.0.1:$SOXIFIER_PORT
ipt prerouting_lan_rule -t nat -p tcp -m set --match-set ipban dst -j DNAT --to 127.0.0.1:$SOXIFIER_PORT
----------------------------
Внести параметр "reload" в указанное место :
--- /etc/config/firewall ---
config include
option path '/etc/firewall.user'
option reload '1'
----------------------------
Перезапуск : /etc/init.d/firewall restart
Все, теперь можно проверять :
/etc/init.d/redsocks stop
curl -4 https://rutracker.org
# должно обломаться с надписью "Connection refused". если не обламывается - значит ip адрес rutracker.org не в ipset,
# либо не сработали правила фаервола. например, из-за не установленных модулей ipt
/etc/init.d/redsocks start
curl -4 https://rutracker.org
# должно выдать страницу
* Автозапуск redsocks
Я сделал для себя небольшой скриптик, вешающийся на события поднятия и опускания интерфейсов.
--- /etc/hotplug.d/iface/99-exec-on-updown ---
#!/bin/sh
if [ "$ACTION" = ifup ]; then
cmd=$(uci get network.$INTERFACE.exec_on_up)
[ -n "$cmd" ] && $cmd
fi
if [ "$ACTION" = ifdown ]; then
cmd=$(uci get network.$INTERFACE.exec_on_down)
[ -n "$cmd" ] && $cmd
fi
----------------------------------------------
Теперь можно в описания интерфейсов внести в соответствующий раздел :
--- /etc/config/nework ---
config interface 'wan'
........
option exec_on_up '/etc/init.d/redsocks start'
--------------------------
reboot. Заходим снова, смотрим, что есть redsocks, есть ssh, опять проверяем curl -4 https://rutracker.org.
Пробуем зайти на https://rutracker.org с компа внутри локалки.

62
docs/iptables.txt Normal file
View File

@ -0,0 +1,62 @@
For window size changing :
iptables -t mangle -I PREROUTING -p tcp --sport 80 --tcp-flags SYN,ACK SYN,ACK -j NFQUEUE --queue-num 200 --queue-bypass
iptables -t mangle -I PREROUTING -p tcp --sport 80 --tcp-flags SYN,ACK SYN,ACK -m set --match-set zapret src -j NFQUEUE --queue-num 200 --queue-bypass
For outgoing data manipulation ("Host:" case changing) :
iptables -t mangle -I POSTROUTING -p tcp --dport 80 -j NFQUEUE --queue-num 200 --queue-bypass
iptables -t mangle -I POSTROUTING -p tcp --dport 80 -m set --match-set zapret dst -j NFQUEUE --queue-num 200 --queue-bypass
iptables -t mangle -I POSTROUTING -p tcp --dport 80 -m set --match-set zapret dst -m connbytes --connbytes-dir=original --connbytes-mode=packets --connbytes 1:5 -j NFQUEUE --queue-num 200 --queue-bypass
For dpi desync attack :
iptables -t mangle -I POSTROUTING -p tcp -m multiport --dports 80,443 -m connbytes --connbytes-dir=original --connbytes-mode=packets --connbytes 2:4 -m mark ! --mark 0x40000000/0x40000000 -j NFQUEUE --queue-num 200 --queue-bypass
For TPROXY :
sysctl -w net.ipv4.ip_forward=1
iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
ip -f inet rule add fwmark 1 lookup 100
ip -f inet route add local default dev lo table 100
# prevent loop
iptables -t filter -I INPUT -p tcp --dport 988 -j REJECT
iptables -t mangle -A PREROUTING -i eth1 -p tcp --dport 80 -j MARK --set-mark 1
iptables -t mangle -A PREROUTING -i eth1 -p tcp --dport 80 -j TPROXY --tproxy-mark 0x1/0x1 --on-port 988
iptables -t mangle -A PREROUTING -i eth1 -p tcp --dport 80 -m set --match-set zapret dst -j MARK --set-mark 1
iptables -t mangle -A PREROUTING -i eth1 -p tcp --dport 80 -m mark --mark 0x1/0x1 -j TPROXY --tproxy-mark 0x1/0x1 --on-port 988
For DNAT :
# run tpws as user "tpws". its required to avoid loops.
sysctl -w net.ipv4.conf.eth1.route_localnet=1
iptables -t nat -I PREROUTING -p tcp --dport 80 -j DNAT --to 127.0.0.127:988
iptables -t nat -I OUTPUT -p tcp --dport 80 -m owner ! --uid-owner tpws -j DNAT --to 127.0.0.127:988
Reset all iptable rules :
iptables -F
iptables -X
iptables -t nat -F
iptables -t nat -X
iptables -t mangle -F
iptables -t mangle -X
iptables -t raw -F
iptables -t raw -X
Reset iptable policies :
iptables -P INPUT ACCEPT
iptables -P FORWARD ACCEPT
iptables -P OUTPUT ACCEPT
iptables -t mangle -P POSTROUTING ACCEPT
iptables -t mangle -P PREROUTING ACCEPT
iptables -t mangle -P INPUT ACCEPT
iptables -t mangle -P FORWARD ACCEPT
iptables -t mangle -P OUTPUT ACCEPT
iptables -t raw -P PREROUTING ACCEPT
iptables -t raw -P OUTPUT ACCEPT

643
docs/readme.eng.txt Normal file
View File

@ -0,0 +1,643 @@
What is it for
--------------
Bypass the blocking of http/https web sites on DPI without the use of third-party servers.
The project is mainly aimed at the Russian audience to fight russian regulator named "Roskomnadzor".
Some features of the project are russian reality specific (such as getting list of sites
blocked by Roskomnadzor), but most others are common.
(EXPERIMENTAL) FreeBSD and OpenBSD are also supported.
(EXPERIMENTAL, PARTIAL) MacOS limited support.
see docs/bsd.eng.txt
How it works
------------
In the simplest case you are dealing with passive DPI. Passive DPI can read passthrough traffic,
inject its own packets, but cannot drop packets.
If the request is prohibited the passive DPI will inject its own RST packet and optionally http redirect packet.
If fake packets from DPI are only sent to client, you can use iptables commands to drop them if you can write
correct filter rules. This requires manual in-deep traffic analysis and tuning for specific ISP.
This is how we bypass the consequences of a ban trigger.
If the passive DPI sends an RST packet also to the server, there is nothing you can do about it.
Your task is to prevent ban trigger from firing up. Iptables alone will not work.
This project is aimed at preventing the ban rather than eliminating its consequences.
To do that send what DPI does not expect and what breaks its algorithm of recognizing requests and blocking them.
Some DPIs cannot recognize the http request if it is divided into TCP segments.
For example, a request of the form "GET / HTTP / 1.1 \ r \ nHost: kinozal.tv ......"
we send in 2 parts: first go "GET", then "/ HTTP / 1.1 \ r \ nHost: kinozal.tv .....".
Other DPIs stumble when the "Host:" header is written in another case: for example, "host:".
Sometimes work adding extra space after the method: "GET /" => "GET /"
or adding a dot at the end of the host name: "Host: kinozal.tv."
There is also more advanced magic for bypassing DPI at the packet level.
How to put this into practice in the linux system
-------------------------------------------------
In short, the options can be classified according to the following scheme:
1) Passive DPI not sending RST to the server. ISP tuned iptables commands can help.
This option is out of the scope of the project. If you do not allow ban trigger to fire, then you wont have to
deal with its consequences.
2) Modification of the TCP connection at the stream level. Implemented through a proxy or transparent proxy.
3) Modification of TCP connection at the packet level. Implemented through the NFQUEUE handler and raw sockets.
For options 2 and 3, tpws and nfqws programs are implemented, respectively.
You need to run them with the necessary parameters and redirect certain traffic with iptables.
To redirect a TCP connection to a transparent proxy, the following commands are used:
forwarded fraffic :
iptables -t nat -I PREROUTING -i <internal_interface> -p tcp --dport 80 -j DNAT --to 127.0.0.127:988
outgoing traffic :
iptables -t nat -I OUTPUT -o <external_interface> -p tcp --dport 80 -m owner ! --uid-owner tpws -j DNAT --to 127.0.0.127:988
DNAT on localhost works in the OUTPUT chain, but does not work in the PREROUTING chain without enabling the route_localnet parameter:
sysctl -w net.ipv4.conf.<internal_interface>.route_localnet=1
You can use "-j REDIRECT --to-port 988" instead of DNAT, but in this case the transparent proxy process
should listen on the ip address of the incoming interface or on all addresses. Listen all - not good
in terms of security. Listening one (local) is possible, but automated scripts will have to recognize it,
then dynamically enter it into the command. In any case, additional efforts are required.
Using route_localnet can also introduce some security risks. You make available from internal_interface everything
bound to 127.0.0.0/8. Services are usually bound to 127.0.0.1. Its possible to deny input to 127.0.0.1 from all interfaces except lo
or bind tpws to any other IP from 127.0.0.0/8 range, for example to 127.0.0.127, and allow incomings only to that IP :
iptables -A INPUT ! -i lo -d 127.0.0.127 -j ACCEPT
iptables -A INPUT ! -i lo -d 127.0.0.0/8 -j DROP
Owner filter is necessary to prevent recursive redirection of connections from tpws itself.
tpws must be started under OS user "tpws".
NFQUEUE redirection of the outgoing traffic and forwarded traffic going towards the external interface,
can be done with the following commands:
iptables -t mangle -I POSTROUTING -o <external_interface> -p tcp --dport 80 -j NFQUEUE --queue-num 200 --queue-bypass
In order not to touch the traffic to unblocked addresses, you can take a list of blocked hosts, resolve it
into IP addresses and put them to ipset 'zapret', then add a filter to the command:
iptables -t mangle -I POSTROUTING -o <external_interface> -p tcp --dport 80 -m set --match-set zapret dst -j NFQUEUE --queue-num 200 --queue-bypass
Some DPIs catch only the first http request, ignoring subsequent requests in a keep-alive session.
Then we can reduce CPU load, refusing to process unnecessary packets.
iptables -t mangle -I POSTROUTING -o <внешний_интерфейс> -p tcp --dport 80 -m connbytes --connbytes-dir=original --connbytes-mode=packets --connbytes 2:4 -m mark ! --mark 0x40000000/0x40000000 -m set --match-set zapret dst -j NFQUEUE --queue-num 200 --queue-bypass
Mark filter does not allow nfqws-generated packets to enter the queue again.
Its necessary to use this filter when also using "connbytes 2:4". Without it packet ordering can be changed breaking the whole idea.
ip6tables
---------
ip6tables work almost exactly the same way as ipv4, but there are a number of important nuances.
In DNAT, you should take the address --to in square brackets. For example :
ip6tables -t nat -I OUTPUT -o <external_interface> -p tcp --dport 80 -m owner ! --uid-owner tpws -j DNAT --to [::1]:988
The route_localnet parameter does not exist for ipv6.
DNAT to localhost (:: 1) is possible only in the OUTPUT chain.
In the PREROUTING DNAT chain, it is possible to any global address or to the link local address of the same interface
the packet came from.
NFQUEUE works without changes.
When it will not work
----------------------
* If DNS server returns false responses. ISP can return false IP addresses or not return anything
when blocked domains are queried. If this is the case change DNS to public ones, such as 8.8.8.8 or 1.1.1.1.
Sometimes ISP hijacks queries to any DNS server. Dnscrypt or dns-over-tls help.
* If blocking is done by IP.
* If a connection passes through a filter capable of reconstructing a TCP connection, and which
follows all standards. For example, we are routed to squid. Connection goes through the full OS tcpip stack,
fragmentation disappears immediately as a means of circumvention. Squid is correct, it will find everything
as it should, it is useless to deceive him.
BUT. Only small providers can afford using squid, since it is very resource intensive.
Large companies usually use DPI, which is designed for much greater bandwidth.
nfqws
-----
This program is a packet modifier and a NFQUEUE queue handler.
For BSD systems there is dvtws. Its built from the same source and has almost the same parameters (see bsd.eng.txt).
nfqws takes the following parameters:
--debug=0|1 ; 1=print debug info
--qnum=<nfqueue_number>
--wsize=<window_size> ; set window size. 0 = do not modify (obsolete !)
--hostcase ; change Host: => host:
--hostspell=HoSt ; exact spelling of the "Host" header. must be 4 chars. default is "host"
--hostnospace ; remove space after Host: and add it to User-Agent: to preserve packet size
--domcase ; mix domain case after Host: like this : TeSt.cOm
--daemon ; daemonize
--pidfile=<filename> ; write pid to file
--user=<username> ; drop root privs
--uid=uid[:gid] ; drop root privs
--dpi-desync[=<mode>][,<mode2>] ; try to desync dpi state. modes : fake rst rstack disorder disorder2 split split2
--dpi-desync-fwmark=<int|0xHEX> ; override fwmark for desync packet. default = 0x40000000
--dpi-desync-ttl=<int> ; set ttl for desync packet
--dpi-desync-fooling=none|md5sig|ts|badseq|badsum ; can take multiple comma separated values
--dpi-desync-retrans=0|1 ; (fake,rst,rstack only) 0(default)=reinject original data packet after fake 1=drop original data packet to force its retransmission
--dpi-desync-repeats=<N> ; send every desync packet N times
--dpi-desync-skip-nosni=0|1 ; 1(default)=do not apply desync to requests without hostname in the SNI
--dpi-desync-split-pos=<1..1500> ; (for split* and disorder* only) split TCP packet at specified position
--dpi-desync-any-protocol=0|1 ; 0(default)=desync only http and tls 1=desync any nonempty data packet
--dpi-desync-fake-http=<filename> ; file containing fake http request. replacement for built-in
--dpi-desync-fake-tls=<filename> ; file containing fake TLS ClientHello (for https). replacement for built-in
--hostlist=<filename> ; apply fooling only to the listed hosts (one host per line, subdomains auto apply)
The manipulation parameters can be combined in any way.
WARNING. --wsize parameter is now not used anymore in scripts. TCP split can be achieved using DPI desync attack.
DPI DESYNC ATTACK
After completion of the tcp 3-way handshake, the first data packet from the client goes.
It usually has "GET / ..." or TLS ClientHello. We drop this packet, replacing with something else.
It can be a fake version with another harmless but valid http or https request (fake), tcp reset packet (rst,rstack),
split into 2 segments original packet with fake segment in the middle (disorder).
In articles these attack have names "TCB desynchronization" and "TCB teardown".
Fake packet must reach DPI, but do not reach the destination server.
The following means are available: set a low TTL, send a packet with bad checksum,
add tcp option "MD5 signature". All of them have their own disadvantages :
* md5sig does not work on all servers
* badsum doesn't work if your device is behind NAT which does not pass invalid packets.
Linux NAT by default does not pass them without special setting "sysctl -w net.netfilter.nf_conntrack_checksum=0"
Openwrt sets it from the box, other routers in most cases dont, and its not always possible to change it.
If nfqws is on the router, its not neccessary to switch of "net.netfilter.nf_conntrack_checksum".
Fake packet doesn't go through FORWARD chain, it goes through OUTPUT. But if your router is behind another NAT, for example ISP NAT,
and that NAT does not pass invalid packets, you cant do anything.
* badseq packets will be dropped by server, but DPI also can ignore them
* TTL looks like the best option, but it requires special tuning for earch ISP. If DPI is further than local ISP websites
you can cut access to them. Manual IP exclude list is required. Its possible to use md5sig with ttl.
This way you cant hurt anything, but good chances it will help to open local ISP websites.
If automatic solution cannot be found then use zapret-hosts-user-exclude.txt.
--dpi-desync-fooling takes multiple comma separated values.
For fake,rst,rstack modes original packet can be sent after the fake one or just dropped.
If its dropped OS will perform first retransmission after 0.2 sec, then the delay increases exponentially.
Delay can help to make sure fake and original packets are properly ordered and processed on DPI.
When dpi-desync-retrans=1 its mandatory to use connbytes in iptables rule. Otherwise loop happens.
Disorder mode splits original packet and sends packets in the following order :
1. 2nd segment
2. fake 1st segment, data filled with zeroes
3. 1st segment
4. fake 1st segment, data filled with zeroes (2nd copy)
Original packet is always dropped. --dpi-desync-split-pos sets split position (default 3).
If position is higher than packet length, pos=1 is used.
This sequence is designed to make reconstruction of critical message as difficult as possible.
Fake segments may not be required to bypass some DPIs, but can potentially help if more sophisticated reconstruction
algorithms are used.
Mode 'disorder2' disables sending of fake segments.
Split mode is very similar to disorder but without segment reordering :
1. fake 1st segment, data filled with zeroes
2. 1st segment
3. fake 1st segment, data filled with zeroes (2nd copy)
4. 2nd segment
Mode 'split2' disables sending of fake segments. It can be used as a faster alternative to --wsize.
In disorder2 and split2 modes no fake packets are sent, so ttl and fooling options are not required.
There are DPIs that analyze responses from the server, particularly the certificate from the ServerHello
that contain domain name(s). The ClientHello delivery confirmation is an ACK packet from the server
with ACK sequence number corresponding to the length of the ClientHello+1.
In the disorder variant, a selective acknowledgement (SACK) usually arrives first, then a full ACK.
If, instead of ACK or SACK, there is an RST packet with minimal delay, DPI cuts you off at the request stage.
If the RST is after a full ACK after a delay of about ping to the server, then probably DPI acts
on the server response. The DPI may be satisfied with good ClientHello and stop monitoring the TCP session
without checking ServerHello. Then you were lucky. 'fake' option could work.
If it does not stop monitoring and persistently checks the ServerHello, also performing reconstruction of TCP segments,
doing something about it is hardly possible without the help of the server.
The best solution is to enable TLS 1.3 support on the server. TLS 1.3 sends the server certificate in encrypted form.
This is recommendation to all admins of blocked sites. Enable TLS 1.3. You will give more opportunities to overcome DPI.
Hosts are extracted from plain http request Host: header and SNI of ClientHelllo TLS message.
Subdomains are applied automatically. gzip lists are supported.
iptables for performing the attack on the first packet :
iptables -t mangle -I POSTROUTING -o <external_interface> -p tcp -m multiport --dports 80,443 -m connbytes --connbytes-dir=original --connbytes-mode=packets --connbytes 2:4 -m mark ! --mark 0x40000000/0x40000000 -j NFQUEUE --queue-num 200 --queue-bypass
This is good if DPI does not track all requests in http keep-alive session.
If it does, then pass all outgoing packets for http and only first data packet for https :
iptables -t mangle -I POSTROUTING -o <external_interface> -p tcp --dport 443 -m connbytes --connbytes-dir=original --connbytes-mode=packets --connbytes 2:4 -m mark ! --mark 0x40000000/0x40000000 -j NFQUEUE --queue-num 200 --queue-bypass
iptables -t mangle -I POSTROUTING -o <external_interface> -p tcp --dport 80 -m mark ! --mark 0x40000000/0x40000000 -j NFQUEUE --queue-num 200 --queue-bypass
mark is needed to keep away generated packets from NFQUEUE. nfqws sets fwmark when it sends generated packets.
nfqws can internally filter marked packets. but when connbytes filter is used without mark filter
packet ordering can be changed breaking the whole idea of desync attack.
DESYNC COMBOS
dpi-desync parameter can take 2 comma separated arguments.
1st phase mode can be fake,rst,rstack, 2nd phase mode - disorder,disorder2,split,split2.
Can be useful for ISPs with more than one DPI.
VIRTUAL MACHINES
Most of nfqws packet magic does not work from VMs powered by virtualbox and vmware when network is NATed.
Hypervisor forcibly changes ttl and does not forward fake packets.
Set up bridge networking.
tpws
-----
tpws is transparent proxy.
--debug=0|1|2 ; 0(default)=silent 1=verbose 2=debug
--bind-addr=<v4_addr>|<v6_addr>; for v6 link locals append %interface_name : fe80::1%br-lan
--bind-iface4=<interface_name> ; bind to the first ipv4 addr of interface
--bind-iface6=<interface_name> ; bind to the first ipv6 addr of interface
--bind-linklocal=prefer|force ; prefer or force ipv6 link local
--bind-wait-ifup=<sec> ; wait for interface to appear and up
--bind-wait-ip=<sec> ; after ifup wait for ip address to appear up to N seconds
--bind-wait-ip-linklocal=<sec> ; accept only link locals first N seconds then any
--bind-wait-only ; wait for bind conditions satisfaction then exit. return code 0 if success.
--port=<port> ; port number to listen on
--socks ; implement socks4/5 proxy instead of transparent proxy
--local-rcvbuf=<bytes> ; SO_RCVBUF for local legs
--local-sndbuf=<bytes> ; SO_SNDBUF for local legs
--remote-rcvbuf=<bytes> ; SO_RCVBUF for remote legs
--remote-sndbuf=<bytes> ; SO_SNDBUF for remote legs
--skip-nodelay ; do not set TCP_NODELAY for outgoing connections. incompatible with split.
--no-resolve ; disable socks5 remote dns
--maxconn=<max_connections> ; max number of local legs
--maxfiles=<max_open_files> ; max file descriptors (setrlimit). min requirement is (X*connections+16), where X=6 in tcp proxy mode, X=4 in tampering mode.
; its worth to make a reserve with 1.5 multiplier. by default maxfiles is (X*connections)*1.5+16
--max-orphan-time=<sec> ; if local leg sends something and closes and remote leg is still connecting then cancel connection attempt after N seconds
--hostlist=<filename> ; only act on host in the list (one host per line, subdomains auto apply, gzip lists supported)
--split-http-req=method|host ; split http request at specified logical position.
--split-pos=<numeric_offset> ; split at specified pos. split-http-req takes precedence over split-pos for http reqs.
--split-any-protocol ; split not only http and https
--hostcase ; change Host: => host:
--hostspell ; exact spelling of "Host" header. must be 4 chars. default is "host"
--hostdot ; add "." after Host: name
--hosttab ; add tab after Host: name
--hostnospace ; remove space after Host:
--hostpad=<bytes> ; add dummy padding headers before Host:
--domcase ; mix domain case after Host: like this : TeSt.cOm
--methodspace ; add extra space after method
--methodeol ; add end-of-line before method
--unixeol ; replace 0D0A to 0A
--daemon ; daemonize
--pidfile=<filename> ; write pid to file
--user=<username> ; drop root privs
--uid=uid[:gid] ; drop root privs
The manipulation parameters can be combined in any way.
split-http-req takes precedence over split-pos for http reqs.
split-pos works by default only on http and TLS ClientHello. use --split-any-protocol to act on any packet
tpws can bind to multiple interfaces and IP addresses (up to 32).
Port number is always the same.
Parameters --bind-iface* и --bind-addr create new bind.
Other parameters --bind-* are related to the last bind.
To bind to all ipv4 specify --bind-addr "0.0.0.0", all ipv6 - "::". --bind-addr="" - mean bind to all ipv4 and ipv6.
If no binds are specified default bind to all ipv4 and ipv6 addresses is created.
The --bind-wait* parameters can help in situations where you need to get IP from the interface, but it is not there yet, it is not raised
or not configured.
In different systems, ifup events are caught in different ways and do not guarantee that the interface has already received an IP address of a certain type.
In the general case, there is no single mechanism to hang oneself on an event of the type "link local address appeared on the X interface."
in socks proxy mode no additional system privileges are required
connection to local IPs of the system where tpws runs are prohibited
tpws supports remote dns resolving (curl : --socks5-hostname firefox : socks_remote_dns=true) , but does it in blocking mode.
tpws uses async sockets for all activity but resolving can break this model.
if tpws serves many clients it can cause trouble. also DoS attack is possible against tpws.
if remote resolving causes trouble configure clients to use local name resolution and use
--no-resolve option on tpws side.
Ways to get a list of blocked IP
--------------------------------
1) Enter the blocked domains to ipset/zapret-hosts-user.txt and run ipset/get_user.sh
At the output, you get ipset/zapret-ip-user.txt with IP addresses.
2) ipset/get_reestr_*.sh. Russian specific
3) ipset/get_antifilter_*.sh. Russian specific
4) ipset/get_config.sh. This script calls what is written into the GETLIST variable from the config file.
If the variable is not defined, then only lists for ipsets nozapret/nozapret6 are resolved.
So, if you're not russian, the only way for you is to manually add blocked domains.
Or write your own ipset/get_iran_blocklist.sh , if you know where to download this one.
On routers, it is not recommended to call these scripts more than once in 2 days to minimize flash memory writes.
ipset/create_ipset.sh executes forced ipset update.
With "no-update" parameter create_ipset.sh creates ipset but populate it only if it was actually created.
It's useful when multiple subsequent calls are possible to avoid wasting of cpu time redoing the same job.
Ipset loading is resource consuming. Its a good idea to call create_ipset without "no-update" parameter
only once a several days. Use it with "no-update" option in other cases.
ipset scripts automatically call ip2net utility.
ip2net helps to reduce ip list size by combining IPs to subnets. Also it cuts invalid IPs from the list.
Stored lists are already processed by ip2net. They are error free and ready for loading.
create_ipset.sh supports loading ip lists from gzip files. First it looks for the filename with the ".gz" extension,
such as "zapret-ip.txt.gz", if not found it falls back to the original name "zapret-ip.txt".
So your own get_iran_blockslist.sh can use "zz" function to produce gz. Study how other russian get_XXX.sh work.
Gzipping helps saving a lot of precious flash space on embedded systems.
User lists are not gzipped because they are not expected to be very large.
You can add a list of domains to ipset/zapret-hosts-user-ipban.txt. Their ip addresses will be placed
in a separate ipset "ipban". It can be used to route connections to transparent proxy "redsocks" or VPN.
IPV6: if ipv6 is enabled, then additional txt's are created with the same name, but with a "6" at the end before the extension.
zapret-ip.txt => zapret-ip6.txt
The ipsets zapret6 and ipban6 are created.
IP EXCLUSION SYSTEM. All scripts resolve zapret-hosts-user-exclude.txt file, creating zapret-ip-exclude.txt and zapret-ip-exclude6.txt.
They are the source for ipsets nozapret/nozapret6. All rules created by init scripts are created with these ipsets in mind.
The IPs placed in them are not involved in the process.
zapret-hosts-user-exclude.txt can contain domains, ipv4 and ipv6 addresses or subnets.
FreeBSD. ipset/*.sh scripts also work in FreeBSD. Instead of ipset they create ipfw lookup tables with the same names as in Linux.
ipfw tables can store both ipv4 and ipv6 addresses and subnets. There's no 4 and 6 separation.
LISTS_RELOAD config parameter defines a custom lists reloading command.
Its useful on BSD systems with PF.
LISTS_RELOAD=- disables reloading ip list backend.
Domain name filtering
---------------------
An alternative to ipset is to use tpws or nfqws with a list of domains. Only one list is supported.
Enter the blocked domains to ipset/zapret-hosts-users.txt. Remove ipset/zapret-hosts.txt.gz.
Then the init script will run tpws with the zapret-hosts-users.txt list.
Other option ( Roskomnadzor list - get_hostlist.sh ) is russian specific.
You can write your own replacement for get_hostlist.sh.
When filtering by domain name, daemons should run without filtering by ipset.
When using large regulator lists estimate the amount of RAM on the router !
Choosing parameters
-------------------
The file /opt/zapret/config is used by various components of the system and contains basic settings.
It needs to be viewed and edited if necessary.
Main mode :
tpws - use tpws
tpws - use nfqws
filter - only fill ipset or load hostlist
custom - use custom script for running daemons and establishing firewall rules
MODE=tpws
Enable http fooling :
MODE_HTTP=1
Apply fooling to keep alive http sessions. Only applicable to nfqws. Tpws always fool keepalives.
Not enabling this can save CPU time.
MODE_HTTP_KEEPALIVE=0
Enable https fooling :
MODE_HTTPS=1
Host filtering mode :
none - apply fooling to all hosts
ipset - limit fooling to hosts from ipset zapret/zapret6
hostlist - limit fooling to hosts from hostlist
MODE_FILTER=none
Its possible to change manipulation options used by tpws :
TPWS_OPT="--hostspell=HOST --split-http-req=method --split-pos=3"
nfqws options for DPI desync attack:
DESYNC_MARK=0x40000000
NFQWS_OPT_DESYNC="--dpi-desync=fake --dpi-desync-ttl=0 --dpi-desync-fooling=badsum --dpi-desync-fwmark=$DESYNC_MARK"
flow offloading control (openwrt only)
donttouch : disable system flow offloading setting if selected mode is incompatible with it, dont touch it otherwise and dont configure selective flow offloading
none : always disable system flow offloading setting and dont configure selective flow offloading
software : always disable system flow offloading setting and configure selective software flow offloading
hardware : always disable system flow offloading setting and configure selective hardware flow offloading
FLOWOFFLOAD=donttouch
The GETLIST parameter tells the install_easy.sh installer which script to call
to update the list of blocked ip or hosts.
Its called via get_config.sh from scheduled tasks (crontab or systemd timer).
Put here the name of the script that you will use to update the lists.
If not, then the parameter should be commented out.
You can individually disable ipv4 or ipv6. If the parameter is commented out or not equal to "1",
use of the protocol is permitted.
#DISABLE_IPV4=1
DISABLE_IPV6=1
The number of threads for mdig multithreaded DNS resolver (1..100).
The more of them, the faster, but will your DNS server be offended by hammering ?
MDIG_THREADS=30
temp directory. Used by ipset/*.sh scripts for large lists processing.
/tmp by default. Can be reassigned if /tmp is tmpfs and RAM is low.
TMPDIR=/opt/zapret/tmp
ipset options :
IPSET_OPT="hashsize 262144 maxelem 2097152"
Kernel automatically increases hashsize if ipset is too large for the current hashsize.
This procedure requires internal reallocation and may require additional memory.
On low RAM systems it can cause errors.
Do not use too high hashsize. This way you waste your RAM. And dont use too low hashsize to avoid reallocs.
ip2net options. separate for ipv4 and ipv6.
IP2NET_OPT4="--prefix-length=22-30 --v4-threshold=3/4"
IP2NET_OPT6="--prefix-length=56-64 --v6-threshold=5"
Enable gzip compression for large lists. Used by ipset/*.sh scripts.
GZIP_LISTS=1
Command to reload ip/host lists after update.
Comment or leave empty for auto backend selection : ipset or ipfw if present.
On BSD systems with PF no auto reloading happens. You must provide your own command.
Newer FreeBSD versions support table only reloading : pfctl -Tl -f /etc/pf.conf
Set to "-" to disable reload.
LISTS_RELOAD="pfctl -f /etc/pf.conf"
The following settings are not relevant for openwrt :
If your system works as a router, then you need to enter the names of the internal and external interfaces:
IFACE_LAN = eth0
IFACE_WAN = eth1
IMPORTANT: configuring routing, masquerade, etc. not a zapret task.
Only modes that intercept transit traffic are enabled.
The INIT_APPLY_FW=1 parameter enables the init script to independently apply iptables rules.
With other values or if the parameter is commented out, the rules will not be applied.
This is useful if you have a firewall management system, in the settings of which you should tie the rules.
Screwing to the firewall control system or your launch system
-------------------------------------------------------------
If you use some kind of firewall management system, then it may conflict with an existing startup script.
When re-applying the rules, it could break the iptables settings from the zapret.
In this case, the rules for iptables should be screwed to your firewall separately from running tpws or nfqws.
The following calls allow you to apply or remove iptables rules separately:
/opt/zapret/init.d/sysv/zapret start-fw
/opt/zapret/init.d/sysv/zapret stop-fw
And you can start or stop the demons separately from the firewall:
/opt/zapret/init.d/sysv/zapret start-daemons
/opt/zapret/init.d/sysv/zapret stop-daemons
Simple install to desktop linux system
--------------------------------------
Simple install works on most modern linux distributions with systemd, OpenWRT and MacOS.
Run install_easy.sh and answer its questions.
Simple install to openwrt
-------------------------
install_easy.sh works on openwrt but there're additional challenges.
They are mainly about possibly low flash free space.
Simple install will not work if it has no space to install itself and required packages from the repo.
Another challenge would be to bring zapret to the router. You can download zip from github and use it.
Do not repack zip contents in Windows, because this way you break chmod and links.
Install openssh-sftp-server and unzip to openwrt and use sftp to transfer the file.
The best way to start is to put zapret dir to /tmp and run /tmp/zapret/install_easy.sh from there.
After installation remove /tmp/zapret to free RAM.
The absolute minimum for openwrt is 64/8 system, 64/16 is comfortable, 128/extroot is recommended.
Android
-------
Its not possible to use nfqws and tpws in transparent proxy mode without root privileges.
Without root tpws can run in --socks mode.
I have no NFQUEUE presence statistics in stock android kernels, but its present on my MTK device.
If NFQUEUE is present nfqws works.
There's no ipset support unless you run custom kernel. In common case task of bringing up ipset
on android is ranging from "not easy" to "almost impossible", unless you find working kernel
image for your device.
Android does not use /etc/passwd, tpws --user won't work. There's replacement.
Use numeric uids in --uid option.
Its recommended to use gid 3003 (AID_INET), otherwise tpws will not have inet access.
Example : --uid 1:3003
In iptables use : "! --uid-owner 1" instead of "! --uid-owner tpws".
Write your own shell script with iptables and tpws, run it using your root manager.
Autorun scripts are here :
magisk : /data/adb/service.d
supersu : /system/su.d
I haven't checked whether android can kill iptable rules at its own will during wifi connection/disconnection,
mobile data on/off, ...
How to run tpws on root-less android.
You can't write to /system, /data, can't run from sd card.
Selinux prevents running executables in /data/local/tmp from apps.
Use adb and adb shell.
mkdir /data/local/tmp/zapret
adb push tpws /data/local/tmp/zapret
chmod 755 /data/local/tmp/zapret /data/local/tmp/zapret/tpws
chcon u:object_r:system_file:s0 /data/local/tmp/zapret/tpws
Now its possible to run /data/local/tmp/zapret/tpws from any app such as tasker.
FreeBSD, OpenBSD, MacOS
-----------------------
see docs/bsd.eng.txt
Windows (WSL)
-------------
Using WSL (Windows subsystem for Linux) it's possible to run tpws in socks mode under rather new builds of
windows 10 and windows server.
Its not required to install any linux distributions as suggested in most articles.
tpws is static binary. It doesn't need a distribution.
Install WSL : dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all
Copy binaries/x86_64/tpws_wsl.tgz to the target system.
Run : wsl --import tpws "%USERPROFILE%\tpws" tpws_wsl.tgz
Run tpws : wsl --exec /tpws --uid=1 --no-resolve --socks --bind-addr=127.0.0.1 --port=1080 <fooling_options>
Configure socks as 127.0.0.1:1080 in a browser or another program.
Cleanup : wsl --unregister tpws
Tested in windows 10 build 19041 (20.04).
NOTICE. There is native windows solution GoodByeDPI. It works on packet level like nfqws.
Other devices
-------------
Author's goal does not include easy supporting as much devices as possibles.
Please do not ask for easy supporting firmwares. It requires a lot of work and owning lots of devices. Its counterproductive.
As a devices owner its easier for you and should not be too hard if firmware is open.
Most closed stock firmwares are not designed for custom usage and sometimes actively prevent it.
In the latter case you have to hack into it and reverse engineer. Its not easy.
Binaries are universal. They can run on almost all firmwares.
You will need :
* root shell access. true sh shell, not microtik-like console
* startup hook
* r/w partition to store binaries and startup script with executable permission (+x)
* tpws can be run almost anywhere but nfqws require kernel support for NFQUEUE. Its missing in most firmwares.
* too old 2.6 kernels are unsupported and can cause errors
If binaries crash with segfault (rare but happens on some kernels) try to unpack upx like this : upx -d tpws.
First manually debug your scenario. Run iptables + daemon and check if its what you want.
Write your own script with iptables magic and run required daemon from there. Put it to startup.
Dont ask me how to do it. Its different for all firmwares and requires studying.
Find manual or reverse engineer yourself.
Check for race conditions. Firmware can clear or modify iptables after your startup script.
If this is the case then run another script in background and add some delay there.
Https blocking bypass
----------------------
SOMETIMES (but not often) a tls handshake split trick works.
Try MODE=..._https
May be you're lucky.
MORE OFTEN DPI desync attack work, but it may require some manual tuning.
OTHERWISE you have to redirect traffic through a third-party host.
It is proposed to use transparent redirect through socks5 using iptables + redsocks, or iptables + iproute + vpn.
Redsocks variant is described in https.txt.
iproute + wireguard - in wireguard_iproute_openwrt.txt.
(they are russian)

1314
docs/readme.txt Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,133 @@
Index: WireGuard-0.0.20190123/src/cookie.c
===================================================================
--- WireGuard-0.0.20190123.orig/src/cookie.c
+++ WireGuard-0.0.20190123/src/cookie.c
@@ -193,6 +193,8 @@ void wg_cookie_message_create(struct mes
xchacha20poly1305_encrypt(dst->encrypted_cookie, cookie, COOKIE_LEN,
macs->mac1, COOKIE_LEN, dst->nonce,
checker->cookie_encryption_key);
+ // MOD : randomize trash
+ dst->header.trash = gen_trash();
}
void wg_cookie_message_consume(struct message_handshake_cookie *src,
Index: WireGuard-0.0.20190123/src/messages.h
===================================================================
--- WireGuard-0.0.20190123.orig/src/messages.h
+++ WireGuard-0.0.20190123/src/messages.h
@@ -53,23 +53,41 @@ enum limits {
MAX_QUEUED_PACKETS = 1024 /* TODO: replace this with DQL */
};
+/*
enum message_type {
- MESSAGE_INVALID = 0,
- MESSAGE_HANDSHAKE_INITIATION = 1,
- MESSAGE_HANDSHAKE_RESPONSE = 2,
- MESSAGE_HANDSHAKE_COOKIE = 3,
- MESSAGE_DATA = 4
+ MESSAGE_INVALID = 0,
+ MESSAGE_HANDSHAKE_INITIATION = 1,
+ MESSAGE_HANDSHAKE_RESPONSE = 2,
+ MESSAGE_HANDSHAKE_COOKIE = 3,
+ MESSAGE_DATA = 4
};
+*/
+
+// MOD : message type
+enum message_type {
+ MESSAGE_INVALID = 0xE319CCD0,
+ MESSAGE_HANDSHAKE_INITIATION = 0x48ADE198,
+ MESSAGE_HANDSHAKE_RESPONSE = 0xFCA6A8F3,
+ MESSAGE_HANDSHAKE_COOKIE = 0x64A3BB18,
+ MESSAGE_DATA = 0x391820AA
+};
+
+// MOD : generate fast trash without true RNG
+__le32 gen_trash(void);
struct message_header {
- /* The actual layout of this that we want is:
- * u8 type
- * u8 reserved_zero[3]
- *
- * But it turns out that by encoding this as little endian,
- * we achieve the same thing, and it makes checking faster.
- */
- __le32 type;
+ /* The actual layout of this that we want is:
+ * u8 type
+ * u8 reserved_zero[3]
+ *
+ * But it turns out that by encoding this as little endian,
+ * we achieve the same thing, and it makes checking faster.
+ */
+
+ // MOD : trash field to change message size and add 4 byte offset to all fields
+ __le32 trash;
+
+ __le32 type;
};
struct message_macs {
Index: WireGuard-0.0.20190123/src/noise.c
===================================================================
--- WireGuard-0.0.20190123.orig/src/noise.c
+++ WireGuard-0.0.20190123/src/noise.c
@@ -17,6 +17,24 @@
#include <linux/highmem.h>
#include <crypto/algapi.h>
+
+// MOD : trash generator
+__le32 gtrash = 0;
+__le32 gen_trash(void)
+{
+ if (gtrash)
+ gtrash = gtrash*1103515243 + 12345;
+ else
+ // first value is true random
+ get_random_bytes_wait(&gtrash, sizeof(gtrash));
+ return gtrash;
+}
+
/* This implements Noise_IKpsk2:
*
* <- s
@@ -515,6 +533,10 @@ wg_noise_handshake_create_initiation(str
&handshake->entry);
handshake->state = HANDSHAKE_CREATED_INITIATION;
+
+ // MOD : randomize trash
+ dst->header.trash = gen_trash();
+
ret = true;
out:
@@ -655,6 +677,10 @@ bool wg_noise_handshake_create_response(
&handshake->entry);
handshake->state = HANDSHAKE_CREATED_RESPONSE;
+
+ // MOD : randomize trash
+ dst->header.trash = gen_trash();
+
ret = true;
out:
Index: WireGuard-0.0.20190123/src/send.c
===================================================================
--- WireGuard-0.0.20190123.orig/src/send.c
+++ WireGuard-0.0.20190123/src/send.c
@@ -200,6 +200,10 @@ static bool encrypt_packet(struct sk_buf
header->header.type = cpu_to_le32(MESSAGE_DATA);
header->key_idx = keypair->remote_index;
header->counter = cpu_to_le64(PACKET_CB(skb)->nonce);
+
+ // MOD : randomize trash
+ header->header.trash = gen_trash();
+
pskb_put(skb, trailer, trailer_len);
/* Now we can encrypt the scattergather segments */

View File

@ -0,0 +1,244 @@
Посвящено возможной блокировке в РФ VPN протоколов через DPI.
Предпосылками являются последние законодательные акты и во всю сочащиеся "секретные" записки.
В РФ разрабатываются и готовятся к применению более продвинутые решения по блокировке трафика.
Вполне вероятно будут резать стандартные VPN протоколы. Нам надо быть к этому готовыми.
Один из возможных и перспективных путей решения данного вопроса - кустомная модификация
исходников VPN с целью незначительного изменения протокола, ломающего стандартные модули обнаружения в DPI.
Это относительно сложно, доступно только для гиков.
Никто не будет разрабатывать специальные модули обнаружения в DPI, если только кто-то не сделает простое и
удобное решение для всех, и его станут широко применять. Но это маловероятно, и даже если и так,
то всегда можно модифицировать протокол чуток по другому. Делать моды для DPI несравненно дольше
и дороже, чем клепать на коленке изменения протокола для wireguard.
ЗАМЕЧЕНИЕ : альтернативой модификации конечного софта для VPN является использование "навесных"
обфускаторов. см : https://github.com/bol-van/ipobfs
Рассмотрю что нам надо пропатчить в wireguard. Модифицированный wireguard проверен на виртуалках
с десктопным linux, он работает, сообщения в wireshark действительно не вписываются в стандартный
протокол и не опознаются.
Wireguard протокол очень простой. Все сообщения описаны в messages.h
Поставим себе целью сделать 2 простые модификации :
1) Добавим в начало всех сообщений немного мусора, чтобы изменить размер сообщений и смещения полей
2) Изменим коды типов сообщений
Этого может быть вполне достаточно для обмана DPI
--messages.h--------------------------
/*
enum message_type {
MESSAGE_INVALID = 0,
MESSAGE_HANDSHAKE_INITIATION = 1,
MESSAGE_HANDSHAKE_RESPONSE = 2,
MESSAGE_HANDSHAKE_COOKIE = 3,
MESSAGE_DATA = 4
};
*/
// MOD : message type
enum message_type {
MESSAGE_INVALID = 0xE319CCD0,
MESSAGE_HANDSHAKE_INITIATION = 0x48ADE198,
MESSAGE_HANDSHAKE_RESPONSE = 0xFCA6A8F3,
MESSAGE_HANDSHAKE_COOKIE = 0x64A3BB18,
MESSAGE_DATA = 0x391820AA
};
// MOD : generate fast trash without true RNG
__le32 gen_trash(void);
struct message_header {
/* The actual layout of this that we want is:
* u8 type
* u8 reserved_zero[3]
*
* But it turns out that by encoding this as little endian,
* we achieve the same thing, and it makes checking faster.
*/
// MOD : trash field to change message size and add 4 byte offset to all fields
__le32 trash;
__le32 type;
};
--------------------------------------
Напишем функцию для генерации trash. Функция должна быть быстрая, важно не замедлить скорость.
Мы не расчитываем, что нас будут специально ловить, иначе бы пришлось делать полноценный обфускатор.
Задача лишь сломать стандартный модуль обнаружения протокола wireguard. Потому истинная рандомность
trash не важна.
Но все же немного "трэша" не повредит. Гонки между тредами так же пофигистичны. Это же трэш.
--noise.c-----------------------------
// MOD : trash generator
__le32 gtrash = 0;
__le32 gen_trash(void)
{
if (gtrash)
gtrash = gtrash*1103515243 + 12345;
else
// first value is true random
get_random_bytes_wait(&gtrash, sizeof(gtrash));
return gtrash;
}
--------------------------------------
Теперь осталось найти все места, где создаются сообщения и внести туда заполнение поля trash.
Сообщений всего 4. Их можно найти по присваиванию полю type одного из значений enum message_type.
2 места в noise.c в функциях wg_noise_handshake_create_initiation и wg_noise_handshake_create_response,
1 место в cookie.c в функции wg_cookie_message_create
Дописываем в конец инициализации структуры сообщения :
--------------------------------------
// MOD : randomize trash
dst->header.trash = gen_trash();
--------------------------------------
и 1 место в send.c в функции encrypt_packet
--------------------------------------
// MOD : randomize trash
header->header.trash = gen_trash();
--------------------------------------
Вот и весь патчинг. Полный patch (версия wireguard 0.0.20190123) лежит в 010-wg-mod.patch.
Патчинг кода - самое простое. Для десктопного linux дальше все просто.
Пересобираем через make, устанавливаем через make install, перегружаем
модуль wireguard, перезапускаем интерфейсы, и все готово.
Настоящий геморой начнется когда вы это попытаетесь засунуть на роутер под openwrt.
Одна из больших проблем linux - отсутствие совместимости драйверов на уровне бинариков.
Поэтому собирать необходимо в точности под вашу версию ядра и в точности под его .config.
Вам придется либо полностью самостоятельно собирать всю прошивку, либо найти SDK в точности
от вашей версии прошивки для вашей архитектуры и собрать модуль с помощью этого SDK.
Последний вариант более легкий.
Для сборки вам понадобится система на linux x86_64. Ее можно установить в виртуалке.
Теоретически можно пользоваться WSL из win10, но на практике там очень медленное I/O,
по крайней мере на старых версиях win10. Безумно медленное. Будете собирать вечность.
Может в новых win10 что-то и улучшили, но я бы сразу расчитывал на полноценный linux.
Находим здесь вашу версию : https://downloads.openwrt.org/
Скачиваем файл openwrt-sdk-*.tar.xz или lede-sdk-*.tar.xz
Например : https://downloads.openwrt.org/releases/18.06.2/targets/ar71xx/generic/openwrt-sdk-18.06.2-ar71xx-generic_gcc-7.3.0_musl.Linux-x86_64.tar.xz
Если ваша версия непонятна или стара, то проще будет найти последнюю прошивку и перешить роутер.
Распаковываем SDK. Следующими командами можно собрать оригинальный вариант wireguard :
# scripts/feeds update -a
# scripts/feeds install -a
# make defconfig
# make -j 4 package/wireguard/compile
Сборка будет довольно долгой. Ведь придется подтащить ядро, собрать его, собрать зависимости.
"-j 4" означает использовать 4 потока. Впишите вместо 4 количество доступных cpu cores.
Получим следующие файлы :
openwrt-sdk-18.06.2-ar71xx-generic_gcc-7.3.0_musl.Linux-x86_64/bin/targets/ar71xx/generic/packages/kmod-wireguard_4.9.152+0.0.20190123-1_mips_24kc.ipk
openwrt-sdk-18.06.2-ar71xx-generic_gcc-7.3.0_musl.Linux-x86_64/bin/packages/mips_24kc/base/wireguard-tools_0.0.20190123-1_mips_24kc.ipk
Но это будет оригинальный wireguard. Нам нужен патченый.
Установим quilt и mc для нормального редактора вместо vim :
# sudo apt-get update
# sudo apt-get install quilt mc
# make package/wireguard/clean
# make package/wireguard/prepare V=s QUILT=1
Сорцы приготовлены для сборки в :
openwrt-sdk-18.06.2-ar71xx-generic_gcc-7.3.0_musl.Linux-x86_64/build_dir/target-mips_24kc_musl/linux-ar71xx_generic/WireGuard-0.0.20190123/src
# cd build_dir/target-mips_24kc_musl/linux-ar71xx_generic/WireGuard-0.0.20190123/src
# quilt push -a
# quilt new 010-wg-mod.patch
# export EDITOR=mcedit
Далее будет открываться редактор mcedit, в который нужно вносить изменения в каждый файл :
# quilt edit messages.h
# quilt edit cookie.c
# quilt edit noise.c
# quilt edit send.c
# quilt diff
# quilt refresh
Получили файл патча в :
openwrt-sdk-18.06.2-ar71xx-generic_gcc-7.3.0_musl.Linux-x86_64/build_dir/target-mips_24kc_musl/linux-ar71xx_generic/WireGuard-0.0.20190123/patches/010-wg-mod.patch
Выходим в корень SDK.
# make package/wireguard/compile V=99
Если не было ошибок, то получили измененные ipk.
Патч можно зафиксировать в описании пакета :
# make package/wireguard/update
Получим :
openwrt-sdk-18.06.2-ar71xx-generic_gcc-7.3.0_musl.Linux-x86_64/feeds/base/package/network/services/wireguard/patches/010-wg-mod.patch
При последующей очистке и пересборке он будет автоматом применяться.
АЛЬТЕРНАТИВА : можно не возиться с quilt.
сделайте
# make package/wireguard/clean
# make package/wireguard/prepare
и напрямую модифицируйте или копируйте файлы в
openwrt-sdk-18.06.2-ar71xx-generic_gcc-7.3.0_musl.Linux-x86_64/build_dir/target-mips_24kc_musl/linux-ar71xx_generic/WireGuard-0.0.20190123/src
затем
# make package/wireguard/compile
Если нужно поменять версию wireguard, то идите в
openwrt-sdk-18.06.2-ar71xx-generic_gcc-7.3.0_musl.Linux-x86_64/feeds/base/package/network/services/wireguard/Makefile
поменяйте там версию в PKG_VERSION на последнюю из : https://git.zx2c4.com/WireGuard
скачайте tar.xz с этой версией , вычислите его sha256sum, впишите в PKG_HASH
1 раз где-нибудь пропатчите файлы последней версии wireguard в текстовом редакторе, скопируйте в build_dir,
сделайте версию для openwrt. эти же файлы скопируйте на ваш сервер с десктопным linux, сделайте там make / make install
Но имейте в виду, что build_dir - локация для временных файлов.
make clean оттуда все снесет, включая ваши модификации. Модифицированные файлы лучше сохранить отдельно,
чтобы потом было легко скопировать обратно.
Полученные ipk копируем на роутер в /tmp, устанавливаем через
# cd /tmp
# rm -r /tmp/opkg-lists
# opkg install *.ipk
Если требует зависимостей, то
# opkg update
# opkg install .... <зависимости>
# rm -r /tmp/opkg-lists
# opkg install *.ipk
В /tmp/opkg-lists opkg хранит кэш списка пакетов. Если попытаться установить файл ipk, и такой же пакет
найдется в репозитории, opkg будет устанавливать из репозитория. А нам это не надо.
# rmmod wireguard
# kmodloader
# dmesg | tail
должны увидеть что-то вроде :
[8985.415490] wireguard: WireGuard 0.0.20190123 loaded. See www.wireguard.com for information.
[8985.424178] wireguard: Copyright (C) 2015-2019 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
значит модуль загрузился
Могут понадобиться ключи opkg --force-reinstall, --force-depends.
--force-depends поможет при несоответствии hash версии ядра. То есть версия x.x.x та же самая, но hash конфигурации разный.
При несоответствии x.x.x вы что-то делаете не так, работать это не будет.
Например : 4.14.56-1-b1186491495127cc6ff81d29c00a91fc, 4.14.56-1-3f8a21a63974cfb7ee67e41f2d4b805d
Это свидетельствует о несоответствии .config ядра при сборке прошивки и в SDK.
Если несоответствие легкое, то может все прокатить, но при более серьезной разнице в .config модуль может не загрузиться
или вызвать стабильные или хаотические падения ядра и перезагрузки (включая вариант беонечной перезагрузки - bootloop).
Так что перед --force-depends убедитесь, что знаете как лечится такая ситуация, и не стоит это делать при отсутствии физического
доступа к девайсу.
Когда поднимите линк, и вдруг ничего не будет работать, то посмотрите в wireshark udp пакеты
на порт endpoint. Они не должны начинаться с 0,1,2,3,4. В первых 4 байтах должен быть рандом,
в следующих 4 байтах - значения из измененного enum message_type. Если пакет все еще начинается с 0..4,
значит модуль wireguard оригинальный, что-то не собралось, не скопировалось, не перезапустилось.
В противном случае должен подняться линк, пинги ходить. Значит вы победили, поздравляю.
Регулятору будет намного сложнее поймать ваш VPN.

View File

@ -0,0 +1,519 @@
Есть возможность поднять свой VPN сервер ? Не хотим использовать redsocks ?
Хотим завертывать на VPN только часть трафика ?
Например, из ipset zapret только порт tcp:443, из ipban - весь трафик, не только tcp ?
Да, с VPN такое возможно.
Опишу понятийно как настраивается policy based routing в openwrt на примере wireguard.
Вместо wireguard можно использовать openvpn или любой другой. Но wireguard прекрасен сразу несколькими вещами.
Главная из которых - в разы большая скорость, даже немного превышающая ipsec.
Ведь openvpn основан на tun, а tun - всегда в разы медленнее решения в kernel mode,
и если для PC оно может быть не так актуально, для soho роутеров - более чем.
Wireguard может дать 50 mbps там, где openvpn еле тащит 10.
Но есть и дополнительное требование. Wireguard работает в ядре, значит ядро должно
быть под вашим контролем. vps на базе openvz не подойдет ! Нужен xen, kvm,
любой другой вариант, где загружается ваше собственное ядро, а не используется
общее, разделяемое на множество vps. В openvz вам никто не даст лезть в ядро.
Если вдруг окажется, что основные VPN протоколы блокируется DPI, включая wireguard,
то стоит смотреть в сторону либо обфускации трафика до состояния нераспознаваемого
мусора, либо маскировки под TLS (лучше на порт 443). Скорость, конечно, вы потеряете, но это
та самая ситуация, которая описывается словами "медленно или никак".
Маскированные под TLS протоколы DPI может распознать двумя действиями :
пассивно через анализ статистических характеристик пакетов (время, размер, периодичность, ..)
или активно через подключение к вашему серверу от себя и попытку поговорить с сервером по
известным протоколам (называется active probing). Если вы подключаетесь к серверу
с фиксированных IP, то активный пробинг можно надежно заблокировать через ограничение
диапазонов IP адресов, с которых можно подключаться к серверу. В ином случае можно использовать
технику "port knocking".
Перспективным направлением так же считаю легкую собственную модификацию исходников
существующих VPN с целью незначительного изменения протокола, которая ломает стандартные
модули обнаружения в DPI. В wireguard можно добавить в начало пакета handshake лишнее поле,
заполненное случайным мусором. Разумеется, в таком случае требуется держать измененную версию
как на сервере, так и на клиенте. Если затея срабатывает, то вы получаете максимальную
скорость, при этом полностью нагибая регулятора.
Полезная инфа по теме : https://habr.com/ru/post/415977/
Понятийно необходимо выполнить следующие шаги :
1) Поднять vpn сервер.
2) Настроить vpn клиент. Результат этого шага - получение поднятого интерфейса vpn.
Будь то wireguard, openvpn или любой другой тип vpn.
3) Создать такую схему маршрутизации, при которой пакеты, помечаемые особым mark,
попадают на vpn, а остальные идут обычным способом.
4) Создать правила, выставляющие mark для всего трафика, который необходимо рулить на vpn.
Критерии могут быть любые, ограниченные лишь возможностями iptables и вашим воображением.
Будем считать наш vpn сервер находится на ip 91.15.68.202.
Вешать его будем на udp порт 12345. На этот же порт будем вешать и клиентов.
Сервер работает под debian 9. Клиент работает под openwrt.
Для vpn отведем подсеть 192.168.254.0/24.
--- Поднятие сервера ---
На сервере должны быть установлены заголовки ядра (linux-headers-...) и компилятор gcc.
Качаем последний tar.xz с wireguard отсюда : https://git.zx2c4.com/WireGuard/
# tar xf WireGuard*.tar.xz
# cd WireGuard-*/src
# make
# strip --strip-debug wireguard.ko
# sudo make install
wireguard основан на понятии криптороутинга. Каждый пир (сервер - тоже пир)
имеет пару открытый/закрытый ключ. Закрытый ключ остается у пира,
открытый прописывается у его партнера. Каждый пир авторизует другого
по знанию приватного ключа, соответствующего прописанному у него публичному ключу.
Протокол построен таким образом, что на все неправильные udp пакеты не следует ответа.
Не знаешь приватный ключ ? Не смог послать правильный запрос ? Долбись сколько влезет,
я тебе ничего не отвечу. Это защищает от активного пробинга со стороны DPI и просто
экономит ресурсы.
Значит первым делом нужно создать 2 пары ключей : для сервера и для клиента.
wg genkey генерит приватный ключ, wg pubkey получает из него публичный ключ.
# wg genkey
oAUkmhoREtFQ5D5yZmeHEgYaSWCcLYlKe2jBP7EAGV0=
# echo oAUkmhoREtFQ5D5yZmeHEgYaSWCcLYlKe2jBP7EAGV0= | wg pubkey
bCdDaPYSTBZVO1HTmKD+Tztuf3PbOWGDWfz7Lb1E6C4=
# wg genkey
OKXX0TSlyjJmGt3/yHlHxi0AqjJ0vh+Msne3qEHk0VM=
# echo OKXX0TSlyjJmGt3/yHlHxi0AqjJ0vh+Msne3qEHk0VM= | wg pubkey
EELdA2XzjcKxtriOCPBXMOgxlkgpbRdIyjtc3aIpkxg=
Пишем конфиг
--/etc/wireguard/wgvps.conf-------------------
[Interface]
PrivateKey = OKXX0TSlyjJmGt3/yHlHxi0AqjJ0vh+Msne3qEHk0VM=
ListenPort = 12345
[Peer]
#Endpoint =
PublicKey = bCdDaPYSTBZVO1HTmKD+Tztuf3PbOWGDWfz7Lb1E6C4=
AllowedIPs = 192.168.254.3
PersistentKeepalive=20
----------------------------------------------
Wireguard - минималистичный vpn. В нем нет никаких средств для автоконфигурации ip.
Все придется прописывать руками.
В wgvps.conf должны быть перечислены все пиры с их публичными ключами,
а так же прописаны допустимые для них ip адреса.
Назначим нашему клиенту 192.168.254.3. Сервер будет иметь ip 192.168.254.1.
Endpoint должен быть прописан хотя бы на одном пире.
Если endpoint настроен для пира, то wireguard будет периодически пытаться к нему подключиться.
В схеме клиент/сервер у сервера можно не прописывать endpoint-ы пиров, что позволит
менять ip и быть за nat. Endpoint пира настраивается динамически после успешной фазы
проверки ключа.
Включаем маршрутизцию :
# echo net.ipv4.ip_forward = 1 >>/etc/sysctl.conf
# sysctl -p
Интерфейс конфигурится стандартно для дебианоподобных систем :
--/etc/network/interfaces.d/wgvps-------------
auto wgvps
iface wgvps inet static
address 192.168.254.1
netmask 255.255.255.0
pre-up ip link add $IFACE type wireguard
pre-up wg setconf $IFACE /etc/wireguard/$IFACE.conf
post-up iptables -t nat -A POSTROUTING -o eth0 -s 192.168.254.0/24 -j MASQUERADE
post-up iptables -A FORWARD -o eth0 -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu
post-down iptables -D FORWARD -o eth0 -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu
post-down iptables -t nat -D POSTROUTING -o eth0 -s 192.168.254.0/24 -j MASQUERADE
post-down ip link del $IFACE
----------------------------------------------
Поднятие через ifup wgvps, опускание через ifdown wgvps.
При поднятии интерфейса заодно настраивается nat. eth0 здесь означает интерфейс vpn сервера с инетовским ip адресом.
Если у вас какая-то система управления фаерволом, то надо настройку nat прикручивать туда.
Пример написан для простейшего случая, когда никаких ограничений нет, таблицы iptables пустые.
Чтобы посмотреть текущие настройки wireguard, запустите 'wg' без параметров.
--- Поднятие клиента ---
# opkg update
# opkg install wireguard
Добавляем записи в конфиги.
--/etc/config/network--------------------------
config interface 'wgvps'
option proto 'wireguard'
option auto '1'
option private_key 'oAUkmhoREtFQ5D5yZmeHEgYaSWCcLYlKe2jBP7EAGV0='
option listen_port '12345'
option metric '9'
option mtu '1420'
config wireguard_wgvps
option public_key 'EELdA2XzjcKxtriOCPBXMOgxlkgpbRdIyjtc3aIpkxg=
list allowed_ips '0.0.0.0/0'
option endpoint_host '91.15.68.202'
option endpoint_port '12345'
option route_allowed_ips '0'
option persistent_keepalive '20'
config interface 'wgvps_ip'
option proto 'static'
option ifname '@wgvps'
list ipaddr '192.168.254.3/24'
config route
option interface 'wgvps'
option target '0.0.0.0/0'
option table '100'
config rule
option mark '0x800/0x800'
option priority '100'
option lookup '100'
------------------------------------------------
--/etc/config/firewall--------------------------
config zone
option name 'tunvps'
option output 'ACCEPT'
option input 'REJECT'
option masq '1'
option mtu_fix '1'
option forward 'REJECT'
option network 'wgvps wgvps_ip'
config forwarding
option dest 'tunvps'
option src 'lan'
config rule
option name 'Allow-ICMP-tunvps'
option src 'tunvps'
option proto 'icmp'
option target 'ACCEPT'
config rule
option target 'ACCEPT'
option src 'wan'
option proto 'udp'
option family 'ipv4'
option src_port '12345'
option src_ip '91.15.68.202'
option name 'WG-VPS'
------------------------------------------------
Что тут было сделано :
*) Настроен интерфейс wireguard. Указан собственный приватный ключ.
*) Настроен пир-партнер с указанием его публичнго ключа и endpoint (ip:port нашего сервера)
такая настройка заставит периодически долбиться на сервер по указанному ip
route_allowed_ip '0' запрещает автоматическое создание маршрута
allowed_ips '0.0.0.0/0' разрешает пакеты с любым адресом источника.
ведь мы собираемся подключаться к любым ip в инете
persistent_keepalive '20' помогает исключить дропание mapping на nat-е, если мы сидим за ним,
да и вообще полезная вещь, чтобы не было подвисших пиров
*) Статическая конфигурация ip интерфейса wgvps.
*) Маршрут default route на wgvps в отдельной таблице маршрутизации с номером 100. Аналог команды ip route add .. table 100
*) Правило использовать таблицу 100 при выставлении в mark бита 0x800. Аналог команды ip rule.
*) Отдельная зона фаервола для VPN - 'tunvps'. В принципе ее можно не создавать, можете приписать интерфейс к зоне wan.
Но в случае с отдельной зоной можно настроить особые правила на подключения с vpn сервера в сторону клиента.
*) Разрешение форвардинга между локалкой за роутером и wgvps.
*) Разрешение принимать icmp от vpn сервера, включая пинги. ICMP жизненно важны для правильного функционирования ip сети !
*) И обязательно проткнуть дырку в фаерволе, чтобы принимать пакеты wireguard со стороны инетовского ip vpn сервера.
# fw3 restart
# ifup wgvps
# ifconfig wgvps
# ping 192.168.254.1
Если все хорошо, должны ходить пинги.
С сервера не помешает :
# ping 192.168.254.3
--- Маркировка трафика ---
Завернем на vpn все из ipset zapret на tcp:443 и все из ipban.
OUTPUT относится к исходящим с роутера пакетам, PREROUTING - ко всем остальным.
Если с самого роутера ничего заруливать не надо, можно опустить все до команд с PREROUTING.
--/etc/firewall.user----------------------------
. /opt/zapret/init.d/openwrt/functions
create_ipset no-update
network_find_wan_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 -j MARK --set-mark 0x800/0x800
ipt OUTPUT -t mangle -o $DEVICE -m set --match-set ipban dst -j MARK --set-mark 0x800/0x800
done
network_get_device DEVICE lan
ipt PREROUTING -t mangle -i $DEVICE -p tcp --dport 443 -m set --match-set zapret dst -j MARK --set-mark 0x800/0x800
ipt PREROUTING -t mangle -i $DEVICE -m set --match-set ipban dst -j MARK --set-mark 0x800/0x800
------------------------------------------------
# fw3 restart
--- По поводу двойного NAT ---
В описанной конфигурации nat выполняется дважды : на роутере-клиенте происходит замена адреса источника из LAN
на 192.168.254.3 и на сервере замена 192.168.254.3 на внешний адрес сервера в инете.
Зачем так делать ? Исключительно для простоты настройки. Но если вы готовы чуток еще поднапрячься и не хотите двойного nat,
то можете вписать в /etc/config/firewall "masq '0'", на сервер дописать маршрут до вашей подсети lan.
Чтобы не делать это для каждого клиента, можно отвести под всех клиентов диапазон 192.168.0.0-192.168.127.255
и прописать его одним маршрутом.
--/etc/network/interfaces.d/wgvps-------------
post-up ip route add dev $IFACE 192.168.0.0/17
post-down ip route del dev $IFACE 192.168.0.0/17
----------------------------------------------
Так же необходимо указать wireguard дополнительные разрешенные ip для peer :
--/etc/wireguard/wgvps.conf-------------------
[Peer]
PublicKey = bCdDaPYSTBZVO1HTmKD+Tztuf3PbOWGDWfz7Lb1E6C4=
AllowedIPs = 192.168.254.3, 192.168.2.0/24
----------------------------------------------
Всем клиентам придется назначать различные диапазоны адресов в lan и индивидуально прописывать AllowedIPs
для каждого peer.
# ifdown wgvps ; ifup wgvps
На клиенте разрешим форвард icmp, чтобы работал пинг и корректно определялось mtu.
--/etc/config/firewall--------------------------
config rule
option name 'Allow-ICMP-tunvps'
option src 'tunvps'
option dest 'lan'
option proto 'icmp'
option target 'ACCEPT'
------------------------------------------------
Существуют еще два неочевидных нюанса.
Первый из них касается пакетов с самого роутера (цепочка OUTPUT).
Адрес источника выбирается по особому алгоритму, если программа явно его не задала, еще до этапа iptables.
Он берется с интерфейса, куда бы пошел пакет при нормальном раскладе.
Обратная маршрутизация с VPN станет невозможной, да и wireguard такие пакеты порежет, поскольку они не вписываются в AllowedIPs.
Никаким мистическим образом автоматом source address не поменяется.
В прошлом варианте настройки проблема решалось через маскарад. Сейчас же маскарада нет.
Потому все же придется его делать в случае, когда пакет изначально направился бы через wan,
а мы его завертываем на VPN. Помечаем такие пакеты марком 0x1000.
Если вам не актуальны исходящие с самого роутера, то можно ничего не менять.
Другой нюанс связан с обработкой проброшенных на vps портов, соединения по которым приходят как входящие с интерфейса wgvps.
Представьте себе, что вы пробросили порт 2222. Кто-то подключается с адреса 1.2.3.4. Вам приходит пакет SYN 1.2.3.4:51723=>192.168.2.2:2222.
По правилам маршрутизации он пойдет в локалку. 192.168.2.2 его обработает, ответит пакетом ACK 192.168.2.2:2222=>1.2.3.4:51723.
Этот пакет придет на роутер. И куда он дальше пойдет ? Если он не занесен в ipban, то согласно правилам машрутизации
он пойдет по WAN интерфейсу, а не по исходному wgvps.
Чтобы решить эту проблему, необходимо воспользоваться CONNMARK. Существуют 2 отдельных марка : fwmark и connmark.
connmark относится к соединению, fwmark - к пакету. Трэкингом соединений занимается conntrack.
Посмотреть его таблицу можно командой "conntrack -L". Там же найдете connmark : mark=xxxx.
Как только видим приходящий с wgvps пакет с новым соединением, отмечаем его connmark как 0x800/0x800.
При этом fwmark не меняется, иначе бы пакет тут же бы завернулся обратно на wgvps согласно ip rule.
Если к нам приходит пакет с какого-то другого интерфейса, то восстанавливаем его connmark в fwmark по маске 0x800.
И теперь он подпадает под правило ip rule, заворачиваясь на wgvps, что и требовалось.
--/etc/firewall.user----------------------------
. /opt/zapret/init.d/openwrt/functions
create_ipset no-update
network_find_wan_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 -j MARK --set-mark 0x800/0x800
ipt OUTPUT -t mangle -o $DEVICE -m set --match-set ipban dst -j MARK --set-mark 0x800/0x800
ipt OUTPUT -t mangle -o $DEVICE -j MARK --set-mark 0x1000/0x1000
done
network_get_device DEVICE lan
ipt PREROUTING -t mangle -i $DEVICE -p tcp --dport 443 -m set --match-set zapret dst -j MARK --set-mark 0x800/0x800
ipt PREROUTING -t mangle -i $DEVICE -m set --match-set ipban dst -j MARK --set-mark 0x800/0x800
# do masquerade for OUTPUT to ensure correct outgoing address
ipt postrouting_tunvps_rule -t nat -m mark --mark 0x1000/0x1000 -j MASQUERADE
# incoming from wgvps
network_get_device DEVICE wgvps
ipt PREROUTING -t mangle ! -i $DEVICE -j CONNMARK --restore-mark --nfmask 0x800 --ctmask 0x800
ipt PREROUTING -t mangle -i $DEVICE -m conntrack --ctstate NEW -j CONNMARK --set-xmark 0x800/0x800
------------------------------------------------
# fw3 restart
Сейчас уже можно с vpn сервера пингануть ip адрес внутри локалки клиента. Пинги должны ходить.
Отсутствие двойного NAT значительно облегчает проброс портов с внешнего IP vpn сервера в локалку какого-либо клиента.
Для этого надо выполнить 2 действия : добавить разрешение в фаервол на клиенте и сделать dnat на сервере.
Пример форварда портов 5001 и 5201 на 192.168.2.2 :
--/etc/config/firewall--------------------------
config rule
option target 'ACCEPT'
option src 'tunvps'
option dest 'lan'
option proto 'tcp udp'
option dest_port '5001 5201'
option dest_ip '192.168.2.2'
option name 'IPERF'
------------------------------------------------
# fw3 restart
--/etc/network/interfaces.d/wgvps-------------
post-up iptables -t nat -A PREROUTING -i eth0 -p tcp -m multiport --dports 5001,5201 -j DNAT --to-destination 192.168.2.2
post-up iptables -t nat -A POSTROUTING -o $IFACE -d 192.168.2.2 -p tcp -m multiport --dports 5001,5201 -j MASQUERADE
post-up iptables -t nat -A PREROUTING -i eth0 -p udp -m multiport --dports 5001,5201 -j DNAT --to-destination 192.168.2.2
post-up iptables -t nat -A POSTROUTING -o $IFACE -d 192.168.2.2 -p udp -m multiport --dports 5001,5201 -j MASQUERADE
post-down iptables -t nat -D PREROUTING -i eth0 -p tcp -m multiport --dports 5001,5201 -j DNAT --to-destination 192.168.2.2
post-down iptables -t nat -D POSTROUTING -o $IFACE -d 192.168.2.2 -p tcp -m multiport --dports 5001,5201 -j MASQUERADE
post-down iptables -t nat -D PREROUTING -i eth0 -p udp -m multiport --dports 5001,5201 -j DNAT --to-destination 192.168.2.2
post-down iptables -t nat -D POSTROUTING -o $IFACE -d 192.168.2.2 -p udp -m multiport --dports 5001,5201 -j MASQUERADE
----------------------------------------------
# ifdown wgvps ; ifup wgvps
Пример приведен для iperf и iperf3, чтобы показать как пробрасывать несколько портов tcp+udp с минимальным количеством команд.
Проброс tcp и udp порта так же необходим для полноценной работы bittorrent клиента, чтобы работали входящие.
--- Как мне отправлять на vpn весь трафик с bittorrent ? ---
Можно поступить так : посмотрите порт в настройках torrent клиента, убедитесь, что не поставлено "случайный порт",
добавьте на роутер правило маркировки по порту источника.
Но мне предпочтительно иное решение. На windows есть замечательная возможность
прописать правило установки поля качества обслуживания в заголовках ip пакетов в зависимости от процесса-источника.
Для windows 7/2008R2 необходимо будет установить ключик реестра и перезагрузить комп :
# reg add HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\services\Tcpip\QoS /v "Do not use NLA" /t REG_SZ /d "1"
Редактировать политику можно в : gpedit.msc -> Computer Configuration -> Windows Settings -> Policy-based QoS
На win 10 ключик реестра больше не работает, правила qos в gpedit применяются только для профиля домена.
Необходимо пользоваться командой powershell New-NetQosPolicy. Гуглите хелп по ней. Пример :
# powershell New-NetQosPolicy -Name "torrent" -AppPathNameMatchCondition "qbittorrent.exe" -DSCPAction 1
Однозначно требуется проверка в wireshark или netmon успешности установки поля dscp. Если там по-прежнему 0x00,
значит что-то не сработало. 0x04 означает DSCP=1 (dscp находится в старших 6 битах).
На роутере в фаер прописываем правило :
--/etc/config/firewall--------------------------
config rule
option target 'MARK'
option src 'lan'
option proto 'all'
option extra '-m dscp --dscp 1'
option name 'route-dscp-1'
option set_mark '0x0800/0x0800'
------------------------------------------------
# fw3 restart
Теперь все с полем dscp "1" идет на vpn. Клиент сам решает какой трафик ему нужно забрасывать
на vpn, перенастраивать роутер не нужно.
На linux клиенте проще всего будет выставлять dscp в iptables по номеру порта источника :
--/etc/rc.local---------------------------------
iptables -A OUTPUT -t mangle -p tcp --sport 23444 -j DSCP --set-dscp 1
iptables -A OUTPUT -t mangle -p udp --sport 23444 -j DSCP --set-dscp 1
------------------------------------------------
можно привязываться к pid процесса, но тогда нужно перенастраивать iptables при каждом перезапуске
торент клиента, это требует рута, и все становится очень неудобно.
--- Автоматизация проброса портов через miniupnd ---
Да, его тоже можно использовать на vps. Только как всегда есть нюансы.
miniupnpd поддерживает 3 протокола IGD : upnp,nat-pmp и pcp.
upnp и pcp работают через мультикаст, который не пройдет через wgvps.
nat-pmp работает через посылку специальных сообщений на udp:5351 на default gateway.
Обычно их обслуживает miniupnpd на роутере. При создании lease miniupnpd добавляет
правила для проброса портов в цепочку iptables MINIUPNPD, при потери lease - убирает.
udp:5351 можно перенаправить на vpn сервер через DNAT, чтобы их обрабатывал miniupnpd там.
Но вы должны иметь однозначный критерий перенаправления.
Если вы решили завернуть на vpn все, то проблем нет. Пробрасываем udp:5351 безусловно.
Если у вас идет перенаправление только с торрент, то необходимо к условию перенаправления
добавить условия, выделяющие torrent трафик из прочего. Или по dscp, или по sport.
Чтобы запросы от остальных программ обрабатывались miniupnpd на роутере.
Если какая-то программа создаст lease не там, где нужно, то входящий трафик до нее не дойдет.
На роутере стоит запретить протокол upnp, чтобы торрент клиент не удовлетворился запросом,
обслуженным по upnp на роутере, и пытался использовать nat-pmp.
--/etc/config/upnp--------------------------
config upnpd 'config'
.....
option enable_upnp '0'
------------------------------------------------
/etc/init.d/miniupnpd restart
Делаем проброс порта на роутере.
Для простоты изложения будем считать, что на vpn у нас завернут весь трафик.
Если это не так, то следует добавить фильтр в "config redirect".
Заодно выделяем диапазон портов для торрент клиентов.
Порт в торент клиенте следует прописать какой-то из этого диапазона.
------------------------------------------------
config redirect
option enabled '1'
option target 'DNAT'
option src 'lan'
option dest 'tunvps'
option proto 'udp'
option src_dport '5351'
option dest_ip '192.168.254.1'
option dest_port '5351'
option name 'NAT-PMP'
option reflection '0'
config rule
option enabled '1'
option target 'ACCEPT'
option src 'tunvps'
option dest 'lan'
option name 'tunvps-torrent'
option dest_port '28000-28009'
------------------------------------------------
fw3 reload
На сервере :
apt install miniupnpd
--- /etc/miniupnpd/miniupnpd.conf --------
enable_natpmp=yes
enable_upnp=no
lease_file=/var/log/upnp.leases
system_uptime=yes
clean_ruleset_threshold=10
clean_ruleset_interval=600
force_igd_desc_v1=no
listening_ip=192.168.254.1/16
ext_ifname=eth0
------------------------------------------
systemctl restart miniupnpd
listening_ip прописан именно таким образом, чтобы обозначить диапазон разрешенных IP.
С других IP он не будет обрабатывать запросы на редирект.
В ext_ifname впишите название inet интерфейса на сервере.
Запускаем торрент клиент. Попутно смотрим в tcpdump весь путь udp:5351 до сервера и обратно.
Смотрим syslog сервера на ругань от miniupnpd.
Если все ок, то можем проверить редиректы : iptables -t nat -nL MINIUPNPD
С какого-нибудь другого хоста (не vpn сервер, не ваше подключение) можно попробовать telnet-нуться на проброшенный порт.
Должно установиться соединение. Или качайте торент и смотрите в пирах флаг "I" (incoming).
Если "I" есть и по ним идет закачка, значит все в порядке.
ОСОБЕННОСТЬ НОВЫХ DEBIAN : по умолчанию используются iptables-nft. miniupnpd работает с iptables-legacy.
ЛЕЧЕНИЕ : update-alternatives --set iptables /usr/sbin/iptables-legacy
--- А если не заработало ? ---
Мануал пишется не как копипастная инструкция, а как помощь уже соображающему.
В руки вам ifconfig, ip, iptables, tcpdump, ping. В умелых руках творят чудеса.

View File

@ -0,0 +1,9 @@
GET / HTTP/1.1
Host: www.iana.org
Connection: keep-alive
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4300.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9,ru;q=0.8

Binary file not shown.

View File

@ -0,0 +1,35 @@
#!/system/bin/busybox sh
# download hostlist from http(s) (need curl, its absent by default),
# feed it to zapret. save flash write cycles
u="https://your.host.com/censorship/hoslist.txt"
SCRIPT=$(readlink -f "$0")
EXEDIR=$(dirname "$SCRIPT")
d=/data/censorship
[ -d $d ] || mkdir $d
f=$d/hostlist.txt
t=/hostlist.txt
curl -k --fail --max-time 10 -o "$t" "$u" && {
if [ -s "$t" ]; then
m1=$(md5sum "$t" | cut -d ' ' -f 1)
m2=$(md5sum "$f" | cut -d ' ' -f 1)
echo $m1 $m2
if [ -z "$m2" ] || [ "$m1" != "$m2" ]; then
echo updating hostlist
cp -f "$t" "$f"
else
echo hostlist was not changed. keeping old copy
fi
else
echo downloaded hostlist is empty. disabling zapret
rm "$f"
fi
}
rm -f "$t"
"$EXEDIR/unzapret"
[ -s "$f" ] && exec "$EXEDIR/zapret" "--hostlist=$f"

View File

@ -0,0 +1,39 @@
#!/system/bin/busybox sh
# download hostlist from http(s) (need curl, its absent by default),
# resolve to ip list, feed to zapret-ip. save flash write cycles
u="https://your.host.com/censorship/hoslist.txt"
SCRIPT=$(readlink -f "$0")
EXEDIR=$(dirname "$SCRIPT")
d=/data/censorship
[ -d $d ] || mkdir $d
f=$d/hostlist.txt
t=/hostlist.txt
i=/iplist.txt
curl -k --fail --max-time 10 -o "$t" "$u" && {
if [ -s "$t" ]; then
m1=$(md5sum "$t" | cut -d ' ' -f 1)
m2=$(md5sum "$f" | cut -d ' ' -f 1)
echo $m1 $m2
if [ -z "$m2" ] || [ "$m1" != "$m2" ]; then
echo updating hostlist
cp -f "$t" "$f"
else
echo hostlist was not changed. keeping old copy
fi
else
echo downloaded hostlist is empty. disabling zapret
rm "$f"
fi
}
rm -f "$t"
"$EXEDIR/unzapret-ip"
[ -s "$f" ] && {
mdig --threads=10 --family=4 <"$f" >"$i"
[ -s "$i" ] && exec "$EXEDIR/zapret-ip" "$i"
}

Binary file not shown.

9
files/huawei/E8372/unzapret Executable file
View File

@ -0,0 +1,9 @@
#!/system/bin/busybox sh
rule="PREROUTING -t nat -i br0 ! -d 192.168.0.0/16 -p tcp -m multiport --dports 80,443 -j REDIRECT --to-port 1"
iptables -C $rule 2>/dev/null && iptables -D $rule
killall tpws
rule="OUTPUT -t mangle -o wan0 -p tcp -m multiport --dports 80,443 -m mark ! --mark 0x40000000/0x40000000 -j NFQUEUE --queue-num 200 --queue-bypass"
iptables -C $rule 2>/dev/null && iptables -D $rule
killall nfqws

11
files/huawei/E8372/unzapret-ip Executable file
View File

@ -0,0 +1,11 @@
#!/system/bin/busybox sh
rule="PREROUTING -t nat -i br0 -p tcp -m multiport --dports 80,443 -j tpws"
iptables -C $rule 2>/dev/null && iptables -D $rule
iptables -F tpws -t nat
iptables -X tpws -t nat
killall tpws
rule="OUTPUT -t mangle -o wan0 -p tcp -m multiport --dports 80,443 -m mark ! --mark 0x40000000/0x40000000 -j NFQUEUE --queue-num 200 --queue-bypass"
iptables -C $rule 2>/dev/null && iptables -D $rule
killall nfqws

15
files/huawei/E8372/zapret Executable file
View File

@ -0,0 +1,15 @@
#!/system/bin/busybox sh
# $1 - additional parameters for nfqws
insmod /online/modules/unfuck_nfqueue.ko 2>/dev/null
rule="PREROUTING -t nat -i br0 ! -d 192.168.0.0/16 -p tcp -m multiport --dports 80,443 -j REDIRECT --to-port 1"
iptables -C $rule 2>/dev/null || iptables -I $rule
tpws --uid 1:3003 --port=1 --daemon
rule="OUTPUT -t mangle -o wan0 -p tcp -m multiport --dports 80,443 -m mark ! --mark 0x40000000/0x40000000 -j NFQUEUE --queue-num 200 --queue-bypass"
iptables -C $rule 2>/dev/null || iptables -I $rule
nfqws --uid 2 --qnum=200 --dpi-desync=disorder --dpi-desync-ttl=8 --dpi-desync-fooling=md5sig --daemon $1

34
files/huawei/E8372/zapret-ip Executable file
View File

@ -0,0 +1,34 @@
#!/system/bin/busybox sh
# $1 - ip list file. create individual rules for tpws redirection. ipset is not available
[ -z "$1" ] && {
echo need iplist file as parameter
exit 1
}
insmod /online/modules/unfuck_nfqueue.ko 2>/dev/null
tpws --maxconn=1024 --uid 1:3003 --port=1 --daemon
REDIR="-j REDIRECT --to-port 1"
iptables -F tpws -t nat
iptables -X tpws -t nat
iptables -N tpws -t nat
iptables -A tpws -t nat -d 192.168.0.0/16 -j RETURN
while read ip; do
echo redirecting $ip
iptables -A tpws -t nat -d $ip -p tcp $REDIR
done <"$1"
rule="PREROUTING -t nat -i br0 -p tcp -m multiport --dports 80,443 -j tpws"
iptables -C $rule 2>/dev/null || iptables -I $rule
nfqws --uid 2 --qnum=200 --dpi-desync=disorder --dpi-desync-ttl=8 --dpi-desync-fooling=md5sig --daemon
rule="OUTPUT -t mangle -o wan0 -p tcp -m multiport --dports 80,443 -m mark ! --mark 0x40000000/0x40000000 -j NFQUEUE --queue-num 200 --queue-bypass"
iptables -C $rule 2>/dev/null || iptables -I $rule

401
init.d/macos/functions Normal file
View File

@ -0,0 +1,401 @@
[ -n "$ZAPRET_BASE" ] || ZAPRET_BASE=/opt/zapret
IPSET_DIR=$ZAPRET_BASE/ipset
. "$IPSET_DIR/def.sh"
HOSTLIST="$ZHOSTLIST.gz"
[ -f "$HOSTLIST" ] || HOSTLIST="$ZHOSTLIST"
[ -f "$HOSTLIST" ] || HOSTLIST="$ZUSERLIST"
PIDDIR=/var/run
TPPORT=988
TPWS_WAIT="--bind-wait-ip=60"
TPWS="$ZAPRET_BASE/tpws/tpws"
PF_MAIN="/etc/pf.conf"
PF_ANCHOR_DIR=/etc/pf.anchors
PF_ANCHOR_ZAPRET="$PF_ANCHOR_DIR/zapret"
PF_ANCHOR_ZAPRET_V4="$PF_ANCHOR_DIR/zapret-v4"
PF_ANCHOR_ZAPRET_V6="$PF_ANCHOR_DIR/zapret-v6"
[ -n "$IFACE_WAN" ] && OWAN=" on $IFACE_WAN"
on_off_function()
{
# $1 : function name on
# $2 : function name off
# $3 : 0 - off, 1 - on
local F="$1"
[ "$3" = "1" ] || F="$2"
shift
shift
shift
"$F" "$@"
}
run_daemon()
{
# $1 - daemon number : 1,2,3,...
# $2 - daemon
# $3 - daemon args
# use $PIDDIR/$DAEMONBASE$1.pid as pidfile
local DAEMONBASE="$(basename $2)"
local PIDFILE="$PIDDIR/$DAEMONBASE$1.pid"
local ARGS="--daemon --pidfile=$PIDFILE $3"
[ -f "$PIDFILE" ] && pgrep -qF "$PIDFILE" && {
echo Already running $1: $2
return 0
}
echo "Starting daemon $1: $2 $ARGS"
"$2" $ARGS
}
stop_daemon()
{
# $1 - daemon number : 1,2,3,...
# $2 - daemon
# use $PIDDIR/$DAEMONBASE$1.pid as pidfile
local PID
local DAEMONBASE="$(basename $2)"
local PIDFILE="$PIDDIR/$DAEMONBASE$1.pid"
[ -f "$PIDFILE" ] && read PID <"$PIDFILE"
[ -n "$PID" ] && {
echo "Stopping daemon $1: $2 (PID=$PID)"
kill $PID
rm -f "$PIDFILE"
}
return 0
}
do_daemon()
{
# $1 - 1 - run, 0 - stop
on_off_function run_daemon stop_daemon "$@"
}
filter_apply_hostlist_target()
{
# $1 - var name of tpws or nfqws params
[ "$MODE_FILTER" = "hostlist" ] && eval $1="\"\$$1 --hostlist=$HOSTLIST\""
}
tpws_apply_binds()
{
local o
[ "$DISABLE_IPV4" = "1" ] || o="--bind-addr=127.0.0.1"
[ "$DISABLE_IPV6" = "1" ] || {
for i in lo0 $IFACE_LAN; do
o="$o --bind-iface6=$i --bind-linklocal=force $TPWS_WAIT"
done
}
eval $1="\"\$$1 $o\""
}
wait_interface_ll()
{
echo waiting for an ipv6 link local address on $1 ...
"$TPWS" --bind-wait-only --bind-iface6=$1 --bind-linklocal=force $TPWS_WAIT
}
wait_lan_ll()
{
[ "$DISABLE_IPV6" != "1" ] && [ -n "$IFACE_LAN" ] && {
wait_interface_ll $IFACE_LAN >&2 || {
echo "wait interface failed"
return 1
}
}
return 0
}
get_ipv6_linklocal()
{
ifconfig $1 | sed -nEe 's/^.*inet6 (fe80:[a-f0-9:]+).*/\1/p'
}
pf_anchor_root_reload()
{
echo reloading PF root anchor
pfctl -qf "$PF_MAIN"
}
pf_anchor_root()
{
local patch
[ -f "$PF_MAIN" ] && {
grep -q '^rdr-anchor "zapret"$' "$PF_MAIN" || {
echo patching rdr-anchor in $PF_MAIN
patch=1
sed -i '' -e '/^rdr-anchor "com\.apple\/\*"$/i \
rdr-anchor "zapret"
' $PF_MAIN
}
grep -q '^anchor "zapret"$' "$PF_MAIN" || {
echo patching anchor in $PF_MAIN
patch=1
sed -i '' -e '/^anchor "com\.apple\/\*"$/i \
anchor "zapret"
' $PF_MAIN
}
grep -q "^set limit table-entries" "$PF_MAIN" || {
echo patching table-entries limit
patch=1
sed -i '' -e '/^scrub-anchor "com\.apple\/\*"$/i \
set limit table-entries 5000000
' $PF_MAIN
}
grep -q '^anchor "zapret"$' "$PF_MAIN" &&
grep -q '^rdr-anchor "zapret"$' "$PF_MAIN" &&
grep -q '^set limit table-entries' "$PF_MAIN" && {
if [ -n "$patch" ]; then
echo successfully patched $PF_MAIN
pf_anchor_root_reload
else
echo successfully checked zapret anchors in $PF_MAIN
fi
return 0
}
}
echo ----------------------------------
echo Automatic $PF_MAIN patching failed. You must apply root anchors manually in your PF config.
echo rdr-anchor \"zapret\"
echo anchor \"zapret\"
echo ----------------------------------
return 1
}
pf_anchor_root_del()
{
sed -i '' -e '/^anchor "zapret"$/d' -e '/^rdr-anchor "zapret"$/d' -e '/^set limit table-entries/d' "$PF_MAIN"
}
pf_anchor_zapret()
{
[ "$DISABLE_IPV4" = "1" ] || {
if [ -f "$ZIPLIST_EXCLUDE" ]; then
echo "table <nozapret> persist file \"$ZIPLIST_EXCLUDE\""
else
echo "table <nozapret> persist"
fi
}
[ "$DISABLE_IPV4" = "1" ] || {
if [ -f "$ZIPLIST_EXCLUDE6" ]; then
echo "table <nozapret6> persist file \"$ZIPLIST_EXCLUDE6\""
else
echo "table <nozapret6> persist"
fi
}
echo
[ "$DISABLE_IPV4" = "1" ] || echo "rdr-anchor \"/zapret-v4\" inet to !<nozapret>"
[ "$DISABLE_IPV6" = "1" ] || echo "rdr-anchor \"/zapret-v6\" inet6 to !<nozapret6>"
[ "$DISABLE_IPV4" = "1" ] || echo "anchor \"/zapret-v4\" inet to !<nozapret>"
[ "$DISABLE_IPV6" = "1" ] || echo "anchor \"/zapret-v6\" inet6 to !<nozapret6>"
}
pf_anchor_zapret_tables()
{
# $1 - variable to receive applied table names
# $2/$3 $4/$5 ... table_name/table_file
local tblv=$1
local _tbl
shift
[ "$MODE_FILTER" = "ipset" ] &&
{
while [ -n "$1" ] && [ -n "$2" ] ; do
[ -f "$2" ] && {
echo "table <$1> file \"$2\""
_tbl="$_tbl<$1> "
}
shift
shift
done
}
[ -n "$_tbl" ] || _tbl="any"
eval $tblv="\"\$_tbl\""
}
pf_anchor_port_target()
{
if [ "$MODE_HTTP" = "1" ] && [ "$MODE_HTTPS" = "1" ]; then
echo "{80,443}"
elif [ "$MODE_HTTPS" = "1" ]; then
echo "443"
elif [ "$MODE_HTTP" = "1" ]; then
echo "80"
fi
}
pf_anchor_zapret_v4()
{
local tbl port
[ "$DISABLE_IPV4" = "1" ] || {
[ ! "$MODE_HTTP" = "1" ] && [ ! "$MODE_HTTPS" = "1" ] && return
pf_anchor_zapret_tables tbl zapret-user "$ZIPLIST_USER" zapret "$ZIPLIST"
port=$(pf_anchor_port_target)
for t in $tbl; do
[ -n "$IFACE_LAN" ] && echo "rdr on $IFACE_LAN inet proto tcp from any to $t port $port -> 127.0.0.1 port $TPPORT"
done
echo "rdr on lo0 inet proto tcp from !127.0.0.0/8 to any port $port -> 127.0.0.1 port $TPPORT"
for t in $tbl; do
echo "pass out$OWAN route-to (lo0 127.0.0.1) inet proto tcp from !127.0.0.0/8 to $t port $port user { >root }"
done
}
}
pf_anchor_zapret_v6()
{
local tbl port LL_LAN
[ "$DISABLE_IPV6" = "1" ] || {
[ ! "$MODE_HTTP" = "1" ] && [ ! "$MODE_HTTPS" = "1" ] && return
# LAN link local is only for router
[ -n "$IFACE_LAN" ] && LL_LAN=$(get_ipv6_linklocal $IFACE_LAN)
pf_anchor_zapret_tables tbl zapret6-user "$ZIPLIST_USER6" zapret6 "$ZIPLIST6"
port=$(pf_anchor_port_target)
for t in $tbl; do
[ -n "$LL_LAN" ] && echo "rdr on $IFACE_LAN inet6 proto tcp from any to $t port $port -> $LL_LAN port $TPPORT"
done
echo "rdr on lo0 inet6 proto tcp from !::1 to any port $port -> fe80::1 port $TPPORT"
for t in $tbl; do
echo "pass out$OWAN route-to (lo0 fe80::1) inet6 proto tcp from !::1 to $t port $port user { >root }"
done
}
}
pf_anchors_create()
{
wait_lan_ll
pf_anchor_zapret >"$PF_ANCHOR_ZAPRET"
pf_anchor_zapret_v4 >"$PF_ANCHOR_ZAPRET_V4"
pf_anchor_zapret_v6 >"$PF_ANCHOR_ZAPRET_V6"
}
pf_anchors_del()
{
rm -f "$PF_ANCHOR_ZAPRET" "$PF_ANCHOR_ZAPRET_V4" "$PF_ANCHOR_ZAPRET_V6"
}
pf_anchors_load()
{
echo loading zapret anchor from "$PF_ANCHOR_ZAPRET"
pfctl -qa zapret -f "$PF_ANCHOR_ZAPRET" || {
echo error loading zapret anchor
return 1
}
if [ "$DISABLE_IPV4" = "1" ]; then
echo clearing zapret-v4 anchor
pfctl -qa zapret-v4 -F all 2>/dev/null
else
echo loading zapret-v4 anchor from "$PF_ANCHOR_ZAPRET_V4"
pfctl -qa zapret-v4 -f "$PF_ANCHOR_ZAPRET_V4" || {
echo error loading zapret-v4 anchor
return 1
}
fi
if [ "$DISABLE_IPV6" = "1" ]; then
echo clearing zapret-v6 anchor
pfctl -qa zapret-v6 -F all 2>/dev/null
else
echo loading zapret-v6 anchor from "$PF_ANCHOR_ZAPRET_V6"
pfctl -qa zapret-v6 -f "$PF_ANCHOR_ZAPRET_V6" || {
echo error loading zapret-v6 anchor
return 1
}
fi
echo successfully loaded PF anchors
return 0
}
pf_anchors_clear()
{
echo clearing zapret anchors
pfctl -qa zapret-v4 -F all 2>/dev/null
pfctl -qa zapret-v6 -F all 2>/dev/null
pfctl -qa zapret -F all 2>/dev/null
}
pf_enable()
{
echo enabling PF
pfctl -qe
}
pf_table_reload()
{
echo reloading zapret tables
[ "$DISABLE_IPV4" = "1" ] || pfctl -qTl -a zapret-v4 -f "$PF_ANCHOR_ZAPRET_V4"
[ "$DISABLE_IPV6" = "1" ] || pfctl -qTl -a zapret-v6 -f "$PF_ANCHOR_ZAPRET_V6"
pfctl -qTl -a zapret -f "$PF_ANCHOR_ZAPRET"
}
zapret_do_firewall()
{
# $1 - 1 - add, 0 - del
case "${MODE}" in
tpws)
if [ "$1" = "1" ] ; then
pf_anchor_root || return 1
pf_anchors_create
pf_anchors_load || return 1
pf_enable
else
pf_anchors_clear
fi
;;
filter)
;;
*)
echo "unsupported MODE=$MODE"
return 1
;;
esac
return 0
}
zapret_apply_firewall()
{
zapret_do_firewall 1 "$@"
}
zapret_unapply_firewall()
{
zapret_do_firewall 0 "$@"
}
zapret_restart_firewall()
{
zapret_unapply_firewall "$@"
zapret_apply_firewall "$@"
}
zapret_do_daemons()
{
# $1 - 1 - run, 0 - stop
local opt
case "${MODE}" in
tpws)
[ "$1" = "1" ] && [ "$DISABLE_IPV4" = "1" ] && [ "$DISABLE_IPV6" = "1" ] && {
echo "both ipv4 and ipv6 are disabled. nothing to do"
return 0
}
# MacOS requires root. kernel hardcoded requirement for /dev/pf ioctls
opt="--user=root --port=$TPPORT"
filter_apply_hostlist_target opt
tpws_apply_binds opt
opt="$opt $TPWS_OPT"
do_daemon $1 1 "$TPWS" "$opt"
;;
filter)
;;
*)
echo "unsupported MODE=$MODE"
return 1
;;
esac
}
zapret_run_daemons()
{
zapret_do_daemons 1 "$@"
}
zapret_stop_daemons()
{
zapret_do_daemons 0 "$@"
}
zapret_restart_daemons()
{
zapret_stop_daemons "$@"
zapret_run_daemons "$@"
}

51
init.d/macos/zapret Executable file
View File

@ -0,0 +1,51 @@
#!/bin/sh
EXEDIR="$(dirname "$0")"
ZAPRET_BASE="$EXEDIR/../.."
ZAPRET_BASE="$(cd "$ZAPRET_BASE"; pwd)"
. "$EXEDIR/functions"
case "$1" in
start)
zapret_run_daemons
[ "$INIT_APPLY_FW" != "1" ] || zapret_apply_firewall
;;
stop)
[ "$INIT_APPLY_FW" != "1" ] || zapret_unapply_firewall
zapret_stop_daemons
;;
restart)
"$0" stop
"$0" start
;;
start-fw)
zapret_apply_firewall
;;
stop-fw)
zapret_unapply_firewall
;;
restart-fw)
zapret_restart_firewall
;;
reload-fw-tables)
pf_table_reload
;;
start-daemons)
zapret_run_daemons
;;
stop-daemons)
zapret_stop_daemons
;;
restart-daemons)
zapret_restart_daemons
;;
*)
N="$SCRIPT/$NAME"
echo "Usage: $N {start|stop|start-fw|stop-fw|restart-fw|reload-fw-tables|start-daemons|stop-daemons|restart-daemons}" >&2
exit 1
;;
esac

17
init.d/macos/zapret.plist Normal file
View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>zapret</string>
<key>LaunchOnlyOnce</key>
<false/>
<key>ProgramArguments</key>
<array>
<string>/opt/zapret/init.d/macos/zapret</string>
<string>start</string>
</array>
<key>RunAtLoad</key>
<true/>
</dict>
</plist>

8
init.d/openwrt/90-zapret Normal file
View File

@ -0,0 +1,8 @@
#!/bin/sh
ZAPRET=/etc/init.d/zapret
[ -x "$ZAPRET" ] && [ "$INTERFACE" = "lan" ] && {
[ "$ACTION" = "ifup" ] && {
$ZAPRET enabled && $ZAPRET restart
}
}

20
init.d/openwrt/custom Normal file
View File

@ -0,0 +1,20 @@
# this script contain your special code to launch daemons and configure firewall
# use helpers from "functions" file and "zapret" init script
# in case of upgrade keep this file only, do not modify others
zapret_custom_daemons()
{
# PLACEHOLDER
echo !!! NEED ATTENTION !!!
echo Start daemon\(s\)
echo Study how other sections work
run_daemon 1 /bin/sleep 20
}
zapret_custom_firewall()
{
# PLACEHOLDER
echo !!! NEED ATTENTION !!!
echo Configure iptables for required actions
echo Study how other sections work
}

View File

@ -0,0 +1,44 @@
# this custom script demonstrates how to use 2 copies of nfqws
# it preserves config settings : MODE_HTTP, MODE_HTTP_KEEPALIVE, MODE_HTTPS, MODE_FILTER, NFQWS_OPT_DESYNC
# NFQWS_OPT_DESYNC - parameters for http
# NFQWS_OPT_DESYNC2 - parameters for https. you should add this variable to config file, its absent there
QNUM2=$(($QNUM+1))
zapret_custom_daemons()
{
local opt
[ "$MODE_HTTP" = "1" ] && {
opt="$NFQWS_OPT_BASE $NFQWS_OPT_DESYNC"
filter_apply_hostlist_target opt
run_daemon 1 $NFQWS "$opt"
}
[ "$MODE_HTTPS" = "1" ] && {
opt="$NFQWS_OPT_BASE $NFQWS_OPT_DESYNC2 --qnum=$QNUM2"
filter_apply_hostlist_target opt
run_daemon 2 $NFQWS "$opt"
}
}
zapret_custom_firewall()
{
local f4 f6
local first_packet_only="-m connbytes --connbytes-dir=original --connbytes-mode=packets --connbytes 2:4"
local desync="-m mark ! --mark $DESYNC_MARK/$DESYNC_MARK"
[ "$MODE_HTTP" = "1" ] && {
f4="--dport 80"
[ "$MODE_HTTP_KEEPALIVE" = "1" ] || f4="$f4 $first_packet_only"
f6=$f4
filter_apply_ipset_target f4 f6
fw_nfqws_post "$f4 $desync" "$f6 $desync" $QNUM
}
[ "$MODE_HTTPS" = "1" ] && {
f4="--dport 443 $first_packet_only"
f6=$f4
filter_apply_ipset_target f4 f6
fw_nfqws_post "$f4 $desync" "$f6 $desync" $QNUM2
}
}

View File

@ -0,0 +1,39 @@
# this custom script demonstrates how to apply tpws to http and nfqws to https
# it preserves config settings : MODE_HTTP, MODE_HTTPS, MODE_FILTER, TPWS_OPT, NFQWS_OPT_DESYNC
zapret_custom_daemons()
{
local opt
[ "$MODE_HTTP" = "1" ] && {
opt="$TPWS_OPT"
filter_apply_hostlist_target opt
run_tpws 1 "$opt"
}
[ "$MODE_HTTPS" = "1" ] && {
opt="$NFQWS_OPT_BASE $NFQWS_OPT_DESYNC"
filter_apply_hostlist_target opt
run_daemon 2 $NFQWS "$opt"
}
}
zapret_custom_firewall()
{
local f4 f6
local first_packet_only="-m connbytes --connbytes-dir=original --connbytes-mode=packets --connbytes 2:4"
local desync="-m mark ! --mark $DESYNC_MARK/$DESYNC_MARK"
[ "$MODE_HTTP" = "1" ] && {
f4="--dport 80"
f6=$f4
filter_apply_ipset_target f4 f6
fw_tpws "$f4" "$f6" $TPPORT
}
[ "$MODE_HTTPS" = "1" ] && {
f4="--dport 443 $first_packet_only"
f6=$f4
filter_apply_ipset_target f4 f6
fw_nfqws_post "$f4 $desync" "$f6 $desync" $QNUM
}
}

View File

@ -0,0 +1,20 @@
# this script contain your special code to launch daemons and configure firewall
# use helpers from "functions" file and "zapret" init script
# in case of upgrade keep this file only, do not modify others
zapret_custom_daemons()
{
# PLACEHOLDER
echo !!! NEED ATTENTION !!!
echo Start daemon\(s\)
echo Study how other sections work
run_daemon 1 /bin/sleep 20
}
zapret_custom_firewall()
{
# PLACEHOLDER
echo !!! NEED ATTENTION !!!
echo Configure iptables for required actions
echo Study how other sections work
}

View File

@ -0,0 +1,11 @@
SCRIPT=$(readlink /etc/init.d/zapret)
if [ -n "$SCRIPT" ]; then
EXEDIR=$(dirname "$SCRIPT")
ZAPRET_BASE=$(readlink -f "$EXEDIR/../..")
else
ZAPRET_BASE=/opt/zapret
fi
. "$ZAPRET_BASE/init.d/openwrt/functions"
zapret_apply_firewall

425
init.d/openwrt/functions Normal file
View File

@ -0,0 +1,425 @@
. /lib/functions/network.sh
[ -n "$ZAPRET_BASE" ] || ZAPRET_BASE=/opt/zapret
. "$ZAPRET_BASE/config"
QNUM=200
TPPORT=988
TPWS_USER=daemon
TPWS_LOCALHOST4=127.0.0.127
[ -n "$DESYNC_MARK" ] || DESYNC_MARK=0x40000000
# max wait time for the link local ipv6 on the LAN interface
LINKLOCAL_WAIT_SEC=5
IPSET_CR="$ZAPRET_BASE/ipset/create_ipset.sh"
CUSTOM_SCRIPT="$ZAPRET_BASE/init.d/openwrt/custom"
[ -f "$CUSTOM_SCRIPT" ] && . "$CUSTOM_SCRIPT"
IPSET_EXCLUDE="-m set ! --match-set nozapret"
IPSET_EXCLUDE6="-m set ! --match-set nozapret6"
exists()
{
which "$1" >/dev/null 2>/dev/null
}
existf()
{
type "$1" >/dev/null 2>/dev/null
}
# can be multiple ipv6 outgoing interfaces
# uplink from isp, tunnelbroker, vpn, ...
# want them all. who knows what's the real one that blocks sites
# dont want any manual configuration - want to do it automatically
# standard network_find_wan[6] return only the first
# we use low level function from network.sh to avoid this limitation
# it can change theoretically and stop working
network_find_wan_all()
{
__network_ifstatus "$1" "" "[@.route[@.target='0.0.0.0' && !@.table]].interface" "" 10 2>/dev/null && return
network_find_wan $1
}
network_find_wan6_all()
{
__network_ifstatus "$1" "" "[@.route[@.target='::' && !@.table]].interface" "" 10 2>/dev/null && return
network_find_wan6 $1
}
ipt()
{
iptables -C "$@" 2>/dev/null || iptables -I "$@"
}
ipt_del()
{
iptables -C "$@" 2>/dev/null && iptables -D "$@"
}
ipt6()
{
ip6tables -C "$@" 2>/dev/null || ip6tables -I "$@"
}
ipt6_del()
{
ip6tables -C "$@" 2>/dev/null && ip6tables -D "$@"
}
# there's no route_localnet for ipv6
# the best we can is to route to link local of the incoming interface
# OUTPUT - can DNAT to ::1
# PREROUTING - can't DNAT to ::1. can DNAT to link local of -i interface or to any global addr
# not a good idea to expose tpws to the world (bind to ::)
get_ipv6_linklocal()
{
# $1 - interface name. if empty - any interface
if exists ip ; then
local dev
[ -n "$1" ] && dev="dev $1"
ip addr show $dev | sed -e 's/^.*inet6 \([^ ]*\)\/[0-9]* scope link.*$/\1/;t;d' | head -n 1
else
ifconfig $1 | sed -re 's/^.*inet6 addr: ([^ ]*)\/[0-9]* Scope:Link.*$/\1/;t;d' | head -n 1
fi
}
get_ipv6_global()
{
# $1 - interface name. if empty - any interface
if exists ip ; then
local dev
[ -n "$1" ] && dev="dev $1"
ip addr show $dev | sed -e 's/^.*inet6 \([^ ]*\)\/[0-9]* scope global.*$/\1/;t;d' | head -n 1
else
ifconfig $1 | sed -re 's/^.*inet6 addr: ([^ ]*)\/[0-9]* Scope:Global.*$/\1/;t;d' | head -n 1
fi
}
dnat6_target()
{
# get target ip address for DNAT. prefer link locals
# tpws should be as inaccessible from outside as possible
# link local address can appear not immediately after ifup
# DNAT6_TARGET=- means attempt was made but address was not found (to avoid multiple re-attempts)
[ -n "$DNAT6_TARGET" ] || {
# no reason to query if its down
network_is_up lan || return
local DEVICE
network_get_device DEVICE lan
local ct=0
while
DNAT6_TARGET=$(get_ipv6_linklocal $DEVICE)
[ -n "$DNAT6_TARGET" ] && break
[ "$ct" -ge "$LINKLOCAL_WAIT_SEC" ] && break
echo waiting for the link local for another $(($LINKLOCAL_WAIT_SEC - $ct)) seconds ...
ct=$(($ct+1))
sleep 1
do :; done
[ -n "$DNAT6_TARGET" ] || {
echo no link local. getting global
DNAT6_TARGET=$(get_ipv6_global $DEVICE)
[ -n "$DNAT6_TARGET" ] || {
echo could not get any address
DNAT6_TARGET=-
}
}
}
}
fw_nfqws_pre4()
{
# $1 - filter ipv4
# $2 - queue number
local DEVICE wan_iface
[ "$DISABLE_IPV4" = "1" ] || {
network_find_wan_all wan_iface
for ext_iface in $wan_iface; do
network_get_device DEVICE $ext_iface
ipt PREROUTING -t mangle -i $DEVICE -p tcp $1 $IPSET_EXCLUDE src -j NFQUEUE --queue-num $2 --queue-bypass
done
}
}
fw_nfqws_pre6()
{
# $1 - filter ipv6
# $2 - queue number
local DEVICE wan_iface
[ "$DISABLE_IPV6" = "1" ] || {
network_find_wan6_all wan_iface
for ext_iface in $wan_iface; do
network_get_device DEVICE $ext_iface
ipt6 PREROUTING -t mangle -i $DEVICE -p tcp $1 $IPSET_EXCLUDE6 src -j NFQUEUE --queue-num $2 --queue-bypass
done
}
}
fw_nfqws_pre()
{
# $1 - filter ipv4
# $2 - filter ipv6
# $3 - queue number
fw_nfqws_pre4 "$1" $3
fw_nfqws_pre6 "$2" $3
}
fw_nfqws_post4()
{
# $1 - filter ipv4
# $2 - queue number
local DEVICE wan_iface
[ "$DISABLE_IPV4" = "1" ] || {
network_find_wan_all wan_iface
for ext_iface in $wan_iface; do
network_get_device DEVICE $ext_iface
ipt POSTROUTING -t mangle -o $DEVICE -p tcp $1 $IPSET_EXCLUDE dst -j NFQUEUE --queue-num $2 --queue-bypass
done
}
}
fw_nfqws_post6()
{
# $1 - filter ipv6
# $2 - queue number
local DEVICE wan_iface
[ "$DISABLE_IPV6" = "1" ] || {
network_find_wan6_all wan_iface
for ext_iface in $wan_iface; do
network_get_device DEVICE $ext_iface
ipt6 POSTROUTING -t mangle -o $DEVICE -p tcp $1 $IPSET_EXCLUDE6 dst -j NFQUEUE --queue-num $2 --queue-bypass
done
}
}
fw_nfqws_post()
{
# $1 - filter ipv4
# $2 - filter ipv6
# $3 - queue number
fw_nfqws_post4 "$1" $3
fw_nfqws_post6 "$2" $3
}
IPT_OWNER="-m owner ! --uid-owner $TPWS_USER"
fw_tpws4()
{
# $1 - filter ipv6
# $2 - tpws port
local DEVICE wan_iface
[ "$DISABLE_IPV4" = "1" ] || {
network_find_wan_all wan_iface
for ext_iface in $wan_iface; do
network_get_device DEVICE $ext_iface
ipt OUTPUT -t nat -o $DEVICE $IPT_OWNER -p tcp $1 $IPSET_EXCLUDE dst -j DNAT --to $TPWS_LOCALHOST4:$2
done
ipt prerouting_lan_rule -t nat -p tcp $1 $IPSET_EXCLUDE dst -j DNAT --to $TPWS_LOCALHOST4:$2
network_get_device DEVICE lan
[ -n "$DEVICE" ] && {
# allow localnet route only to special tpws IP
iptables -N input_lan_rule_zapret 2>/dev/null
ipt input_lan_rule_zapret -d 127.0.0.0/8 -j DROP
ipt input_lan_rule_zapret -d $TPWS_LOCALHOST4 -j RETURN
ipt input_lan_rule -j input_lan_rule_zapret
sysctl -qw net.ipv4.conf.$DEVICE.route_localnet=1
}
}
}
fw_tpws6()
{
# $1 - filter ipv6
# $2 - tpws port
local DEVICE wan_iface
[ "$DISABLE_IPV6" = "1" ] || {
network_find_wan6_all wan_iface
for ext_iface in $wan_iface; do
network_get_device DEVICE $ext_iface
ipt6 OUTPUT -t nat -o $DEVICE $IPT_OWNER -p tcp $1 $IPSET_EXCLUDE6 dst -j DNAT --to [::1]:$2
done
network_get_device DEVICE lan
dnat6_target
[ "$DNAT6_TARGET" != "-" ] && ipt6 PREROUTING -t nat -i $DEVICE -p tcp $1 $IPSET_EXCLUDE6 dst -j DNAT --to [$DNAT6_TARGET]:$2
}
}
fw_tpws()
{
# $1 - filter ipv4
# $2 - filter ipv6
# $3 - tpws port
fw_tpws4 "$1" $3
fw_tpws6 "$2" $3
}
filter_apply_port_target()
{
# $1 - var name of iptables filter
local f
if [ "$MODE_HTTP" = "1" ] && [ "$MODE_HTTPS" = "1" ]; then
f="-m multiport --dports 80,443"
elif [ "$MODE_HTTPS" = "1" ]; then
f="--dport 443"
elif [ "$MODE_HTTP" = "1" ]; then
f="--dport 80"
else
echo WARNING !!! HTTP and HTTPS are both disabled
fi
eval $1="\"\$$1 $f\""
}
filter_apply_ipset_target()
{
# $1 - var name of ipv4 iptables filter
# $2 - var name of ipv6 iptables filter
if [ "$MODE_FILTER" = "ipset" ]; then
eval $1="\"\$$1 -m set --match-set zapret dst\""
eval $2="\"\$$2 -m set --match-set zapret6 dst\""
fi
}
create_ipset()
{
echo "Creating ipset"
"$IPSET_CR" "$@"
}
is_flow_offload_avail()
{
# $1 = '' for ipv4, '6' for ipv6
grep -q FLOWOFFLOAD /proc/net/ip$1_tables_targets
}
list_nfqws_rules()
{
# $1 = '' for ipv4, '6' for ipv6
ip$1tables -S POSTROUTING -t mangle | grep "NFQUEUE --queue-num $QNUM --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 /' -e 's/--dport /--sport /' -e 's/--dports /--sports /' -e 's/ dst$/ src/' -e 's/ dst / src /'
}
apply_flow_offloading_enable_rule()
{
# $1 = '' for ipv4, '6' for ipv6
local i off='-j FLOWOFFLOAD'
[ "$FLOWOFFLOAD" = "hardware" ] && off="$off --hw"
i="forwarding_rule_zapret -m comment --comment zapret_traffic_offloading_enable -m conntrack --ctstate RELATED,ESTABLISHED $off"
echo enabling ipv${1:-4} flow offloading : $i
ip$1tables -A $i
}
apply_flow_offloading_exempt_rule()
{
# $1 = '' for ipv4, '6' for ipv6
local i v
v=$1
shift
i="forwarding_rule_zapret $@ -m comment --comment zapret_traffic_offloading_exemption -j RETURN"
echo applying ipv${v:-4} flow offloading exemption : $i
ip${v}tables -A $i
}
flow_offloading_exempt_v()
{
# $1 = '' for ipv4, '6' for ipv6
is_flow_offload_avail $1 || return 0
ipt$1_del forwarding_rule -j forwarding_rule_zapret
ip$1tables -F forwarding_rule_zapret 2>/dev/null
ip$1tables -X forwarding_rule_zapret 2>/dev/null
[ "$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 |
while read rule; do
apply_flow_offloading_exempt_rule "$1" $rule
done
apply_flow_offloading_enable_rule $1
ipt$1 forwarding_rule -j forwarding_rule_zapret
}
return 0
}
flow_offloading_exempt()
{
[ "$DISABLE_IPV4" = "1" ] || flow_offloading_exempt_v
[ "$DISABLE_IPV6" = "1" ] || flow_offloading_exempt_v 6
}
zapret_apply_firewall()
{
local first_packet_only="-m connbytes --connbytes-dir=original --connbytes-mode=packets --connbytes 2:4"
local desync="-m mark ! --mark $DESYNC_MARK/$DESYNC_MARK"
local f4 f6
# always create ipsets. ip_exclude ipset is required
create_ipset no-update
case "${MODE}" in
tpws)
if [ ! "$MODE_HTTP" = "1" ] && [ ! "$MODE_HTTPS" = "1" ]; then
echo both http and https are disabled. not applying redirection.
else
filter_apply_port_target f4
f6=$f4
filter_apply_ipset_target f4 f6
fw_tpws "$f4" "$f6" $TPPORT
fi
;;
nfqws)
if [ ! "$MODE_HTTP" = "1" ] && [ ! "$MODE_HTTPS" = "1" ]; then
echo both http and https are disabled. not applying redirection.
else
if [ "$MODE_HTTP_KEEPALIVE" = "1" ]; then
if [ "$MODE_HTTP" = "1" ]; then
f4="--dport 80"
f6=$f4
filter_apply_ipset_target f4 f6
fw_nfqws_post "$f4 $desync" "$f6 $desync" $QNUM
fi
if [ "$MODE_HTTPS" = "1" ]; then
f4="--dport 443 $first_packet_only"
f6=$f4
filter_apply_ipset_target f4 f6
fw_nfqws_post "$f4 $desync" "$f6 $desync" $QNUM
fi
else
filter_apply_port_target f4
f4="$f4 $first_packet_only"
f6=$f4
filter_apply_ipset_target f4 f6
fw_nfqws_post "$f4 $desync" "$f6 $desync" $QNUM
fi
fi
;;
custom)
existf zapret_custom_firewall && zapret_custom_firewall
;;
esac
flow_offloading_exempt
}

100
init.d/openwrt/zapret Executable file
View File

@ -0,0 +1,100 @@
#!/bin/sh /etc/rc.common
USE_PROCD=1
# after network
START=21
SCRIPT=$(readlink /etc/init.d/zapret)
if [ -n "$SCRIPT" ]; then
EXEDIR=$(dirname "$SCRIPT")
ZAPRET_BASE=$(readlink -f "$EXEDIR/../..")
else
ZAPRET_BASE=/opt/zapret
fi
. "$ZAPRET_BASE/init.d/openwrt/functions"
# !!!!! in openwrt firewall rules are configured separately
PIDDIR=/var/run
QNUM=200
NFQWS_USER=daemon
NFQWS="$ZAPRET_BASE/nfq/nfqws"
NFQWS_OPT_BASE="--qnum=$QNUM --user=$NFQWS_USER"
TPWS_USER=daemon
TPPORT=988
TPWS="$ZAPRET_BASE/tpws/tpws"
TPWS_LOCALHOST4=127.0.0.127
HOSTLIST="$ZAPRET_BASE/ipset/zapret-hosts.txt.gz"
[ -f "$HOSTLIST" ] || HOSTLIST="$ZAPRET_BASE/ipset/zapret-hosts.txt"
[ -f "$HOSTLIST" ] || HOSTLIST="$ZAPRET_BASE/ipset/zapret-hosts-user.txt"
TPWS_OPT_BASE="--user=$TPWS_USER --port=$TPPORT"
TPWS_OPT_BASE4="--bind-addr=$TPWS_LOCALHOST4"
TPWS_OPT_BASE6="--bind-addr=::1"
# first wait for lan to ifup, then wait for bind-wait-ip-linklocal seconds for link local address and bind-wait-ip for any ipv6 as the worst case
TPWS_OPT_BASE6_PRE="--bind-linklocal=prefer --bind-wait-ifup=30 --bind-wait-ip=30 --bind-wait-ip-linklocal=3"
run_daemon()
{
# $1 - daemon string id or number. can use 1,2,3,...
# $2 - daemon
# $3 - daemon args
# use $PIDDIR/$DAEMONBASE$1.pid as pidfile
local DAEMONBASE=$(basename $2)
echo "Starting daemon $1: $2 $3"
procd_open_instance
procd_set_param command $2 $3
procd_set_param pidfile $PIDDIR/$DAEMONBASE$1.pid
procd_close_instance
}
run_tpws()
{
[ "$DISABLE_IPV4" = "1" ] && [ "$DISABLE_IPV6" = "1" ] && return 0
local OPT="$TPWS_OPT_BASE"
local DEVICE
[ "$DISABLE_IPV4" = "1" ] || OPT="$OPT $TPWS_OPT_BASE4"
[ "$DISABLE_IPV6" = "1" ] || {
OPT="$OPT $TPWS_OPT_BASE6"
network_get_device DEVICE lan
[ -n "$DEVICE" ] && OPT="$OPT --bind-iface6=$DEVICE $TPWS_OPT_BASE6_PRE"
}
run_daemon $1 $TPWS "$OPT $2"
}
stop_tpws()
{
stop_daemon $1 $TPWS
}
filter_apply_hostlist_target()
{
# $1 - var name of tpws or nfqws params
[ "$MODE_FILTER" = "hostlist" ] && eval $1="\"\$$1 --hostlist=$HOSTLIST\""
}
start_service() {
local opt
case "${MODE}" in
tpws)
opt="$TPWS_OPT"
filter_apply_hostlist_target opt
run_tpws 1 "$opt"
;;
nfqws)
opt="$NFQWS_OPT_BASE $NFQWS_OPT_DESYNC"
filter_apply_hostlist_target opt
run_daemon 1 $NFQWS "$opt"
;;
custom)
existf zapret_custom_daemons && zapret_custom_daemons $1
;;
esac
}

View File

@ -0,0 +1,13 @@
[Unit]
Description=zapret ip/host list update
[Service]
Restart=no
IgnoreSIGPIPE=no
KillMode=control-group
GuessMainPID=no
RemainAfterExit=no
ExecStart=/opt/zapret/ipset/get_config.sh
[Install]
WantedBy=multi-user.target

View File

@ -0,0 +1,11 @@
[Unit]
Description=zapret ip/host list update timer
[Timer]
OnCalendar=*-*-2,4,6,8,10,12,14,16,18,20,22,24,26,28,30 00:00:00
RandomizedDelaySec=86400
Persistent=true
Unit=zapret-list-update.service
[Install]
WantedBy=timers.target

View File

@ -0,0 +1,17 @@
[Unit]
After=network-online.target
Wants=network-online.target
[Service]
Type=forking
Restart=no
TimeoutSec=30sec
IgnoreSIGPIPE=no
KillMode=none
GuessMainPID=no
RemainAfterExit=no
ExecStart=/opt/zapret/init.d/sysv/zapret start
ExecStop=/opt/zapret/init.d/sysv/zapret stop
[Install]
WantedBy=multi-user.target

24
init.d/sysv/custom Normal file
View File

@ -0,0 +1,24 @@
# this script contain your special code to launch daemons and configure firewall
# use helpers from "functions" file
# in case of upgrade keep this file only, do not modify others
zapret_custom_daemons()
{
# $1 - 1 - run, 0 - stop
# PLACEHOLDER
echo !!! NEED ATTENTION !!!
echo Start daemon\(s\)
echo Study how other sections work
do_daemon $1 1 /bin/sleep 20
}
zapret_custom_firewall()
{
# $1 - 1 - run, 0 - stop
# PLACEHOLDER
echo !!! NEED ATTENTION !!!
echo Configure iptables for required actions
echo Study how other sections work
}

44
init.d/sysv/custom-2nfqws Normal file
View File

@ -0,0 +1,44 @@
# this custom script demonstrates how to use 2 copies of nfqws
# it preserves config settings : MODE_HTTP, MODE_HTTP_KEEPALIVE, MODE_HTTPS, MODE_FILTER, NFQWS_OPT_DESYNC
# NFQWS_OPT_DESYNC - parameters for http
# NFQWS_OPT_DESYNC2 - parameters for https. you should add this variable to config file, its absent there
QNUM2=$(($QNUM+1))
zapret_custom_daemons()
{
local opt
[ "$MODE_HTTP" = "1" ] && {
opt="$NFQWS_OPT_DESYNC"
filter_apply_hostlist_target opt
do_nfqws $1 1 "$opt"
}
[ "$MODE_HTTPS" = "1" ] && {
opt="$NFQWS_OPT_DESYNC2 --qnum=$QNUM2"
filter_apply_hostlist_target opt
do_nfqws $1 2 "$opt"
}
}
zapret_custom_firewall()
{
local f4 f6
local first_packet_only="-m connbytes --connbytes-dir=original --connbytes-mode=packets --connbytes 2:4"
local desync="-m mark ! --mark $DESYNC_MARK/$DESYNC_MARK"
[ "$MODE_HTTP" = "1" ] && {
f4="--dport 80"
[ "$MODE_HTTP_KEEPALIVE" = "1" ] || f4="$f4 $first_packet_only"
f6=$f4
filter_apply_ipset_target f4 f6
fw_nfqws_post $1 "$f4 $desync" "$f6 $desync" $QNUM
}
[ "$MODE_HTTPS" = "1" ] && {
f4="--dport 443 $first_packet_only"
f6=$f4
filter_apply_ipset_target f4 f6
fw_nfqws_post $1 "$f4 $desync" "$f6 $desync" $QNUM2
}
}

View File

@ -0,0 +1,39 @@
# this custom script demonstrates how to apply tpws to http and nfqws to https
# it preserves config settings : MODE_HTTP, MODE_HTTPS, MODE_FILTER, TPWS_OPT, NFQWS_OPT_DESYNC
zapret_custom_daemons()
{
local opt
[ "$MODE_HTTP" = "1" ] && {
opt="$TPWS_OPT"
filter_apply_hostlist_target opt
do_tpws $1 1 "$opt"
}
[ "$MODE_HTTPS" = "1" ] && {
opt="$NFQWS_OPT_DESYNC"
filter_apply_hostlist_target opt
do_nfqws $1 2 "$opt"
}
}
zapret_custom_firewall()
{
local f4 f6
local first_packet_only="-m connbytes --connbytes-dir=original --connbytes-mode=packets --connbytes 2:4"
local desync="-m mark ! --mark $DESYNC_MARK/$DESYNC_MARK"
[ "$MODE_HTTP" = "1" ] && {
f4="--dport 80"
f6=$f4
filter_apply_ipset_target f4 f6
fw_tpws $1 "$f4" "$f6" $TPPORT
}
[ "$MODE_HTTPS" = "1" ] && {
f4="--dport 443 $first_packet_only"
f6=$f4
filter_apply_ipset_target f4 f6
fw_nfqws_post $1 "$f4 $desync" "$f6 $desync" $QNUM
}
}

532
init.d/sysv/functions Normal file
View File

@ -0,0 +1,532 @@
# init script functions library for desktop linux systems
[ -n "$ZAPRET_BASE" ] || ZAPRET_BASE=/opt/zapret
# SHOULD EDIT config
. "$ZAPRET_BASE/config"
PIDDIR=/var/run
IPSET_CR="$ZAPRET_BASE/ipset/create_ipset.sh"
WS_USER=tpws
QNUM=200
NFQWS="$ZAPRET_BASE/nfq/nfqws"
NFQWS_OPT_BASE="--qnum=$QNUM --user=$WS_USER"
TPPORT=988
TPWS="$ZAPRET_BASE/tpws/tpws"
TPWS_LOCALHOST4=127.0.0.127
HOSTLIST="$ZAPRET_BASE/ipset/zapret-hosts.txt.gz"
[ -f "$HOSTLIST" ] || HOSTLIST="$ZAPRET_BASE/ipset/zapret-hosts.txt"
[ -f "$HOSTLIST" ] || HOSTLIST="$ZAPRET_BASE/ipset/zapret-hosts-user.txt"
TPWS_OPT_BASE="--user=$WS_USER --port=$TPPORT"
TPWS_OPT_BASE4="--bind-addr=$TPWS_LOCALHOST4"
TPWS_OPT_BASE6="--bind-addr=::1"
# first wait for lan to ifup, then wait for bind-wait-ip-linklocal seconds for link local address and bind-wait-ip for any ipv6 as the worst case
TPWS_OPT_BASE6_PRE="--bind-linklocal=prefer --bind-wait-ifup=30 --bind-wait-ip=30 --bind-wait-ip-linklocal=3"
[ -n "$IFACE_WAN" ] && IPT_OWAN="-o $IFACE_WAN"
[ -n "$IFACE_WAN" ] && IPT_IWAN="-i $IFACE_WAN"
[ -n "$IFACE_LAN" ] && IPT_ILAN="-i $IFACE_LAN"
[ -n "$DESYNC_MARK" ] || DESYNC_MARK=0x40000000
# max wait time for the link local ipv6 on the LAN interface
LINKLOCAL_WAIT_SEC=5
CUSTOM_SCRIPT="$ZAPRET_BASE/init.d/sysv/custom"
[ -f "$CUSTOM_SCRIPT" ] && . "$CUSTOM_SCRIPT"
IPSET_EXCLUDE="-m set ! --match-set nozapret"
IPSET_EXCLUDE6="-m set ! --match-set nozapret6"
exists()
{
which "$1" >/dev/null 2>/dev/null
}
existf()
{
type "$1" >/dev/null 2>/dev/null
}
on_off_function()
{
# $1 : function name on
# $2 : function name off
# $3 : 0 - off, 1 - on
local F="$1"
[ "$3" = "1" ] || F="$2"
shift
shift
shift
"$F" "$@"
}
ipt()
{
iptables -C "$@" 2>/dev/null || iptables -I "$@"
}
ipt_del()
{
iptables -C "$@" 2>/dev/null && iptables -D "$@"
}
ipt_add_del()
{
on_off_function ipt ipt_del "$@"
}
ipt6()
{
ip6tables -C "$@" 2>/dev/null || ip6tables -I "$@"
}
ipt6_del()
{
ip6tables -C "$@" 2>/dev/null && ip6tables -D "$@"
}
ipt6_add_del()
{
on_off_function ipt6 ipt6_del "$@"
}
# there's no route_localnet for ipv6
# the best we can is to route to link local of the incoming interface
# OUTPUT - can DNAT to ::1
# PREROUTING - can't DNAT to ::1. can DNAT to link local of -i interface or to any global addr
# not a good idea to expose tpws to the world (bind to ::)
get_ipv6_linklocal()
{
# $1 - interface name. if empty - any interface
local dev
[ -n "$1" ] && dev="dev $1"
ip addr show $dev | sed -e 's/^.*inet6 \([^ ]*\)\/[0-9]* scope link.*$/\1/;t;d' | head -n 1
}
get_ipv6_global()
{
# $1 - interface name. if empty - any interface
local dev
[ -n "$1" ] && dev="dev $1"
ip addr show $dev | sed -e 's/^.*inet6 \([^ ]*\)\/[0-9]* scope global.*$/\1/;t;d' | head -n 1
}
iface_is_up()
{
# $1 - interface name
[ -f /sys/class/net/$1/operstate ] || return
local state
read state </sys/class/net/$1/operstate
[ "$state" != "down" ]
}
wait_ifup()
{
# $1 - interface name
local ct=0
while
iface_is_up $1 && return
[ "$ct" -ge "$IFUP_WAIT_SEC" ] && break
echo waiting for ifup of $1 for another $(($IFUP_WAIT_SEC - $ct)) seconds ...
ct=$(($ct+1))
sleep 1
do :; done
false
}
dnat6_target()
{
# get target ip address for DNAT. prefer link locals
# tpws should be as inaccessible from outside as possible
# link local address can appear not immediately after ifup
# DNAT6_TARGET=- means attempt was made but address was not found (to avoid multiple re-attempts)
[ -n "$DNAT6_TARGET" ] || {
local ct=0
while
DNAT6_TARGET=$(get_ipv6_linklocal $IFACE_LAN)
[ -n "$DNAT6_TARGET" ] && break
[ "$ct" -ge "$LINKLOCAL_WAIT_SEC" ] && break
echo waiting for the link local for another $(($LINKLOCAL_WAIT_SEC - $ct)) seconds ...
ct=$(($ct+1))
sleep 1
do :; done
[ -n "$DNAT6_TARGET" ] || {
echo no link local. getting global
DNAT6_TARGET=$(get_ipv6_global $IFACE_LAN)
[ -n "$DNAT6_TARGET" ] || {
echo could not get any address
DNAT6_TARGET=-
}
}
}
}
prepare_tpws_fw4()
{
# otherwise linux kernel will treat 127.0.0.0/8 as "martian" ip and refuse routing to it
# NOTE : kernels <3.6 do not have this feature. consider upgrading or change DNAT to REDIRECT and do not bind to 127.0.0.0/8
[ -n "$IFACE_LAN" ] && {
iptables -C INPUT -i $IFACE_LAN -j input_lan_rule_zapret 2>/dev/null || {
# allow localnet route only to special tpws IP
iptables -N input_lan_rule_zapret 2>/dev/null
iptables -F input_lan_rule_zapret
iptables -A input_lan_rule_zapret -d $TPWS_LOCALHOST4 -j RETURN
iptables -A input_lan_rule_zapret -d 127.0.0.0/8 -j DROP
iptables -I INPUT -i $IFACE_LAN -j input_lan_rule_zapret
}
sysctl -qw net.ipv4.conf.$IFACE_LAN.route_localnet=1
}
}
unprepare_tpws_fw4()
{
[ -n "$IFACE_LAN" ] && {
iptables -C INPUT -i $IFACE_LAN -j input_lan_rule_zapret 2>/dev/null && {
sysctl -qw net.ipv4.conf.$IFACE_LAN.route_localnet=0
iptables -D INPUT -i $IFACE_LAN -j input_lan_rule_zapret
iptables -F input_lan_rule_zapret
iptables -X input_lan_rule_zapret
}
}
}
unprepare_tpws_fw()
{
unprepare_tpws_fw4
}
print_op()
{
if [ "$1" = "1" ]; then
echo "Adding ip$4tables rule for $3 : $2"
else
echo "Deleting ip$4tables rule for $3 : $2"
fi
}
fw_tpws4()
{
# $1 - 1 - add, 0 - del
# $2 - iptable filter for ipv4
# $3 - tpws port
[ "$DISABLE_IPV4" = "1" ] || {
[ "$1" = 1 ] && prepare_tpws_fw4
print_op $1 "$2" "tpws"
[ -n "$IFACE_LAN" ] && {
ipt_add_del $1 PREROUTING -t nat $IPT_ILAN -p tcp $2 $IPSET_EXCLUDE dst -j DNAT --to $TPWS_LOCALHOST4:$3
}
ipt_add_del $1 OUTPUT -t nat $IPT_OWAN -m owner ! --uid-owner $WS_USER -p tcp $2 $IPSET_EXCLUDE dst -j DNAT --to $TPWS_LOCALHOST4:$3
}
}
fw_tpws6()
{
# $1 - 1 - add, 0 - del
# $2 - iptable filter for ipv6
# $3 - tpws port
[ "$DISABLE_IPV6" = "1" ] || {
print_op $1 "$2" "tpws" 6
[ -n "$IFACE_LAN" ] && {
dnat6_target
[ "$DNAT6_TARGET" != "-" ] && ipt6_add_del $1 PREROUTING -t nat $IPT_ILAN -p tcp $2 $IPSET_EXCLUDE6 dst -j DNAT --to [$DNAT6_TARGET]:$3
}
ipt6_add_del $1 OUTPUT -t nat $IPT_OWAN -m owner ! --uid-owner $WS_USER -p tcp $2 $IPSET_EXCLUDE6 dst -j DNAT --to [::1]:$3
}
}
fw_tpws()
{
# $1 - 1 - add, 0 - del
# $2 - iptable filter for ipv4
# $3 - iptable filter for ipv6
# $4 - tpws port
fw_tpws4 $1 "$2" $4
fw_tpws6 $1 "$3" $4
}
fw_nfqws_pre4()
{
# $1 - 1 - add, 0 - del
# $2 - iptable filter for ipv4
# $3 - queue number
[ "$DISABLE_IPV4" = "1" ] || {
print_op $1 "$2" "nfqws prerouting"
ipt_add_del $1 PREROUTING -t mangle $IPT_IWAN -p tcp $2 $IPSET_EXCLUDE src -j NFQUEUE --queue-num $3 --queue-bypass
}
}
fw_nfqws_pre6()
{
# $1 - 1 - add, 0 - del
# $2 - iptable filter for ipv6
# $3 - queue number
[ "$DISABLE_IPV6" = "1" ] || {
print_op $1 "$2" "nfqws prerouting" 6
ipt6_add_del $1 PREROUTING -t mangle $IPT_IWAN -p tcp $2 $IPSET_EXCLUDE6 src -j NFQUEUE --queue-num $3 --queue-bypass
}
}
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
}
fw_nfqws_post4()
{
# $1 - 1 - add, 0 - del
# $2 - iptable filter for ipv4
# $3 - queue number
[ "$DISABLE_IPV4" = "1" ] || {
print_op $1 "$2" "nfqws postrouting"
ipt_add_del $1 POSTROUTING -t mangle $IPT_OWAN -p tcp $2 $IPSET_EXCLUDE dst -j NFQUEUE --queue-num $3 --queue-bypass
}
}
fw_nfqws_post6()
{
# $1 - 1 - add, 0 - del
# $2 - iptable filter for ipv6
# $3 - queue number
[ "$DISABLE_IPV6" = "1" ] || {
print_op $1 "$2" "nfqws postrouting" 6
ipt6_add_del $1 POSTROUTING -t mangle $IPT_OWAN -p tcp $2 $IPSET_EXCLUDE6 dst -j NFQUEUE --queue-num $3 --queue-bypass
}
}
fw_nfqws_post()
{
# $1 - 1 - add, 0 - del
# $2 - iptable filter for ipv4
# $3 - iptable filter for ipv6
# $4 - queue number
fw_nfqws_post4 $1 "$2" $4
fw_nfqws_post6 $1 "$3" $4
}
run_daemon()
{
# $1 - daemon number : 1,2,3,...
# $2 - daemon
# $3 - daemon args
# use $PIDDIR/$DAEMONBASE$1.pid as pidfile
local DAEMONBASE=$(basename $2)
local PIDFILE=$PIDDIR/$DAEMONBASE$1.pid
echo "Starting daemon $1: $2 $3"
if exists start-stop-daemon ; then
start-stop-daemon --start --pidfile "$PIDFILE" --background --make-pidfile --exec "$2" -- $3
else
if [ -f "$PIDFILE" ] && pgrep -F "$PIDFILE" "$DAEMONBASE" >/dev/null; then
echo already running
else
"$2" $3 >/dev/null 2>/dev/null &
PID=$!
if [ -n "$PID" ]; then
echo $PID >$PIDFILE
else
echo could not start daemon $1 : $2 $3
false
fi
fi
fi
}
stop_daemon()
{
# $1 - daemon number : 1,2,3,...
# $2 - daemon
# use $PIDDIR/$DAEMONBASE$1.pid as pidfile
local DAEMONBASE=$(basename $2)
local PIDFILE=$PIDDIR/$DAEMONBASE$1.pid
echo "Stopping daemon $1: $2"
if exists start-stop-daemon ; then
start-stop-daemon --stop --pidfile "$PIDFILE" --exec "$2"
else
if [ -f "$PIDFILE" ]; then
read PID <"$PIDFILE"
kill $PID
rm -f "$PIDFILE"
else
echo no pidfile : $PIDFILE
fi
fi
}
do_daemon()
{
# $1 - 1 - run, 0 - stop
on_off_function run_daemon stop_daemon "$@"
}
prepare_user()
{
# $WS_USER is required to prevent redirection of the traffic originating from TPWS itself
# otherwise infinite loop will occur
# also its good idea not to run tpws as root
id -u $WS_USER >/dev/null 2>/dev/null || useradd --no-create-home --system --shell /bin/false $WS_USER
}
do_tpws()
{
# $1 : 1 - run, 0 - stop
# $2 : daemon number
# $3 : daemon args
[ "$1" = "1" ] && prepare_user
[ "$DISABLE_IPV4" = "1" ] && [ "$DISABLE_IPV6" = "1" ] && return 0
local OPT="$TPWS_OPT_BASE"
[ "$DISABLE_IPV4" = "1" ] || OPT="$OPT $TPWS_OPT_BASE4"
[ "$DISABLE_IPV6" = "1" ] || {
OPT="$OPT $TPWS_OPT_BASE6"
[ -n "$IFACE_LAN" ] && OPT="$OPT --bind-iface6=$IFACE_LAN $TPWS_OPT_BASE6_PRE"
}
do_daemon $1 $2 $TPWS "$OPT $3"
}
do_nfqws()
{
# $1 : 1 - run, 0 - stop
# $2 : daemon number
# $3 : daemon args
[ "$1" = "1" ] && prepare_user
do_daemon $1 $2 $NFQWS "$NFQWS_OPT_BASE $3"
}
filter_apply_port_target()
{
# $1 - var name of iptables filter
local f
if [ "$MODE_HTTP" = "1" ] && [ "$MODE_HTTPS" = "1" ]; then
f="-m multiport --dports 80,443"
elif [ "$MODE_HTTPS" = "1" ]; then
f="--dport 443"
elif [ "$MODE_HTTP" = "1" ]; then
f="--dport 80"
else
echo WARNING !!! HTTP and HTTPS are both disabled
fi
eval $1="\"\$$1 $f\""
}
filter_apply_ipset_target()
{
# $1 - var name of ipv4 iptables filter
# $2 - var name of ipv6 iptables filter
if [ "$MODE_FILTER" = "ipset" ]; then
eval $1="\"\$$1 -m set --match-set zapret dst\""
eval $2="\"\$$2 -m set --match-set zapret6 dst\""
fi
}
filter_apply_hostlist_target()
{
# $1 - var name of tpws or nfqws params
[ "$MODE_FILTER" = "hostlist" ] && eval $1="\"\$$1 --hostlist=$HOSTLIST\""
}
create_ipset()
{
echo "Creating ipset"
"$IPSET_CR" "$@"
}
zapret_do_firewall()
{
# $1 - 1 - add, 0 - del
local first_packet_only="-m connbytes --connbytes-dir=original --connbytes-mode=packets --connbytes 2:4"
local desync="-m mark ! --mark $DESYNC_MARK/$DESYNC_MARK"
local f4 f6
# always create ipsets. ip_exclude ipset is required
[ "$1" != "1" ] || create_ipset no-update
case "${MODE}" in
tpws)
if [ ! "$MODE_HTTP" = "1" ] && [ ! "$MODE_HTTPS" = "1" ]; then
echo both http and https are disabled. not applying redirection.
else
filter_apply_port_target f4
f6=$f4
filter_apply_ipset_target f4 f6
fw_tpws $1 "$f4" "$f6" $TPPORT
fi
;;
nfqws)
if [ ! "$MODE_HTTP" = "1" ] && [ ! "$MODE_HTTPS" = "1" ]; then
echo both http and https are disabled. not applying redirection.
else
if [ "$MODE_HTTP_KEEPALIVE" = "1" ]; then
if [ "$MODE_HTTP" = "1" ]; then
f4="--dport 80"
f6=$f4
filter_apply_ipset_target f4 f6
fw_nfqws_post $1 "$f4 $desync" "$f6 $desync" $QNUM
fi
if [ "$MODE_HTTPS" = "1" ]; then
f4="--dport 443 $first_packet_only"
f6=$f4
filter_apply_ipset_target f4 f6
fw_nfqws_post $1 "$f4 $desync" "$f6 $desync" $QNUM
fi
else
filter_apply_port_target f4
f4="$f4 $first_packet_only"
f6=$f4
filter_apply_ipset_target f4 f6
fw_nfqws_post $1 "$f4 $desync" "$f6 $desync" $QNUM
fi
fi
;;
custom)
existf zapret_custom_firewall && zapret_custom_firewall $1
;;
esac
[ "$1" = 0 ] && unprepare_tpws_fw
}
zapret_apply_firewall()
{
zapret_do_firewall 1 "$@"
}
zapret_unapply_firewall()
{
zapret_do_firewall 0 "$@"
}
zapret_do_daemons()
{
# $1 - 1 - run, 0 - stop
local opt
case "${MODE}" in
tpws)
opt="$TPWS_OPT"
filter_apply_hostlist_target opt
do_tpws $1 1 "$opt"
;;
nfqws)
opt="$NFQWS_OPT_DESYNC"
filter_apply_hostlist_target opt
do_nfqws $1 1 "$opt"
;;
custom)
existf zapret_custom_daemons && zapret_custom_daemons $1
;;
esac
}
zapret_run_daemons()
{
zapret_do_daemons 1 "$@"
}
zapret_stop_daemons()
{
zapret_do_daemons 0 "$@"
}

50
init.d/sysv/zapret Executable file
View File

@ -0,0 +1,50 @@
#!/bin/sh
### BEGIN INIT INFO
# Provides: zapret
# Required-Start: $local_fs $network
# Required-Stop: $local_fs $network
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
### END INIT INFO
SCRIPT=$(readlink -f "$0")
EXEDIR=$(dirname "$SCRIPT")
ZAPRET_BASE=$(readlink -f "$EXEDIR/../..")
. "$EXEDIR/functions"
NAME=zapret
DESC=anti-zapret
case "$1" in
start)
zapret_run_daemons
[ "$INIT_APPLY_FW" != "1" ] || zapret_apply_firewall
;;
stop)
zapret_stop_daemons
[ "$INIT_APPLY_FW" != "1" ] || zapret_unapply_firewall
;;
start-fw)
zapret_apply_firewall
;;
stop-fw)
zapret_unapply_firewall
;;
start-daemons)
zapret_run_daemons
;;
stop-daemons)
zapret_stop_daemons
;;
*)
N=/etc/init.d/$NAME
echo "Usage: $N {start|stop|start-fw|stop-fw|start-daemons|stop-daemons}" >&2
exit 1
;;
esac
exit 0

75
install_bin.sh Executable file
View File

@ -0,0 +1,75 @@
#!/bin/sh
EXEDIR="$(dirname "$0")"
EXEDIR="$(cd "$EXEDIR"; pwd)"
BINS=binaries
BINDIR="$EXEDIR/$BINS"
check_dir()
{
local exe=$BINDIR/$1/ip2net
if [ -f "$exe" ]; then
if [ -x "$exe" ]; then
echo 0.0.0.0 | "$exe" 1>/dev/null 2>/dev/null
else
echo "$exe is not executable. set proper chmod."
return 1
fi
else
echo "$exe is absent"
return 2
fi
}
# link or copy executables. uncomment either ln or cp, comment other
ccp()
{
local F=$(basename $1)
[ -d "$EXEDIR/$2" ] || mkdir "$EXEDIR/$2"
[ -f "$EXEDIR/$2/$F" ] && rm -f "$EXEDIR/$2/$F"
ln -fs "../$BINS/$1" "$EXEDIR/$2" && echo linking : "../$BINS/$1" =\> "$EXEDIR/$2"
#cp -f "$BINDIR/$1" "$EXEDIR/$2" && echo copying : "$BINDIR/$1" =\> "$EXEDIR/$2"
}
UNAME=$(uname)
if [ "$UNAME" = "Linux" ]; then
ARCHLIST="my x86_64 x86 aarch64 arm mips64r2-msb mips32r1-lsb mips32r1-msb ppc"
elif [ "$UNAME" = "Darwin" ]; then
ARCHLIST="my mac64"
else
ARCHLIST="my"
fi
if [ "$1" = "getarch" ]; then
for arch in $ARCHLIST
do
[ -d "$BINDIR/$arch" ] || continue
if check_dir $arch; then
echo $arch
exit 0
fi
done
else
for arch in $ARCHLIST
do
[ -d "$BINDIR/$arch" ] || continue
if check_dir $arch; then
echo $arch is OK
echo installing binaries ...
ccp $arch/ip2net ip2net
ccp $arch/mdig mdig
if [ "$(uname)" = "Linux" ]; then
ccp $arch/nfqws nfq
else
ccp $arch/dvtws nfq
fi
ccp $arch/tpws tpws
exit 0
else
echo $arch is NOT OK
fi
done
echo no compatible binaries found
fi
exit 1

1235
install_easy.sh Executable file

File diff suppressed because it is too large Load Diff

17
ip2net/Makefile Normal file
View File

@ -0,0 +1,17 @@
CC ?= gcc
CFLAGS += -std=gnu99 -s -O3
CFLAGS_BSD = -Wno-address-of-packed-member -Wno-logical-op-parentheses -Wno-switch
LIBS =
SRC_FILES = *.c
all: ip2net
mac: bsd
ip2net: $(SRC_FILES)
$(CC) $(CFLAGS) -o $@ $(SRC_FILES) $(LDFLAGS) $(LIBS)
bsd: $(SRC_FILES)
$(CC) $(CFLAGS) $(CFLAGS_BSD) -o ip2net $(SRC_FILES) $(LDFLAGS) $(LIBS)
clean:
rm -f ip2net *.o

1
ip2net/ip2net Symbolic link
View File

@ -0,0 +1 @@
../binaries/x86_64/ip2net

397
ip2net/ip2net.c Normal file
View File

@ -0,0 +1,397 @@
// group ipv4/ipv6 list from stdout into subnets
// each line must contain either ip or ip/bitcount
// valid ip/bitcount and ip1-ip2 are passed through without modification
// ips are groupped into subnets
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <getopt.h>
#include "qsort.h"
#define ALLOC_STEP 16384
// minimum subnet fill percent is PCTMULT/PCTDIV (for example 3/4)
#define DEFAULT_PCTMULT 3
#define DEFAULT_PCTDIV 4
// subnet search range in "zero bit count"
// means search start from /(32-ZCT_MAX) to /(32-ZCT_MIN)
#define DEFAULT_V4_ZCT_MAX 10 // /22
#define DEFAULT_V4_ZCT_MIN 2 // /30
#define DEFAULT_V6_ZCT_MAX 72 // /56
#define DEFAULT_V6_ZCT_MIN 64 // /64
// must be no less than N ipv6 in subnet
#define DEFAULT_V6_THRESHOLD 5
static int ucmp(const void * a, const void * b, void *arg)
{
if (*(uint32_t*)a < *(uint32_t*)b)
return -1;
else if (*(uint32_t*)a > *(uint32_t*)b)
return 1;
else
return 0;
}
static uint32_t mask_from_bitcount(uint32_t zct)
{
return ~((1 << zct) - 1);
}
// make presorted array unique. return number of unique items.
// 1,1,2,3,3,0,0,0 (ct=8) => 1,2,3,0 (ct=4)
static uint32_t unique(uint32_t *pu, uint32_t ct)
{
uint32_t i, j, u;
for (i = j = 0; j < ct; i++)
{
u = pu[j++];
for (; j < ct && pu[j] == u; j++);
pu[i] = u;
}
return i;
}
static int cmp6(const void * a, const void * b, void *arg)
{
for (uint8_t i = 0; i < sizeof(((struct in6_addr *)0)->s6_addr); i++)
{
if (((struct in6_addr *)a)->s6_addr[i] < ((struct in6_addr *)b)->s6_addr[i])
return -1;
else if (((struct in6_addr *)a)->s6_addr[i] > ((struct in6_addr *)b)->s6_addr[i])
return 1;
}
return 0;
}
// make presorted array unique. return number of unique items.
static uint32_t unique6(struct in6_addr *pu, uint32_t ct)
{
uint32_t i, j, k;
for (i = j = 0; j < ct; i++)
{
for (k = j++; j < ct && !memcmp(pu + j, pu + k, sizeof(struct in6_addr)); j++);
pu[i] = pu[k];
}
return i;
}
static void mask_from_bitcount6(uint32_t zct, struct in6_addr *a)
{
if (zct >= 128)
memset(a->s6_addr,0x00,16);
else
{
int32_t n = (127 - zct) >> 3;
memset(a->s6_addr,0xFF,n);
memset(a->s6_addr+n,0x00,16-n);
a->s6_addr[n] = ~((1 << (zct & 7)) - 1);
}
}
// result = a & b
static void ip6_and(const struct in6_addr *a, const struct in6_addr *b, struct in6_addr *result)
{
((uint64_t*)result->s6_addr)[0] = ((uint64_t*)a->s6_addr)[0] & ((uint64_t*)b->s6_addr)[0];
((uint64_t*)result->s6_addr)[1] = ((uint64_t*)a->s6_addr)[1] & ((uint64_t*)b->s6_addr)[1];
}
static void rtrim(char *s)
{
if (s)
for (char *p = s + strlen(s) - 1; p >= s && (*p == '\n' || *p == '\r'); p--) *p = '\0';
}
static struct params_s
{
bool ipv6;
uint32_t pctmult, pctdiv; // for v4
uint32_t zct_min, zct_max; // for v4 and v6
uint32_t v6_threshold; // for v6
} params;
static void exithelp()
{
printf(
" -4\t\t\t\t; ipv4 list (default)\n"
" -6\t\t\t\t; ipv6 list\n"
" --prefix-length=min[-max]\t; consider prefix lengths from 'min' to 'max'. examples : 22-30 (ipv4), 56-64 (ipv6)\n"
" --v4-threshold=mul/div\t\t; ipv4 only : include subnets with more than mul/div ips. example : 3/4\n"
" --v6-threshold=N\t\t; ipv6 only : include subnets with more than N v6 ips. example : 5\n"
);
exit(1);
}
static void parse_params(int argc, char *argv[])
{
int option_index = 0;
int v, i;
uint32_t plen1=-1, plen2=-1;
memset(&params, 0, sizeof(params));
params.pctmult = DEFAULT_PCTMULT;
params.pctdiv = DEFAULT_PCTDIV;
params.v6_threshold = DEFAULT_V6_THRESHOLD;
const struct option long_options[] = {
{ "help",no_argument,0,0 },// optidx=0
{ "h",no_argument,0,0 },// optidx=1
{ "4",no_argument,0,0 },// optidx=2
{ "6",no_argument,0,0 },// optidx=3
{ "prefix-length",required_argument,0,0 },// optidx=4
{ "v4-threshold",required_argument,0,0 },// optidx=5
{ "v6-threshold",required_argument,0,0 },// optidx=6
{ NULL,0,NULL,0 }
};
while ((v = getopt_long_only(argc, argv, "", long_options, &option_index)) != -1)
{
if (v) exithelp();
switch (option_index)
{
case 0:
case 1:
exithelp();
break;
case 2:
params.ipv6 = false;
break;
case 3:
params.ipv6 = true;
break;
case 4:
i = sscanf(optarg,"%u-%u",&plen1,&plen2);
if (i == 1) plen2 = plen1;
if (!i || plen2<plen1 || !plen1 || !plen2)
{
fprintf(stderr, "invalid parameter for prefix-length : %s\n", optarg);
exit(1);
}
break;
case 5:
i = sscanf(optarg, "%u/%u", &params.pctmult, &params.pctdiv);
if (i!=2 || params.pctdiv<2 || params.pctmult<1 || params.pctmult>=params.pctdiv)
{
fprintf(stderr, "invalid parameter for v4-threshold : %s\n", optarg);
exit(1);
}
break;
case 6:
i = sscanf(optarg, "%u", &params.v6_threshold);
if (i != 1 || params.v6_threshold<1)
{
fprintf(stderr, "invalid parameter for v6-threshold : %s\n", optarg);
exit(1);
}
break;
}
}
if (plen1 != -1 && (!params.ipv6 && (plen1>31 || plen2>31) || params.ipv6 && (plen1>127 || plen2>127)))
{
fprintf(stderr, "invalid parameter for prefix-length\n");
exit(1);
}
params.zct_min = params.ipv6 ? plen2==-1 ? DEFAULT_V6_ZCT_MIN : 128-plen2 : plen2==-1 ? DEFAULT_V4_ZCT_MIN : 32-plen2;
params.zct_max = params.ipv6 ? plen1==-1 ? DEFAULT_V6_ZCT_MAX : 128-plen1 : plen1==-1 ? DEFAULT_V4_ZCT_MAX : 32-plen1;
}
int main(int argc, char **argv)
{
char str[256],d;
uint32_t ipct = 0, iplist_size = 0, pos = 0, p, zct, ip_ct, pos_end;
parse_params(argc, argv);
if (params.ipv6) // ipv6
{
char *s;
struct in6_addr a, *iplist = NULL, *iplist_new;
while (fgets(str, sizeof(str), stdin))
{
rtrim(str);
d = 0;
if ((s = strchr(str, '/')) || (s = strchr(str, '-')))
{
d = *s;
*s = '\0';
}
if (inet_pton(AF_INET6, str, &a))
{
if (d=='/')
{
// we have subnet ip6/y
// output it as is
*s = d;
if (sscanf(s + 1, "%u", &zct) && zct!=128)
{
if (zct<128) printf("%s\n", str);
continue;
}
}
else if (d=='-')
{
*s = d;
if (inet_pton(AF_INET6, s+1, &a)) printf("%s\n", str);
continue;
}
if (ipct >= iplist_size)
{
iplist_size += ALLOC_STEP;
iplist_new = (struct in6_addr*)(iplist ? realloc(iplist, sizeof(*iplist)*iplist_size) : malloc(sizeof(*iplist)*iplist_size));
if (!iplist_new)
{
free(iplist);
fprintf(stderr, "out of memory\n");
return 100;
}
iplist = iplist_new;
}
iplist[ipct++] = a;
}
}
gnu_quicksort(iplist, ipct, sizeof(*iplist), cmp6, NULL);
ipct = unique6(iplist, ipct);
/*
for(uint32_t i=0;i<ipct;i++)
if (inet_ntop(AF_INET6,iplist+i,str,256))
printf("%s\n",str);
printf("\n");
*/
while (pos < ipct)
{
struct in6_addr mask, ip_start, ip;
uint32_t ip_ct_best = 0, zct_best = 0;
pos_end = pos + 1;
// find smallest network with maximum ip coverage with no less than ip6_subnet_threshold addresses
for (zct = params.zct_max; zct >= params.zct_min; zct--)
{
mask_from_bitcount6(zct, &mask);
ip6_and(iplist + pos, &mask, &ip_start);
for (p = pos + 1, ip_ct = 1; p < ipct; p++, ip_ct++)
{
ip6_and(iplist + p, &mask, &ip);
if (memcmp(&ip_start, &ip, sizeof(ip)))
break;
}
if (ip_ct == 1) break;
if (ip_ct >= params.v6_threshold)
{
// network found. but is there smaller network with the same ip_ct ? dont do carpet bombing if possible, use smaller subnets
if (!ip_ct_best || ip_ct == ip_ct_best)
{
ip_ct_best = ip_ct;
zct_best = zct;
pos_end = p;
}
else
break;
}
}
if (!zct_best) ip_start = iplist[pos], pos_end = pos + 1; // network not found, use single ip
inet_ntop(AF_INET6, &ip_start, str, sizeof(str));
printf(zct_best ? "%s/%u\n" : "%s\n", str, 128 - zct_best);
pos = pos_end;
}
free(iplist);
}
else // ipv4
{
uint32_t u1,u2,u3,u4, u11,u22,u33,u44, ip;
uint32_t *iplist = NULL, *iplist_new, i;
while (fgets(str, sizeof(str), stdin))
{
if ((i = sscanf(str, "%u.%u.%u.%u-%u.%u.%u.%u", &u1, &u2, &u3, &u4, &u11, &u22, &u33, &u44)) >= 8 &&
!(u1 & 0xFFFFFF00) && !(u2 & 0xFFFFFF00) && !(u3 & 0xFFFFFF00) && !(u4 & 0xFFFFFF00) &&
!(u11 & 0xFFFFFF00) && !(u22 & 0xFFFFFF00) && !(u33 & 0xFFFFFF00) && !(u44 & 0xFFFFFF00))
{
printf("%u.%u.%u.%u-%u.%u.%u.%u\n", u1, u2, u3, u4, u11, u22, u33, u44);
}
else
if ((i = sscanf(str, "%u.%u.%u.%u/%u", &u1, &u2, &u3, &u4, &zct)) >= 4 &&
!(u1 & 0xFFFFFF00) && !(u2 & 0xFFFFFF00) && !(u3 & 0xFFFFFF00) && !(u4 & 0xFFFFFF00))
{
if (i == 5 && zct != 32)
{
// we have subnet x.x.x.x/y
// output it as is if valid, ignore otherwise
if (zct < 32)
printf("%u.%u.%u.%u/%u\n", u1, u2, u3, u4, zct);
}
else
{
ip = u1 << 24 | u2 << 16 | u3 << 8 | u4;
if (ipct >= iplist_size)
{
iplist_size += ALLOC_STEP;
iplist_new = (uint32_t*)(iplist ? realloc(iplist, sizeof(*iplist)*iplist_size) : malloc(sizeof(*iplist)*iplist_size));
if (!iplist_new)
{
free(iplist);
fprintf(stderr, "out of memory\n");
return 100;
}
iplist = iplist_new;
}
iplist[ipct++] = ip;
}
}
}
gnu_quicksort(iplist, ipct, sizeof(*iplist), ucmp, NULL);
ipct = unique(iplist, ipct);
while (pos < ipct)
{
uint32_t mask, ip_start, ip_end, subnet_ct;
uint32_t ip_ct_best = 0, zct_best = 0;
// find smallest network with maximum ip coverage with no less than mul/div percent addresses
for (zct = params.zct_max; zct >= params.zct_min; zct--)
{
mask = mask_from_bitcount(zct);
ip_start = iplist[pos] & mask;
subnet_ct = ~mask + 1;
if (iplist[pos] > (ip_start + subnet_ct*(params.pctdiv - params.pctmult) / params.pctdiv))
continue; // ip is higher than (1-PCT). definitely coverage is not enough. skip searching
ip_end = ip_start | ~mask;
for (p=pos+1, ip_ct=1; p < ipct && iplist[p] <= ip_end; p++) ip_ct++; // count ips within subnet range
if (ip_ct == 1) break;
if (ip_ct >= (subnet_ct*params.pctmult / params.pctdiv))
{
// network found. but is there smaller network with the same ip_ct ? dont do carpet bombing if possible, use smaller subnets
if (!ip_ct_best || ip_ct == ip_ct_best)
{
ip_ct_best = ip_ct;
zct_best = zct;
pos_end = p;
}
else
break;
}
}
if (!zct_best) ip_start = iplist[pos], pos_end = pos + 1; // network not found, use single ip
u1 = ip_start >> 24;
u2 = (ip_start >> 16) & 0xFF;
u3 = (ip_start >> 8) & 0xFF;
u4 = ip_start & 0xFF;
printf(zct_best ? "%u.%u.%u.%u/%u\n" : "%u.%u.%u.%u\n", u1, u2, u3, u4, 32 - zct_best);
pos = pos_end;
}
free(iplist);
}
return 0;
}

250
ip2net/qsort.c Normal file
View File

@ -0,0 +1,250 @@
/* Copyright (C) 1991-2018 Free Software Foundation, Inc.
This file is part of the GNU C Library.
Written by Douglas C. Schmidt (schmidt@ics.uci.edu).
The GNU C Library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
The GNU C Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with the GNU C Library; if not, see
<http://www.gnu.org/licenses/>. */
/* If you consider tuning this algorithm, you should consult first:
Engineering a sort function; Jon Bentley and M. Douglas McIlroy;
Software - Practice and Experience; Vol. 23 (11), 1249-1265, 1993. */
//#include <alloca.h>
#include <limits.h>
#include <stdlib.h>
//#include <string.h>
#include "qsort.h"
/* Byte-wise swap two items of size SIZE. */
#define SWAP(a, b, size) \
do \
{ \
size_t __size = (size); \
char *__a = (a), *__b = (b); \
do \
{ \
char __tmp = *__a; \
*__a++ = *__b; \
*__b++ = __tmp; \
} while (--__size > 0); \
} while (0)
/* Discontinue quicksort algorithm when partition gets below this size.
This particular magic number was chosen to work best on a Sun 4/260. */
#define MAX_THRESH 4
/* Stack node declarations used to store unfulfilled partition obligations. */
typedef struct
{
char *lo;
char *hi;
} stack_node;
/* The next 4 #defines implement a very fast in-line stack abstraction. */
/* The stack needs log (total_elements) entries (we could even subtract
log(MAX_THRESH)). Since total_elements has type size_t, we get as
upper bound for log (total_elements):
bits per byte (CHAR_BIT) * sizeof(size_t). */
#define STACK_SIZE (CHAR_BIT * sizeof(size_t))
#define PUSH(low, high) ((void) ((top->lo = (low)), (top->hi = (high)), ++top))
#define POP(low, high) ((void) (--top, (low = top->lo), (high = top->hi)))
#define STACK_NOT_EMPTY (stack < top)
/* Order size using quicksort. This implementation incorporates
four optimizations discussed in Sedgewick:
1. Non-recursive, using an explicit stack of pointer that store the
next array partition to sort. To save time, this maximum amount
of space required to store an array of SIZE_MAX is allocated on the
stack. Assuming a 32-bit (64 bit) integer for size_t, this needs
only 32 * sizeof(stack_node) == 256 bytes (for 64 bit: 1024 bytes).
Pretty cheap, actually.
2. Chose the pivot element using a median-of-three decision tree.
This reduces the probability of selecting a bad pivot value and
eliminates certain extraneous comparisons.
3. Only quicksorts TOTAL_ELEMS / MAX_THRESH partitions, leaving
insertion sort to order the MAX_THRESH items within each partition.
This is a big win, since insertion sort is faster for small, mostly
sorted array segments.
4. The larger of the two sub-partitions is always pushed onto the
stack first, with the algorithm then concentrating on the
smaller partition. This *guarantees* no more than log (total_elems)
stack size is needed (actually O(1) in this case)! */
void
gnu_quicksort (void *const pbase, size_t total_elems, size_t size,
__gnu_compar_d_fn_t cmp, void *arg)
{
char *base_ptr = (char *) pbase;
const size_t max_thresh = MAX_THRESH * size;
if (total_elems == 0)
/* Avoid lossage with unsigned arithmetic below. */
return;
if (total_elems > MAX_THRESH)
{
char *lo = base_ptr;
char *hi = &lo[size * (total_elems - 1)];
stack_node stack[STACK_SIZE];
stack_node *top = stack;
PUSH (NULL, NULL);
while (STACK_NOT_EMPTY)
{
char *left_ptr;
char *right_ptr;
/* Select median value from among LO, MID, and HI. Rearrange
LO and HI so the three values are sorted. This lowers the
probability of picking a pathological pivot value and
skips a comparison for both the LEFT_PTR and RIGHT_PTR in
the while loops. */
char *mid = lo + size * ((hi - lo) / size >> 1);
if ((*cmp) ((void *) mid, (void *) lo, arg) < 0)
SWAP (mid, lo, size);
if ((*cmp) ((void *) hi, (void *) mid, arg) < 0)
SWAP (mid, hi, size);
else
goto jump_over;
if ((*cmp) ((void *) mid, (void *) lo, arg) < 0)
SWAP (mid, lo, size);
jump_over:;
left_ptr = lo + size;
right_ptr = hi - size;
/* Here's the famous ``collapse the walls'' section of quicksort.
Gotta like those tight inner loops! They are the main reason
that this algorithm runs much faster than others. */
do
{
while ((*cmp) ((void *) left_ptr, (void *) mid, arg) < 0)
left_ptr += size;
while ((*cmp) ((void *) mid, (void *) right_ptr, arg) < 0)
right_ptr -= size;
if (left_ptr < right_ptr)
{
SWAP (left_ptr, right_ptr, size);
if (mid == left_ptr)
mid = right_ptr;
else if (mid == right_ptr)
mid = left_ptr;
left_ptr += size;
right_ptr -= size;
}
else if (left_ptr == right_ptr)
{
left_ptr += size;
right_ptr -= size;
break;
}
}
while (left_ptr <= right_ptr);
/* Set up pointers for next iteration. First determine whether
left and right partitions are below the threshold size. If so,
ignore one or both. Otherwise, push the larger partition's
bounds on the stack and continue sorting the smaller one. */
if ((size_t) (right_ptr - lo) <= max_thresh)
{
if ((size_t) (hi - left_ptr) <= max_thresh)
/* Ignore both small partitions. */
POP (lo, hi);
else
/* Ignore small left partition. */
lo = left_ptr;
}
else if ((size_t) (hi - left_ptr) <= max_thresh)
/* Ignore small right partition. */
hi = right_ptr;
else if ((right_ptr - lo) > (hi - left_ptr))
{
/* Push larger left partition indices. */
PUSH (lo, right_ptr);
lo = left_ptr;
}
else
{
/* Push larger right partition indices. */
PUSH (left_ptr, hi);
hi = right_ptr;
}
}
}
/* Once the BASE_PTR array is partially sorted by quicksort the rest
is completely sorted using insertion sort, since this is efficient
for partitions below MAX_THRESH size. BASE_PTR points to the beginning
of the array to sort, and END_PTR points at the very last element in
the array (*not* one beyond it!). */
#define min(x, y) ((x) < (y) ? (x) : (y))
{
char *const end_ptr = &base_ptr[size * (total_elems - 1)];
char *tmp_ptr = base_ptr;
char *thresh = min(end_ptr, base_ptr + max_thresh);
char *run_ptr;
/* Find smallest element in first threshold and place it at the
array's beginning. This is the smallest array element,
and the operation speeds up insertion sort's inner loop. */
for (run_ptr = tmp_ptr + size; run_ptr <= thresh; run_ptr += size)
if ((*cmp) ((void *) run_ptr, (void *) tmp_ptr, arg) < 0)
tmp_ptr = run_ptr;
if (tmp_ptr != base_ptr)
SWAP (tmp_ptr, base_ptr, size);
/* Insertion sort, running from left-hand-side up to right-hand-side. */
run_ptr = base_ptr + size;
while ((run_ptr += size) <= end_ptr)
{
tmp_ptr = run_ptr - size;
while ((*cmp) ((void *) run_ptr, (void *) tmp_ptr, arg) < 0)
tmp_ptr -= size;
tmp_ptr += size;
if (tmp_ptr != run_ptr)
{
char *trav;
trav = run_ptr + size;
while (--trav >= run_ptr)
{
char c = *trav;
char *hi, *lo;
for (hi = lo = trav; (lo -= size) >= tmp_ptr; hi = lo)
*hi = *lo;
*hi = c;
}
}
}
}
}

6
ip2net/qsort.h Normal file
View File

@ -0,0 +1,6 @@
#pragma once
// GNU qsort is 2x faster than musl
typedef int (*__gnu_compar_d_fn_t) (const void *, const void *, void *);
void gnu_quicksort (void *const pbase, size_t total_elems, size_t size, __gnu_compar_d_fn_t cmp, void *arg);

19
ipset/antifilter.helper Normal file
View File

@ -0,0 +1,19 @@
get_antifilter()
{
# $1 - list url
# $2 - target file
local ZIPLISTTMP="$TMPDIR/zapret-ip.txt"
[ "$DISABLE_IPV4" != "1" ] && {
curl --fail --max-time 150 --connect-timeout 20 --max-filesize 41943040 -k -L "$1" | cut_local >"$ZIPLISTTMP" &&
{
dlsize=$(LANG=C wc -c "$ZIPLISTTMP" | xargs | cut -f 1 -d ' ')
if [ $dlsize -lt 204800 ]; then
echo list file is too small. can be bad.
exit 2
fi
ip2net4 <"$ZIPLISTTMP" | zz "$2"
rm -f "$ZIPLISTTMP"
}
}
}

8
ipset/clear_lists.sh Executable file
View File

@ -0,0 +1,8 @@
#!/bin/sh
IPSET_DIR="$(dirname "$0")"
IPSET_DIR="$(cd "$IPSET_DIR"; pwd)"
. "$IPSET_DIR/def.sh"
rm -f "$ZIPLIST"* "$ZIPLIST6"* "$ZIPLIST_USER" "$ZIPLIST_USER6" "$ZIPLIST_IPBAN"* "$ZIPLIST_IPBAN6"* "$ZIPLIST_USER_IPBAN" "$ZIPLIST_USER_IPBAN6" "$ZIPLIST_EXCLUDE" "$ZIPLIST_EXCLUDE6" "$ZHOSTLIST"*

201
ipset/create_ipset.sh Executable file
View File

@ -0,0 +1,201 @@
#!/bin/sh
# create ipset or ipfw table from resolved ip's
# $1=no-update - do not update ipset, only create if its absent
IPSET_DIR="$(dirname "$0")"
IPSET_DIR="$(cd "$IPSET_DIR"; pwd)"
. "$IPSET_DIR/def.sh"
IPSET_CMD="$TMPDIR/ipset_cmd.txt"
IPSET_SAVERAM_CHUNK_SIZE=20000
IPSET_SAVERAM_MIN_FILESIZE=131072
while [ -n "$1" ]; do
[ "$1" = "no-update" ] && NO_UPDATE=1
[ "$1" = "clear" ] && DO_CLEAR=1
shift
done
file_extract_lines()
{
# $1 - filename
# $2 - from line (starting with 0)
# $3 - line count
# awk "{ err=1 } NR < $(($2+1)) { next } { print; err=0 } NR == $(($2+$3)) { exit err } END {exit err}" "$1"
awk "NR < $(($2+1)) { next } { print } NR == $(($2+$3)) { exit }" "$1"
}
ipset_restore_chunked()
{
# $1 - filename
# $2 - chunk size
local pos lines
[ -f "$1" ] || return
lines=$(wc -l <"$1")
pos=$lines
while [ "$pos" -gt "0" ]; do
pos=$((pos-$2))
[ "$pos" -lt "0" ] && pos=0
file_extract_lines "$1" $pos $2 | ipset -! restore
sed -i "$(($pos+1)),$ d" "$1"
done
}
ipset_get_script()
{
# $1 - filename
# $2 - ipset name
zzcat "$1" | sort -u | sed -nEe "s/^.+$/add $2 &/p"
}
ipset_restore()
{
# $1 - filename
# $2 - ipset name
# $3 - "6" = ipv6
zzexist "$1" || return
local fsize=$(zzsize "$1")
local svram=0
# do not saveram small files. file can also be gzipped
[ "$SAVERAM" = "1" ] && [ "$fsize" -ge "$IPSET_SAVERAM_MIN_FILESIZE" ] && svram=1
local T="Adding to ipset $2 ($IPSTYPE"
[ "$svram" = "1" ] && T="$T, saveram"
T="$T) : $f"
echo $T
if [ "$svram" = "1" ]; then
ipset_get_script "$1" "$2" >"$IPSET_CMD"
ipset_restore_chunked "$IPSET_CMD" $IPSET_SAVERAM_CHUNK_SIZE
rm -f "$IPSET_CMD"
else
ipset_get_script "$1" "$2" | ipset -! restore
fi
}
create_ipset()
{
if [ "$1" -eq "6" ]; then
FAMILY=inet6
else
FAMILY=inet
fi
ipset create $2 $3 $4 family $FAMILY 2>/dev/null || {
[ "$NO_UPDATE" = "1" ] && return
}
ipset flush $2
[ "$DO_CLEAR" = "1" ] || {
for f in "$5" "$6" ; do
ipset_restore "$f" "$2" $1
done
}
return 0
}
add_ipfw_table()
{
# $1 - table name
sed -nEe "s/^.+$/table $1 add &/p" | ipfw -q /dev/stdin
}
populate_ipfw_table()
{
# $1 - table name
# $2 - ip list file
zzexist "$2" || return
zzcat "$2" | sort -u | add_ipfw_table $1
}
create_ipfw_table()
{
# $1 - table name
# $2 - table options
# $3,$4, ... - ip list files. can be v4,v6 or mixed
local name=$1
ipfw table "$name" create $2 2>/dev/null || {
[ "$NO_UPDATE" = "1" ] && return
}
ipfw -q table $1 flush
shift
shift
[ "$DO_CLEAR" = "1" ] || {
while [ -n "$1" ]; do
populate_ipfw_table $name "$1"
shift
done
}
}
print_reloading_backend()
{
# $1 - backend name
local s="reloading $1 backend"
if [ "$NO_UPDATE" = 1 ]; then
s="$s (no-update)"
else
s="$s (forced-update)"
fi
echo $s
}
oom_adjust_high
if [ -n "$LISTS_RELOAD" ] ; then
if [ "$LISTS_RELOAD" = "-" ] ; then
echo not reloading ip list backend
true
else
echo executing custom ip list reload command : $LISTS_RELOAD
$LISTS_RELOAD
fi
elif exists ipset; then
# ipset seem to buffer the whole script to memory
# on low RAM system this can cause oom errors
# in SAVERAM mode we feed script lines in portions starting from the end, while truncating source file to free /tmp space
# only /tmp is considered tmpfs. other locations mean tmpdir was redirected to a disk
SAVERAM=0
[ "$TMPDIR" = "/tmp" ] && {
RAMSIZE=$($GREP MemTotal /proc/meminfo | awk '{print $2}')
[ "$RAMSIZE" -lt "110000" ] && SAVERAM=1
}
print_reloading_backend ipset
[ "$DISABLE_IPV4" != "1" ] && {
create_ipset 4 $ZIPSET hash:net "$IPSET_OPT" "$ZIPLIST" "$ZIPLIST_USER"
create_ipset 4 $ZIPSET_IPBAN hash:net "$IPSET_OPT" "$ZIPLIST_IPBAN" "$ZIPLIST_USER_IPBAN"
create_ipset 4 $ZIPSET_EXCLUDE hash:net "$IPSET_OPT_EXCLUDE" "$ZIPLIST_EXCLUDE"
}
[ "$DISABLE_IPV6" != "1" ] && {
create_ipset 6 $ZIPSET6 hash:net "$IPSET_OPT" "$ZIPLIST6" "$ZIPLIST_USER6"
create_ipset 6 $ZIPSET_IPBAN6 hash:net "$IPSET_OPT" "$ZIPLIST_IPBAN6" "$ZIPLIST_USER_IPBAN6"
create_ipset 6 $ZIPSET_EXCLUDE6 hash:net "$IPSET_OPT_EXCLUDE" "$ZIPLIST_EXCLUDE6"
}
true
elif exists ipfw; then
print_reloading_backend "ipfw table"
if [ "$DISABLE_IPV4" != "1" ] && [ "$DISABLE_IPV6" != "1" ]; then
create_ipfw_table $ZIPSET "$IPFW_TABLE_OPT" "$ZIPLIST" "$ZIPLIST_USER" "$ZIPLIST6" "$ZIPLIST_USER6"
create_ipfw_table $ZIPSET_IPBAN "$IPFW_TABLE_OPT" "$ZIPLIST_IPBAN" "$ZIPLIST_USER_IPBAN" "$ZIPLIST_IPBAN6" "$ZIPLIST_USER_IPBAN6"
create_ipfw_table $ZIPSET_EXCLUDE "$IPFW_TABLE_OPT_EXCLUDE" "$ZIPLIST_EXCLUDE" "$ZIPLIST_EXCLUDE6"
elif [ "$DISABLE_IPV4" != "1" ]; then
create_ipfw_table $ZIPSET "$IPFW_TABLE_OPT" "$ZIPLIST" "$ZIPLIST_USER"
create_ipfw_table $ZIPSET_IPBAN "$IPFW_TABLE_OPT" "$ZIPLIST_IPBAN" "$ZIPLIST_USER_IPBAN"
create_ipfw_table $ZIPSET_EXCLUDE "$IPFW_TABLE_OPT_EXCLUDE" "$ZIPLIST_EXCLUDE"
elif [ "$DISABLE_IPV6" != "1" ]; then
create_ipfw_table $ZIPSET "$IPFW_TABLE_OPT" "$ZIPLIST6" "$ZIPLIST_USER6"
create_ipfw_table $ZIPSET_IPBAN "$IPFW_TABLE_OPT" "$ZIPLIST_IPBAN6" "$ZIPLIST_USER_IPBAN6"
create_ipfw_table $ZIPSET_EXCLUDE "$IPFW_TABLE_OPT_EXCLUDE" "$ZIPLIST_EXCLUDE6"
else
create_ipfw_table $ZIPSET "$IPFW_TABLE_OPT"
create_ipfw_table $ZIPSET_IPBAN "$IPFW_TABLE_OPT"
create_ipfw_table $ZIPSET_EXCLUDE "$IPFW_TABLE_OPT_EXCLUDE"
fi
true
else
echo no supported ip list backend found
true
fi

186
ipset/def.sh Normal file
View File

@ -0,0 +1,186 @@
. "$IPSET_DIR/../config"
[ -z "$TMPDIR" ] && TMPDIR=/tmp
[ -z "$GZIP_LISTS" ] && GZIP_LISTS=1
[ -z "$IPSET_OPT" ] && IPSET_OPT="hashsize 262144 maxelem 2097152"
[ -z "$IPSET_OPT_EXCLUDE" ] && IPSET_OPT_EXCLUDE="hashsize 1024 maxelem 65536"
[ -z "$IPFW_TABLE_OPT" ] && IPFW_TABLE_OPT="algo addr:radix"
[ -z "$IPFW_TABLE_OPT_EXCLUDE" ] && IPFW_TABLE_OPT_EXCLUDE="algo addr:radix"
ZIPSET=zapret
ZIPSET6=zapret6
ZIPSET_EXCLUDE=nozapret
ZIPSET_EXCLUDE6=nozapret6
ZIPLIST="$IPSET_DIR/zapret-ip.txt"
ZIPLIST6="$IPSET_DIR/zapret-ip6.txt"
ZIPLIST_EXCLUDE="$IPSET_DIR/zapret-ip-exclude.txt"
ZIPLIST_EXCLUDE6="$IPSET_DIR/zapret-ip-exclude6.txt"
ZIPLIST_USER="$IPSET_DIR/zapret-ip-user.txt"
ZIPLIST_USER6="$IPSET_DIR/zapret-ip-user6.txt"
ZUSERLIST="$IPSET_DIR/zapret-hosts-user.txt"
ZHOSTLIST="$IPSET_DIR/zapret-hosts.txt"
ZIPSET_IPBAN=ipban
ZIPSET_IPBAN6=ipban6
ZIPLIST_IPBAN="$IPSET_DIR/zapret-ip-ipban.txt"
ZIPLIST_IPBAN6="$IPSET_DIR/zapret-ip-ipban6.txt"
ZIPLIST_USER_IPBAN="$IPSET_DIR/zapret-ip-user-ipban.txt"
ZIPLIST_USER_IPBAN6="$IPSET_DIR/zapret-ip-user-ipban6.txt"
ZUSERLIST_IPBAN="$IPSET_DIR/zapret-hosts-user-ipban.txt"
ZUSERLIST_EXCLUDE="$IPSET_DIR/zapret-hosts-user-exclude.txt"
IP2NET="$IPSET_DIR/../ip2net/ip2net"
MDIG="$IPSET_DIR/../mdig/mdig"
[ -z "$MDIG_THREADS" ] && MDIG_THREADS=30
exists()
{
which "$1" >/dev/null 2>/dev/null
}
# BSD grep is damn slow with -f option. prefer GNU grep (ggrep) if present
# MacoS in cron does not include /usr/local/bin to PATH
if [ -x /usr/local/bin/ggrep ] ; then
GREP=/usr/local/bin/ggrep
elif exists ggrep; then
GREP=$(which ggrep)
else
GREP=$(which grep)
fi
grep_supports_b()
{
# \b does not work with BSD grep
$GREP --version 2>&1 | $GREP -qE "BusyBox|GNU"
}
get_ip_regex()
{
REG_IPV4='((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(/([0-9]|[12][0-9]|3[012]))?'
REG_IPV6='[0-9a-fA-F]{1,4}:([0-9a-fA-F]{1,4}|:)+(/([0-9][0-9]?|1[01][0-9]|12[0-8]))?'
# good but too slow
# REG_IPV6='([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}(/[0-9]+)?|([0-9a-fA-F]{1,4}:){1,7}:(/[0-9]+)?|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}(/[0-9]+)?|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}(/[0-9]+)?|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}(/[0-9]+)?|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}(/[0-9]+)?|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}(/[0-9]+)?|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})(/[0-9]+)?|:((:[0-9a-fA-F]{1,4}){1,7}|:)(/([0-9][0-9]?|1[01][0-9]|12[0-8]))?'
grep_supports_b && {
REG_IPV4="\b$REG_IPV4\b"
REG_IPV6="\b$REG_IPV6\b"
}
}
ip2net4()
{
if [ -x "$IP2NET" ]; then
"$IP2NET" -4 $IP2NET_OPT4
else
sort -u
fi
}
ip2net6()
{
if [ -x "$IP2NET" ]; then
"$IP2NET" -6 $IP2NET_OPT6
else
sort -u
fi
}
zzexist()
{
[ -f "$1.gz" ] || [ -f "$1" ]
}
zzcat()
{
if [ -f "$1.gz" ]; then
gunzip -c "$1.gz"
else
cat "$1"
fi
}
zz()
{
if [ "$GZIP_LISTS" = "1" ]; then
gzip -c >"$1.gz"
rm -f "$1"
else
cat >"$1"
rm -f "$1.gz"
fi
}
zzsize()
{
local f="$1"
[ -f "$1.gz" ] && f="$1.gz"
wc -c <"$f" | xargs
}
digger()
{
# $1 - hostlist
# $2 - family (4|6)
>&2 echo digging $(wc -l <"$1" | xargs) ipv$2 domains : "$1"
if [ -x "$MDIG" ]; then
zzcat "$1" | "$MDIG" --family=$2 --threads=$MDIG_THREADS --stats=1000
else
local A=A
[ "$2" = "6" ] && A=AAAA
zzcat "$1" | dig $A +short +time=8 +tries=2 -f - | $GREP -E '^[^;].*[^\.]$'
fi
}
cut_local()
{
$GREP -vE '^192\.168\.|^127\.|^10\.'
}
cut_local6()
{
$GREP -vE '^::|^fc..:|^fd..:'
}
oom_adjust_high()
{
[ -f /proc/$$/oom_score_adj ] && {
echo setting high oom kill priority
echo -n 100 >/proc/$$/oom_score_adj
}
}
getexclude()
{
oom_adjust_high
[ -f "$ZUSERLIST_EXCLUDE" ] && {
[ "$DISABLE_IPV4" != "1" ] && digger "$ZUSERLIST_EXCLUDE" 4 | sort -u > "$ZIPLIST_EXCLUDE"
[ "$DISABLE_IPV6" != "1" ] && digger "$ZUSERLIST_EXCLUDE" 6 | sort -u > "$ZIPLIST_EXCLUDE6"
}
}
getuser()
{
getexclude
[ -f "$ZUSERLIST" ] && {
[ "$DISABLE_IPV4" != "1" ] && digger "$ZUSERLIST" 4 | cut_local | sort -u > "$ZIPLIST_USER"
[ "$DISABLE_IPV6" != "1" ] && digger "$ZUSERLIST" 6 | cut_local6 | sort -u > "$ZIPLIST_USER6"
}
[ -f "$ZUSERLIST_IPBAN" ] && {
[ "$DISABLE_IPV4" != "1" ] && digger "$ZUSERLIST_IPBAN" 4 | cut_local | sort -u > "$ZIPLIST_USER_IPBAN"
[ "$DISABLE_IPV6" != "1" ] && digger "$ZUSERLIST_IPBAN" 6 | cut_local6 | sort -u > "$ZIPLIST_USER_IPBAN6"
}
}
hup_zapret_daemons()
{
echo forcing zapret daemons to reload their hostlist
if exists killall; then
kcmd=killall
killall -HUP tpws nfqws dvtws 2>/dev/null
elif exists pkill; then
pkill -HUP ^tpws$ ^nfqws$ ^dvtws$
else
echo no mass killer available ! cant HUP zapret daemons
fi
}

14
ipset/get_antifilter_ip.sh Executable file
View File

@ -0,0 +1,14 @@
#!/bin/sh
IPSET_DIR="$(dirname "$0")"
IPSET_DIR="$(cd "$IPSET_DIR"; pwd)"
. "$IPSET_DIR/def.sh"
getuser
. "$IPSET_DIR/antifilter.helper"
get_antifilter https://antifilter.network/download/ip.lst "$ZIPLIST"
"$IPSET_DIR/create_ipset.sh"

14
ipset/get_antifilter_ipsmart.sh Executable file
View File

@ -0,0 +1,14 @@
#!/bin/sh
IPSET_DIR="$(dirname "$0")"
IPSET_DIR="$(cd "$IPSET_DIR"; pwd)"
. "$IPSET_DIR/def.sh"
getuser
. "$IPSET_DIR/antifilter.helper"
get_antifilter https://antifilter.network/download/ipsmart.lst "$ZIPLIST"
"$IPSET_DIR/create_ipset.sh"

14
ipset/get_antifilter_ipsum.sh Executable file
View File

@ -0,0 +1,14 @@
#!/bin/sh
IPSET_DIR="$(dirname "$0")"
IPSET_DIR="$(cd "$IPSET_DIR"; pwd)"
. "$IPSET_DIR/def.sh"
getuser
. "$IPSET_DIR/antifilter.helper"
get_antifilter https://antifilter.network/download/ipsum.lst "$ZIPLIST"
"$IPSET_DIR/create_ipset.sh"

Some files were not shown because too many files have changed in this diff Show More