commit ae2b6d1e8670ded05332acd8899d517bd4e543d9 Author: bol-van Date: Thu Jan 2 13:10:28 2020 +0300 history purge diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..0a7fc45 --- /dev/null +++ b/Makefile @@ -0,0 +1,21 @@ +DIRS := nfq tpws ip2net mdig +TGT := binaries/my + +all: clean + mkdir -p "$(TGT)"; \ + for dir in $(DIRS); do \ + chmod -x "$$dir/"*; \ + $(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 + +clean: + [ -d "$(TGT)" ] && rm -r "$(TGT)" ; \ + for dir in $(DIRS); do \ + $(MAKE) -C "$$dir" clean; \ + done diff --git a/binaries/aarch64/ip2net b/binaries/aarch64/ip2net new file mode 100755 index 0000000..19b2d72 Binary files /dev/null and b/binaries/aarch64/ip2net differ diff --git a/binaries/aarch64/mdig b/binaries/aarch64/mdig new file mode 100755 index 0000000..0268d24 Binary files /dev/null and b/binaries/aarch64/mdig differ diff --git a/binaries/aarch64/nfqws b/binaries/aarch64/nfqws new file mode 100755 index 0000000..966b168 Binary files /dev/null and b/binaries/aarch64/nfqws differ diff --git a/binaries/aarch64/tpws b/binaries/aarch64/tpws new file mode 100755 index 0000000..fdc591a Binary files /dev/null and b/binaries/aarch64/tpws differ diff --git a/binaries/armhf/ip2net b/binaries/armhf/ip2net new file mode 100755 index 0000000..3a69498 Binary files /dev/null and b/binaries/armhf/ip2net differ diff --git a/binaries/armhf/mdig b/binaries/armhf/mdig new file mode 100755 index 0000000..fb276d9 Binary files /dev/null and b/binaries/armhf/mdig differ diff --git a/binaries/armhf/nfqws b/binaries/armhf/nfqws new file mode 100755 index 0000000..7c6b346 Binary files /dev/null and b/binaries/armhf/nfqws differ diff --git a/binaries/armhf/tpws b/binaries/armhf/tpws new file mode 100755 index 0000000..668373d Binary files /dev/null and b/binaries/armhf/tpws differ diff --git a/binaries/mips32r1-lsb/ip2net b/binaries/mips32r1-lsb/ip2net new file mode 100755 index 0000000..c87362b Binary files /dev/null and b/binaries/mips32r1-lsb/ip2net differ diff --git a/binaries/mips32r1-lsb/mdig b/binaries/mips32r1-lsb/mdig new file mode 100755 index 0000000..50a5625 Binary files /dev/null and b/binaries/mips32r1-lsb/mdig differ diff --git a/binaries/mips32r1-lsb/nfqws b/binaries/mips32r1-lsb/nfqws new file mode 100755 index 0000000..c969d1c Binary files /dev/null and b/binaries/mips32r1-lsb/nfqws differ diff --git a/binaries/mips32r1-lsb/tpws b/binaries/mips32r1-lsb/tpws new file mode 100755 index 0000000..bfaa659 Binary files /dev/null and b/binaries/mips32r1-lsb/tpws differ diff --git a/binaries/mips32r1-msb/ip2net b/binaries/mips32r1-msb/ip2net new file mode 100755 index 0000000..1bd488a Binary files /dev/null and b/binaries/mips32r1-msb/ip2net differ diff --git a/binaries/mips32r1-msb/mdig b/binaries/mips32r1-msb/mdig new file mode 100755 index 0000000..900c911 Binary files /dev/null and b/binaries/mips32r1-msb/mdig differ diff --git a/binaries/mips32r1-msb/nfqws b/binaries/mips32r1-msb/nfqws new file mode 100755 index 0000000..9e37030 Binary files /dev/null and b/binaries/mips32r1-msb/nfqws differ diff --git a/binaries/mips32r1-msb/tpws b/binaries/mips32r1-msb/tpws new file mode 100755 index 0000000..c8f8211 Binary files /dev/null and b/binaries/mips32r1-msb/tpws differ diff --git a/binaries/mips64r2-msb/ip2net b/binaries/mips64r2-msb/ip2net new file mode 100755 index 0000000..ab30c2d Binary files /dev/null and b/binaries/mips64r2-msb/ip2net differ diff --git a/binaries/mips64r2-msb/mdig b/binaries/mips64r2-msb/mdig new file mode 100755 index 0000000..a9d729f Binary files /dev/null and b/binaries/mips64r2-msb/mdig differ diff --git a/binaries/mips64r2-msb/nfqws b/binaries/mips64r2-msb/nfqws new file mode 100755 index 0000000..6970582 Binary files /dev/null and b/binaries/mips64r2-msb/nfqws differ diff --git a/binaries/mips64r2-msb/tpws b/binaries/mips64r2-msb/tpws new file mode 100755 index 0000000..9af3642 Binary files /dev/null and b/binaries/mips64r2-msb/tpws differ diff --git a/binaries/ppc/ip2net b/binaries/ppc/ip2net new file mode 100755 index 0000000..2267654 Binary files /dev/null and b/binaries/ppc/ip2net differ diff --git a/binaries/ppc/mdig b/binaries/ppc/mdig new file mode 100755 index 0000000..aeb8bb3 Binary files /dev/null and b/binaries/ppc/mdig differ diff --git a/binaries/ppc/nfqws b/binaries/ppc/nfqws new file mode 100755 index 0000000..5961769 Binary files /dev/null and b/binaries/ppc/nfqws differ diff --git a/binaries/ppc/tpws b/binaries/ppc/tpws new file mode 100755 index 0000000..6347e83 Binary files /dev/null and b/binaries/ppc/tpws differ diff --git a/binaries/x86/ip2net b/binaries/x86/ip2net new file mode 100755 index 0000000..72cdd34 Binary files /dev/null and b/binaries/x86/ip2net differ diff --git a/binaries/x86/mdig b/binaries/x86/mdig new file mode 100755 index 0000000..5e9e7e7 Binary files /dev/null and b/binaries/x86/mdig differ diff --git a/binaries/x86/nfqws b/binaries/x86/nfqws new file mode 100755 index 0000000..d0a54c6 Binary files /dev/null and b/binaries/x86/nfqws differ diff --git a/binaries/x86/tpws b/binaries/x86/tpws new file mode 100755 index 0000000..7a11c7c Binary files /dev/null and b/binaries/x86/tpws differ diff --git a/binaries/x86_64/ip2net b/binaries/x86_64/ip2net new file mode 100755 index 0000000..e8867cc Binary files /dev/null and b/binaries/x86_64/ip2net differ diff --git a/binaries/x86_64/mdig b/binaries/x86_64/mdig new file mode 100755 index 0000000..bf0d5c8 Binary files /dev/null and b/binaries/x86_64/mdig differ diff --git a/binaries/x86_64/nfqws b/binaries/x86_64/nfqws new file mode 100755 index 0000000..f58bd86 Binary files /dev/null and b/binaries/x86_64/nfqws differ diff --git a/binaries/x86_64/tpws b/binaries/x86_64/tpws new file mode 100755 index 0000000..07cfefb Binary files /dev/null and b/binaries/x86_64/tpws differ diff --git a/config b/config new file mode 100644 index 0000000..ba8de3d --- /dev/null +++ b/config @@ -0,0 +1,53 @@ +# 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" + +# CHOOSE OPERATION MODE +# use nfqws : nfqws_ipset nfqws_ipset_https nfqws_all nfqws_all_https +# use tpws : tpws_ipset tpws_ipset_https tpws_all tpws_all_https tpws_hostlist +# no daemon, just ipset : ipset +# custom mode : custom . should modify init script and add your own code +MODE=tpws_ipset_https + +# CHOOSE NFQWS DAEMON OPTIONS. run "nfq/nfqws --help" for option list +NFQWS_OPT="--wsize=3 --hostspell=HOST" + +# 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_HTTP="--hostspell=HOST --split-http-req=method" +TPWS_OPT_HTTPS="--split-pos=3" + +# for routers based on desktop linux only. has not effect in openwrt. +# CHOOSE LAN and 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 diff --git a/docs/changes.txt b/docs/changes.txt new file mode 100644 index 0000000..76d5d08 --- /dev/null +++ b/docs/changes.txt @@ -0,0 +1,154 @@ +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 diff --git a/docs/compile/build_howto_openwrt.txt b/docs/compile/build_howto_openwrt.txt new file mode 100644 index 0000000..46a65d8 --- /dev/null +++ b/docs/compile/build_howto_openwrt.txt @@ -0,0 +1,42 @@ +How to compile native programs for use in openwrt +------------------------------------------------- + +1) + + cd ~ + + + git clone git://git.openwrt.org/15.05/openwrt.git + + git clone git://git.openwrt.org/14.07/openwrt.git + + 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 diff --git a/docs/compile/openwrt/package/zapret/ip2net/Makefile b/docs/compile/openwrt/package/zapret/ip2net/Makefile new file mode 100644 index 0000000..4564675 --- /dev/null +++ b/docs/compile/openwrt/package/zapret/ip2net/Makefile @@ -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)) + diff --git a/docs/compile/openwrt/package/zapret/ip2net/readme.txt b/docs/compile/openwrt/package/zapret/ip2net/readme.txt new file mode 100644 index 0000000..abf7acd --- /dev/null +++ b/docs/compile/openwrt/package/zapret/ip2net/readme.txt @@ -0,0 +1 @@ +Copy "ip2net" folder here ! diff --git a/docs/compile/openwrt/package/zapret/mdig/Makefile b/docs/compile/openwrt/package/zapret/mdig/Makefile new file mode 100644 index 0000000..55d55d2 --- /dev/null +++ b/docs/compile/openwrt/package/zapret/mdig/Makefile @@ -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)) + diff --git a/docs/compile/openwrt/package/zapret/mdig/readme.txt b/docs/compile/openwrt/package/zapret/mdig/readme.txt new file mode 100644 index 0000000..14e5c14 --- /dev/null +++ b/docs/compile/openwrt/package/zapret/mdig/readme.txt @@ -0,0 +1 @@ +Copy "mdig" folder here ! diff --git a/docs/compile/openwrt/package/zapret/nfqws/Makefile b/docs/compile/openwrt/package/zapret/nfqws/Makefile new file mode 100644 index 0000000..48b562f --- /dev/null +++ b/docs/compile/openwrt/package/zapret/nfqws/Makefile @@ -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)) + + diff --git a/docs/compile/openwrt/package/zapret/nfqws/readme.txt b/docs/compile/openwrt/package/zapret/nfqws/readme.txt new file mode 100644 index 0000000..daf8b84 --- /dev/null +++ b/docs/compile/openwrt/package/zapret/nfqws/readme.txt @@ -0,0 +1 @@ +Copy "nfq" folder here ! diff --git a/docs/compile/openwrt/package/zapret/tpws/Makefile b/docs/compile/openwrt/package/zapret/tpws/Makefile new file mode 100644 index 0000000..3f8dfc7 --- /dev/null +++ b/docs/compile/openwrt/package/zapret/tpws/Makefile @@ -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)) + diff --git a/docs/compile/openwrt/package/zapret/tpws/readme.txt b/docs/compile/openwrt/package/zapret/tpws/readme.txt new file mode 100644 index 0000000..18fa3ed --- /dev/null +++ b/docs/compile/openwrt/package/zapret/tpws/readme.txt @@ -0,0 +1 @@ +Copy "tpws" folder here ! diff --git a/docs/https.txt b/docs/https.txt new file mode 100644 index 0000000..b606397 --- /dev/null +++ b/docs/https.txt @@ -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 с компа внутри локалки. diff --git a/docs/iptables.txt b/docs/iptables.txt new file mode 100644 index 0000000..4035efa --- /dev/null +++ b/docs/iptables.txt @@ -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 1188 -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 1188 + +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 1188 + +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.1:1188 +iptables -t nat -I OUTPUT -p tcp --dport 80 -m owner ! --uid-owner tpws -j DNAT --to 127.0.0.1:1188 + + +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 diff --git a/docs/readme.eng.txt b/docs/readme.eng.txt new file mode 100644 index 0000000..dcdbb38 --- /dev/null +++ b/docs/readme.eng.txt @@ -0,0 +1,475 @@ +What is it for +-------------- + +Bypass the blocking of web sites http. +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. + +How it works +------------ + +DPI providers have gaps. They happen because DPI rules are writtten for +ordinary user programs, omitting all possible cases that are permissible by standards. +This is done for simplicity and speed. It makes no sense to catch 0.01% hackers, +because these blockings are quite simple and easily bypassed even by ordinary users. + +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." + + +How to put this into practice in the linux system +------------------------------------------------- + +How to make the system break the request into parts? You can pipe the entire TCP session +through transparent proxy, or you can replace the tcp window size field on the first incoming TCP packet with a SYN, ACK. +Then the client will think that the server has set a small window size for it and the first data segment +will send no more than the specified length. In subsequent packages, we will not change anything. +The further behavior of the system depends on the implemented algorithm in the OS. +Experience shows that linux always sends first packet no more than the specified +in window size length, the rest of the packets until some time sends no more than max (36, specified_size). +After a number of packets, the window scaling mechanism is triggered and starts taking +the scaling factor into account. The packet size becomes no more than max (36, specified_ramer << scale_factor). +The behavior is not very elegant, but since we do not affect the size of the incoming packets, +and the amount of data received in http is usually much higher than the amount sent, then visually +there will be only small delays. +Windows behaves in a similar case much more predictably. First segment +the specified length goes away, then the window size changes depending on the value, +sent in new tcp packets. That is, the speed is almost immediately restored to the possible maximum. + +Its easy to intercept a packet with SYN, ACK using iptables. +However, the options for editing packets in iptables are severely limited. +It’s not possible to change window size with standard modules. +For this, we will use the NFQUEUE. This tool allows transfer packets to the processes running in user mode. +The process, accepting a packet, can change it, which is what we need. + +iptables -t mangle -I PREROUTING -p tcp --sport 80 --tcp-flags SYN,ACK SYN,ACK -j NFQUEUE --queue-num 200 --queue-bypass + +It will queue the packets we need to the process that listens on the queue with the number 200. +Process will replace the window size. PREROUTING will catch packets addressed to the host itself and routed packets. +That is, the solution works the same way as on the client, so on the router. On a PC-based or OpenWRT router. +In principle, this is enough. +However, with such an impact on TCP there will be a slight delay. +In order not to touch the hosts that are not blocked by the provider, you can make such a move. +Create a list of blocked domains, resolve them to IP addresses and save to ipset named "zapret". +Add to rule: + +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 + +Thus, the impact will be made only on ip addresses related to blocked sites. +The list can be updated in scheduled task every few days. + +If DPI cant be bypassed with splitting a request into segments, then sometimes helps changing case +of the "Host:" http header. We may not need a window size replacement, so the do not need PREROUTING chain. +Instead, we hang on outgoing packets in the POSTROUTING chain: + +iptables -t mangle -I POSTROUTING -p tcp --dport 80 -m set --match-set zapret dst -j NFQUEUE --queue-num 200 --queue-bypass + +In this case, additional points are also possible. DPI can catch only the first http request, ignoring +subsequent requests in the keep-alive session. Then we can reduce the cpu load abandoning the processing of unnecessary packages. + +iptables -t mangle -I POSTROUTING -p tcp --dport 80 -m connbytes --connbytes-dir=original --connbytes-mode=packets --connbytes 1:5 -m set --match-set zapret dst -j NFQUEUE --queue-num 200 --queue-bypass + +It happens that the provider monitors the entire HTTP session with keep-alive requests. In this case +it is not enough to restrict the TCP window when establishing a connection. Each http request must be splitted +to multiple TCP segments. This task is solved through the full proxying of traffic using +transparent proxy (TPROXY or DNAT). TPROXY does not work with connections originating from the local system +so this solution is applicable only on the router. DNAT works with local connections, +but there is a danger of entering into endless recursion, so the daemon is launched as a separate user, +and for this user, DNAT is disabled via "-m owner". Full proxying requires more resources than outbound packet +manipulation without reconstructing a TCP connection. + +iptables -t nat -I PREROUTING -p tcp --dport 80 -j DNAT --to 127.0.0.1:1188 +iptables -t nat -I OUTPUT -p tcp --dport 80 -m owner ! --uid-owner tpws -j DNAT --to 127.0.0.1:1188 + +NOTE: 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..route_localnet=1 + +You can use "-j REDIRECT --to-port 1188" instead of DNAT, but in this case the transpareny 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 in the case of automated +script will have to recognize it, then dynamically enter it into the command. In any case, additional efforts are required. + +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 : + + iptables -t nat -I OUTPUT -p tcp --dport 80 -m owner ! --uid-owner tpws -j DNAT --to [::1]:1188 + +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. +It takes the following parameters: + + --debug=0|1 ; 1=print debug info + --qnum= + --wsize= ; set window size. 0 = do not modify + --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 + --daemon ; daemonize + --pidfile= ; write pid to file + --user= ; drop root privs + --uid=uid[:gid] ; drop root privs + --dpi-desync[=fake|rst|rstack|disorder] ; try to desync dpi state + --dpi-desync-fwmark= ; override fwmark for desync packet. default = 0x40000000 + --dpi-desync-ttl= ; set ttl for desync packet + --dpi-desync-fooling=none|md5sig|badsum + --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-skip-nosni=0|1 ; 1(default)=do not apply desync to requests without hostname in the SNI + --dpi-desync-split-pos=<1..1500> ; (for disorder only) split TCP packet at specified position + --hostlist= ; apply dpi desync only to the listed hosts (one host per line, subdomains auto apply) + +The manipulation parameters can be combined in any way. + +COMMENT. As described earlier, Linux behaves strangely when the window size is changed, unlike Windows. +Following segments do not restore their full length. Connection can go for a long time in batches of small packets. +Package modification parameters (--hostcase, ...) may not work, because nfqws does not work with the connection, +but only with separate packets in which the search may not be found, because scattered across multiple packets. +If the source of the packages is Windows, there is no such problem. + +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. +* 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. + +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. + +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. + +Hostlist is applicable only to desync attack. It does not work for other options. +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 : + +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 + +connbytes will only queue the first data packet. mark is needed to keep away generated packets from NFQUEUE. +nfqws sets fwmark when it sends generated packets. + +tpws +----- + +tpws is transparent proxy. + + --debug=0|1|2 ; 0(default)=silent 1=verbose 2=debug + --bind-addr=| + --bind-iface4= ; bind to the first ipv4 addr of interface + --bind-iface6= ; bind to the first ipv6 addr of interface + --bind-linklocal=prefer|force ; prefer or force ipv6 link local + --bind-wait-ifup= ; wait for interface to appear and up + --bind-wait-ip= ; after ifup wait for ip address to appear up to N seconds + --bind-wait-ip-linklocal= ; accept only link locals first N seconds then any + --port= ; port number to listen on + --socks ; implement socks4/5 proxy instead of transparent proxy + --local-rcvbuf= ; SO_RCVBUF for local legs + --local-sndbuf= ; SO_SNDBUF for local legs + --remote-rcvbuf= ; SO_RCVBUF for remote legs + --remote-sndbuf= ; 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 number of local legs + --maxfiles= ; 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= ; if local leg sends something and closes and remote leg is still connecting then cancel connection attempt after N seconds + + --hostlist= ; only act on host in the list (one host per line, subdomains auto apply) + --split-http-req=method|host ; split http request at specified logical position + --split-pos= ; split at specified pos. invalidates split-http-req. + --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= ; add dummy padding headers before Host: + --methodspace ; add extra space after method + --methodeol ; add end-of-line before method + --unixeol ; replace 0D0A to 0A + --daemon ; daemonize + --pidfile= ; write pid to file + --user= ; drop root privs + --uid=uid[:gid] ; drop root privs + +The manipulation parameters can be combined in any way. +There are exceptions: split-pos replaces split-http-req. hostdot and hosttab are mutually exclusive. +Only split-pos option works for non-HTTP traffic. + +tpws can bind only to one ip or to all at once. +To bind to all ipv4, specify "0.0.0.0", to all ipv6 - "::". Without parameters, tpws bind to all ipv4 and ipv6. +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. +The regulator list has already reached an impressive size of hundreds of thousands of IP addresses. Therefore, to optimize ipset +ip2net utility is used. It takes a list of individual IP addresses and tries to find in it subnets of the maximum size (from / 22 to / 30), +in which more than 3/4 addresses are blocked. ip2net is written in C because the operation is resource intensive. +If ip2net is compiled or a binary is copied to the ip2net directory, the create_ipset.sh script uses an ipset of the hash:net type, +piping the list through ip2net. Otherwise, ipset of hash:ip type is used, the list is loaded as is. +Accordingly, if you don’t like ip2net, just remove the binary from the ip2net directory. +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. + +Domain name filtering +--------------------- + +An alternative to ipset is to use tpws with a list of domains. +tpws can only read one hostlist. + +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, tpws should run without filtering by ipset. +All http traffic goes through tpws, and it decides whether to use manipulation depending on the Host: field in the http request. +This creates an increased load on the system. +The domain search itself works very quickly, the load is connected with pumping the amount of data through the process. +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. +Select MODE: + +nfqws_ipset - use nfqws for http. targets are filtered by ipset "zapret" +nfqws_ipset_https - use nfqws for http and https. targets are filtered by ipset "zapret" +nfqws_all - use nfqws for all http +nfqws_all_https - use nfqws for all http and https +nfqws_all_desync - use nfqws for DPI desync attack on http и https for all http and https +nfqws_ipset_desync - use nfqws for DPI desync attack on http и https for all http and https. targets are filtered by ipset "zapret" +nfqws_hostlist_desync - use nfqws for DPI desync attack on http и https , only to hosts from hostlist + +tpws_ipset - use tpws for http. targets are filtered by ipset "zapret" +tpws_ipset_https - use tpws for http and https. targets are filtered by ipset "zapret" +tpws_all - use tpws for all http +tpws_all_https - use tpws for all http and https +tpws_hostlist - same as tpws_all but touch only domains from the hostlist + +ipset - only fill ipset. futher actions depend on your own code + +Its possible to change manipulation options used by the daemons : + +NFQWS_OPT="--wsize=3 --hostspell=HOST" +TPWS_OPT_HTTP="--hostspell=HOST --split-http-req=method" +TPWS_OPT_HTTPS="--split-pos=3" + +Options for DPI desync attack are configured separately: + +DESYNC_MARK=0x40000000 +NFQWS_OPT_DESYNC="--dpi-desync --dpi-desync-ttl=0 --dpi-desync-fooling=badsum --dpi-desync-fwmark=$DESYNC_MARK" + + +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 + +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. +Run install_easy.sh and answer its questions. + +Simple install to openwrt +------------------------- + +install_easy.sh also 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 the 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, ... + + +Https blocking bypass +---------------------- + +As a rule, DPI tricks do not help to bypass https blocking. +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) + +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. diff --git a/docs/readme.txt b/docs/readme.txt new file mode 100644 index 0000000..f3c4134 --- /dev/null +++ b/docs/readme.txt @@ -0,0 +1,1132 @@ +zapret v.30 + +English +------- + +For english version refer to docs/readme.eng.txt + +Для чего это надо +----------------- + +Обойти блокировки веб сайтов http. + +Как это работает +---------------- + +У провайдеров в DPI бывают бреши. Они случаются от того, что правила DPI пишут для +обычных пользовательских программ, опуская все возможные случаи, допустимые по стандартам. +Это делается для простоты и скорости. Нет смысла ловить хакеров, которых 0.01%, +ведь все равно эти блокировки обходятся довольно просто даже обычными пользователями. + +Некоторые DPI не могут распознать http запрос, если он разделен на TCP сегменты. +Например, запрос вида "GET / HTTP/1.1\r\nHost: kinozal.tv......" +мы посылаем 2 частями : сначала идет "GET ", затем "/ HTTP/1.1\r\nHost: kinozal.tv.....". +Другие DPI спотыкаются, когда заголовок "Host:" пишется в другом регистре : например, "host:". +Кое-где работает добавление дополнительного пробела после метода : "GET /" => "GET /" +или добавление точки в конце имени хоста : "Host: kinozal.tv." + +Как это реализовать на практике в системе linux +----------------------------------------------- + +Как заставить систему разбивать запрос на части ? Можно прогнать всю TCP сессию +через transparent proxy, а можно подменить поле tcp window size на первом входящем TCP пакете с SYN,ACK. +Тогда клиент подумает, что сервер установил для него маленький window size и первый сегмент с данными +отошлет не более указанной длины. В последующих пакетах мы не будем менять ничего. +Дальнейшее поведение системы по выбору размера отсылаемых пакетов зависит от реализованного +в ней алгоритма. Опыт показывает, что linux первый пакет всегда отсылает не более указанной +в window size длины, остальные пакеты до некоторых пор шлет не более max(36,указанный_размер). +После некоторого количества пакетов срабатывает механизм window scaling и начинает +учитываться фактор скалинга, размер пакетов становится не более max(36,указанный_рамер << scale_factor). +Не слишком изящное поведение, но поскольку на размеры входящик пакетов мы не влияем, +а объем принимаемых по http данных обычно гораздо выше объема отсылаемых, то визуально +появятся лишь небольшие задержки. +Windows ведет себя в аналогичном случае гораздо более предсказуемо. Первый сегмент +уходит указанной длины, дальше window size меняется в зависимости от значения, +присылаемого в новых tcp пакетах. То есть скорость почти сразу же восстанавливается +до возможного максимума. + +Перехватить пакет с SYN,ACK не представляет никакой сложности средствами iptables. +Однако, возможности редактирования пакетов в iptables сильно ограничены. +Просто так поменять window size стандартными модулями нельзя. +Для этого мы воспользуемся средством NFQUEUE. Это средство позволяет +передавать пакеты на обработку процессам, работающим в user mode. +Процесс, приняв пакет, может его изменить, что нам и нужно. + +iptables -t mangle -I PREROUTING -p tcp --sport 80 --tcp-flags SYN,ACK SYN,ACK -j NFQUEUE --queue-num 200 --queue-bypass + +Будет отдавать нужные нам пакеты процессу, слушающему на очереди с номером 200. +Он подменит window size. PREROUTING поймает как пакеты, адресованные самому хосту, +так и маршрутизируемые пакеты. То есть решение одинаково работает как на клиенте, +так и на роутере. На роутере на базе PC или на базе OpenWRT. +В принципе этого достаточно. +Однако, при таком воздействии на TCP будет небольшая задержка. +Чтобы не трогать хосты, которые не блокируются провайдером, можно сделать такой ход. +Создать список заблоченых доменов или скачать его с rublacklist. +Заресолвить все домены в ipv4 адреса. Загнать их в ipset с именем "zapret". +Добавить в правило : + +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 + +Таким образом воздействие будет производиться только на ip адреса, относящиеся к заблокированным сайтам. +Список можно обновлять через cron раз в несколько дней. +Если обновлять через rublacklist, то это займет довольно долго. Более часа. Но ресурсов +этот процесс не отнимает, так что никаких проблем это не вызовет, особенно, если система +работает постоянно. + +Если DPI не обходится через разделение запроса на сегменты, то иногда срабатывает изменение +"Host:" на "host:". В этом случае нам может не понадобится замена window size, поэтому цепочка +PREROUTING нам не нужна. Вместо нее вешаемся на исходящие пакеты в цепочке POSTROUTING : + +iptables -t mangle -I POSTROUTING -p tcp --dport 80 -m set --match-set zapret dst -j NFQUEUE --queue-num 200 --queue-bypass + +В этом случае так же возможны дополнительные моменты. DPI может ловить только первый http запрос, игнорируя +последующие запросы в keep-alive сессии. Тогда можем уменьшить нагрузку на проц, отказавшись от процессинга ненужных пакетов. + +iptables -t mangle -I POSTROUTING -p tcp --dport 80 -m connbytes --connbytes-dir=original --connbytes-mode=packets --connbytes 1:5 -m set --match-set zapret dst -j NFQUEUE --queue-num 200 --queue-bypass + +Случается так, что провайдер мониторит всю HTTP сессию с keep-alive запросами. В этом случае +недостаточно ограничивать TCP window при установлении соединения. Необходимо посылать отдельными +TCP сегментами каждый новый запрос. Эта задача решается через полное проксирование трафика через +transparent proxy (TPROXY или DNAT). TPROXY не работает с соединениями, исходящими с локальной системы, +так что это решение применимо только на роутере. DNAT работает и с локальными соединениеми, +но имеется опасность входа в бесконечную рекурсию, поэтому демон запускается под отдельным пользователем, +и для этого пользователя отключается DNAT через "-m owner". Полное проксирование требует больше ресурсов +процессора, чем манипуляция с исходящими пакетами без реконструкции TCP соединения. + +iptables -t nat -I PREROUTING -p tcp --dport 80 -j DNAT --to 127.0.0.1:1188 +iptables -t nat -I OUTPUT -p tcp --dport 80 -m owner ! --uid-owner tpws -j DNAT --to 127.0.0.1:1188 + +ПРИМЕЧАНИЕ: DNAT на localhost работает в цепочке OUTPUT, но не работает в цепочке PREROUTING без включения параметра route_localnet : + +sysctl -w net.ipv4.conf..route_localnet=1 + +Можно использовать "-j REDIRECT --to-port 1188" вместо DNAT , однако в этом случае процесс transparent proxy +должен слушать на ip адресе входящего интерфейса или на всех адресах. Слушать на всех - не есть хорошо +с точки зрения безопасности. Слушать на одном (локальном) можно, но в случае автоматизированного +скрипта придется его узнавать, потом динамически вписывать в команду. В любом случае требуются дополнительные усилия. + +Если ваше устройство поддерживает аппаратное ускорение (flow offloading, hardware nat, hardware acceleration), то iptables могут не работать. +При включенном offloading пакет не проходит по обычному пути netfilter. +Необходимо его отключить. Да, ваш хилый роутер уже не потянет гигабит, даже 200-250 не потянет, а на торрентах и того меньше. +Но если у вас такой линк, и вы хотите продвинутых вещей от роутера, то вам стоит задуматься о покупке mini pc с двумя или более gigabit ethernet. + +В новых ядрах (и в более старых, openwrt портировал изменение на 4.14) присутствует software flow offloading (SFO). +Пакеты, проходящие через SFO, так же проходят мимо большей части механизмов iptables. +При включенном SFO работает DNAT/REDIRECT (tpws). Эти соединения исключаются из offloading. +Однако, остальные соединения идут через SFO, потому NFQUEUE будет срабатывать только до помещения +соединения в flowtable. Практически это означает, что nfqws будет работать на window size changing, +но не будут работать опции по модификации содержимого пакетов. +Offload включается через специальный target в iptables "FLOWOFFLOAD". Не обязательно пропускать весь трафик через offload. +Можно исключить из offload соединения, которые должны попасть на tpws или nfqws. +Однако, openwrt не предусматривает выборочного управления offload. Если вы хотите с этим связаться, +вам придется городить свой собственный огород вокруг этого. + +Особенности применения ip6tables +-------------------------------- + +ip6tables работают почти точно так же, как и ipv4, но есть ряд важных нюансов. +В DNAT следует брать адрес --to в квадратные скобки. Например : + + iptables -t nat -I OUTPUT -p tcp --dport 80 -m owner ! --uid-owner tpws -j DNAT --to [::1]:1188 + +Параметра route_localnet не существует для ipv6. +DNAT на localhost (::1) возможен только в цепочке OUTPUT. +В цепочке PREROUTING DNAT возможен на любой global address или на link local address того же интерфейса, +откуда пришел пакет. +NFQUEUE работает без изменений. + +Когда это работать не будет +--------------------------- + +* Если подменяется DNS. С этой проблемой легко справиться. +* Если блокировка осуществляется по IP. +* Если соединение проходит через фильтр, способный реконструировать TCP соединение, и который +следует всем стандартам. Например, нас заворачивают на squid. Соединение идет через полноценный стек tcpip +операционной системы, фрагментация отпадает сразу как средство обхода. Squid правильный, он все найдет +как надо, обманывать его бесполезно. +НО. Заворачивать на squid могут позволить себе лишь небольшие провайдеры, поскольку это очень ресурсоемко. +Большие компании обычно используют DPI, который расчитан на гораздо большую пропускную способность. +Может применяться комбинированный подход, когда на DPI заворачивают только IP из "плохого" списка, +и дальше уже DPI решает пропускать или нет. Так можно снизить нагрузку на DPI в десятки, если не сотни раз, +а следовательно не покупать очень дорогие решения, обойдясь чем-то существенно более дешевым. +Мелкие провайдеры могут покупать услугу фильтрации у вышестоящих, чтобы самим не морочиться, и +они уже будут применять DPI. + +nfqws +----- + +Эта программа - модификатор пакетов и обработчик очереди NFQUEUE. + + --debug=0|1 ; 1=выводить отладочные сообщения + --daemon ; демонизировать прогу + --pidfile= ; сохранить PID в файл + --user= ; менять uid процесса + --uid=uid[:gid] ; менять uid процесса + --qnum=200 ; номер очереди + --wsize=4 ; менять tcp window size на указанный размер + --hostcase ; менять регистр заголовка "Host:" по умолчанию на "host:". + --hostnospace ; убрать пробел после "Host:" и переместить его в конец значения "User-Agent:" для сохранения длины пакета + --hostspell=HoST ; точное написание заголовка Host (можно "HOST" или "HoSt"). автоматом включает --hostcase + --dpi-desync[=fake|rst|rstack|disorder ; атака по десинхронизации DPI + --dpi-desync-fwmark= ; бит fwmark для пометки десинхронизирующих пакетов, чтобы они повторно не падали в очередь. default = 0x40000000 + --dpi-desync-ttl= ; установить ttl для десинхронизирующих пакетов + --dpi-desync-fooling=none|md5sig|badsum ; дополнительные методики как сделать, чтобы десинхронизирующий пакет не дошел до сервера + --dpi-desync-retrans=0|1 ; (только для fake,rst,rstack) 0(default)=отправлять оригинал следом за фейком 1=дропать оригинал, заставляя ОС выполнять ретрансмиссию через 0.2 сек + --dpi-desync-skip-nosni=0|1 ; 1(default)=не применять dpi desync для запросов без hostname в SNI, в частности для ESNI + --dpi-desync-split-pos=<1..1500> ; (только для disorder) разбивать пакет на указанной позиции + --hostlist= ; применять dpi-desync только к хостам из листа + +Параметры манипуляции могут сочетаться в любых комбинациях. + +ЗАМЕЧАНИЕ. Как описано ранее, linux при измененном window size ведет себя странно, иначе, чем windows. +Следующие сегменты не восстанавливают свою полную длину. Потому соединение может идти долгое время пачками мелких пакетов. +Параметры модификации содержимого пакета (--hostcase, ...) могут не срабатывать, потому что nfqws не работает с соединением, +а лишь с отдельными пакетами, в которых искомое может не быть найдено, т.к. разбросано по нескольким пакетам. +Если источник пакетов - windows, этой проблемы нет. + +АТАКА ДЕСИНХРОНИЗАЦИИ DPI +Суть ее в следующем. После выполнения tcp 3-way handshake идет первый пакет с данными от клиента. +Там обычно "GET / ..." или TLS ClientHello. Мы дропаем этот пакет, заменяя чем-то другим. +Это может быть поддельная версия с безобидным, но валидным запросом http или https (вариант fake), +пакет сброса соединения (варианты rst, rstack), разбитый на части оригинальный пакет с перепутанным +порядком следования сегментов + фейк-сегмент посередине (disorder). +В литературе такие атаки еще называют TCB desynchronization и TCB teardown. +Надо, чтобы фейковые пакеты дошли до DPI, но не дошли до сервера. +На вооружении есть следующие возможности : установить низкий TTL, посылать пакет с инвалидной чексуммой, +добавлять tcp option "MD5 signature". Все они не лишены недостатков. + +* md5sig работает не на всех серверах. Пакеты с md5 обычно отбрасывают только linux. +* badsum не сработает, если ваше устройство за NAT, который не пропускает пакеты с инвалидной суммой. + Linux NAT по умолчанию их не пропускает без особой настройки "sysctl -w net.netfilter.nf_conntrack_checksum=0". + В openwrt она сделана из коробки, в других роутерах как правило нет, и не всегда это можно изменить. + Если nfqws работает на роутере, то не обязательно выключать nf_conntrack_checksum. Фейковый пакет не проходит FORWARD, он идет через OUTPUT. + Но если роутер за другим NAT, например провайдерским, и он не пропускает invalid packets, вы ничего не сможете с этим сделать. +* TTL казалось бы - лучший вариант, но он требует индивидуальной настройки под каждого провайдера. Если DPI находится дальше локальных + сайтов провайдера, то вы можете отрезать себе доступ к ним. Необходим ip exclude list, заполняемый вручную. + Вместе с ttl можно применять md5sig. Это ничего не испортит, зато дает неплохой шанс работы сайтов, до которых "плохой" пакет дойдет по TTL. + Если не удается найти автоматическое решение, воспользуйтесь файлом zapret-hosts-user-exclude.txt. + КАКИМ СТОИТ ВЫБИРАТЬ TTL : найдите минимальное значение, при котором обход еще работает. Это и будет номер хопа вашего DPI. + +Для режимов fake, rst, rstack после фейка отправляем оригинальный пакет. Можно его отправить сразу следом за фейком, а можно его просто дропнуть. +Если его дропнуть, ОС выполнит ретрансмиссию. Первая ретрансмиссия случается через 0.2 сек, потом задержка увеличивается экспоненциально. +Задержка может дать надежную гарантию, что пакеты пойдут именно в нужном порядке и будут именно в нем обработаны на DPI. +По умолчанию используется первый вариант, т.к. он быстрее. + +Режим disorder делит оригинальный пакет на 2 части и отправляет следующую комбинацию в указанном порядке : +1. 2-я часть пакета +2. поддельная 1-я часть пакета, поле данных заполнено нулями +3. 1-я часть пакета +4. поддельная 1-я часть пакета, поле данных заполнено нулями. отсылка 2-й раз. +Оригинальный пакет дропается всегда. Параметр --dpi-desync-split-pos позволяет указать байтовую позицию, на которой +происходит разбивка. По умолчанию - 3. Если позиция больше длины пакета, позиция выбирается 1. +Этой последовательностью для DPI максимально усложняется задача реконструкции начального сообщения, по которому принимается решение о блокировке. +Некоторым DPI хватит и tcp сегментов в неправильном порядке, поддельные части сделаны для дополнительной надежности и более сложных +алгоритмов реконструкции. + +hostlist относится только к атаке desync. он не работает для других параметров. при попытке запустить nfqws с hostlist и без dpi-desync будет ошибка. +Хосты извлекаются из Host: хедера обычных http запросов и из SNI в TLS ClientHello. +Субдомены учитываются автоматически. Поддерживаются листы gzip. + +iptables для задействования атаки : + +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 + +connbytes направит в очередь только первый пакет с данными. mark нужен, чтобы сгенерированный поддельный пакет не попал опять к нам на обработку. +nfqws выставляет fwmark при его отсылке. + +tpws +----- + +tpws - это transparent proxy. + --debug=0|1|2 ; Количество буковок в output : 0(default)=тихо, 1=подробно, 2=отладка + --daemon ; демонизировать прогу + --pidfile= ; сохранить PID в файл + --user= ; менять uid процесса + --uid=uid[:gid] ; менять uid процесса + --bind-addr ; на каком адресе слушать. может быть ipv4 или ipv6 адрес. если не указано, то слушает на всех адресах ipv4 и ipv6 + ; если указан ipv6 link local, то требуется указать с какого он интерфейса через --bind-iface6 + --bind-linklocal=prefer|force ; если prefer, то найти link local от iface6. если не найдено - использовать первый адрес любого типа. + ; если force и link local не найден - выход по ошибке. + --bind-iface4= ; слушать на первом ipv4 интерфейса iface + --bind-iface6= ; слушать на первом ipv6 интерфейса iface. при bind-linklocal определяет интерфейс, откуда брать ipv6 link local + --bind-wait-ifup= ; ждать до N секунд появления и поднятия интерфейса + --bind-wait-ip= ; ждать до N секунд получения IP адреса (если задан --bind-wait-ifup - время идет после поднятия интерфейса) + --bind-wait-ip-linklocal= ; (только если заданы --bind-wait-ip и --bind-linklocal=prefer) согласиться на global address после N секунд + --socks ; вместо прозрачного прокси реализовать socks4/5 proxy + --no-resolve ; запретить ресолвинг имен через socks5 + --port= ; на каком порту слушать + --maxconn= ; максимальное количество соединений от клиентов к прокси + --maxfiles= ; макс количество файловых дескрипторов (setrlimit). мин требование (X*connections+16), где X=6 в tcp proxy mode, X=4 в режиме тамперинга. + ; стоит сделать запас с коэффициентом как минимум 1.5. по умолчанию maxfiles (X*connections)*1.5+16 + --max-orphan-time=; если вы запускате через tpws торрент-клиент с множеством раздач, он пытается установить очень много исходящих соединений, + ; большая часть из которых отваливается по таймату (юзера сидят за NAT, firewall, ...) + ; установление соединения в linux может длиться очень долго. локальный конец отвалился, перед этим послав блок данных, + ; tpws ждет подключения удаленного конца, чтобы отослать ему этот блок, и зависает надолго. + ; настройка позволяет сбрасывать такие подключения через N секунд, теряя блок данных. по умолчанию 5 сек. 0 означает отключить функцию + ; эта функция не действует на успешно подключенные ранее соединения + + --local-rcvbuf= ; SO_RCVBUF для соединений client-proxy + --local-sndbuf= ; SO_SNDBUF для соединений client-proxy + --remote-rcvbuf= ; SO_RCVBUF для соединений proxy-target + --remote-sndbuf= ; SO_SNDBUF для соединений proxy-target + --skip-nodelay ; не устанавливать в исходящих соединения TCP_NODELAY. несовместимо со split. + + --split-http-req=method|host ; способ разделения http запросов на сегменты : около метода (GET,POST) или около заголовка Host + --split-pos= ; делить все посылы на сегменты в указанной позиции. Если отсыл длинее 8Kb (размер буфера приема), то будет разделен каждый блок по 8Kb. + --hostcase ; менять регистр заголовка "Host:". по умолчанию на "host:". + --hostspell=HoST ; точное написание заголовка Host (можно "HOST" или "HoSt"). автоматом включает --hostcase + --hostdot ; добавление точки после имени хоста : "Host: kinozal.tv." + --hosttab ; добавление табуляции после имени хоста : "Host: kinozal.tv\t" + --hostnospace ; убрать пробел после "Host:" + --hostpad= ; добавить паддинг-хедеров общей длиной перед Host: + --methodspace ; добавить пробел после метода : "GET /" => "GET /" + --methodeol ; добавить перевод строки перед методом : "GET /" => "\r\nGET /" + --unixeol ; конвертировать 0D0A в 0A и использовать везде 0A + --hostlist= ; действовать только над доменами, входящими в список из filename. поддомены автоматически учитываются. в файле должен быть хост на каждой строке. + ; список читается 1 раз при старте и хранится в памяти в виде иерархической структуры для быстрого поиска. + ; для списка РКН может потребоваться система с 128 Mb памяти ! расчитывайте требование RAM для процесса как 3-5 кратный размер файла списка. + ; по сигналу HUP список будет перечитан при следующем принятом соединении + ; список может быть запакован в gzip. формат автоматически распознается и разжимается + +Параметры манипуляции могут сочетаться в любых комбинациях. +Есть исключения : split-pos заменяет split-http-req. hostdot и hosttab взаимоисключающи. + +На прикладном уровне в общем случае нет гарантированного средства заставить ядро выплюнуть +блок данных, порезанным в определенном месте. ОС держит буфер отсылки (SNDBUF) у каждого сокета. +Если у сокета включена опция TCP_NODELAY и буфер пуст, то каждый send приводит к отсылке +отдельного ip пакета или группы пакетов, если блок не вмещается в один ip пакет. +Однако, если в момент send уже имеется неотосланный буфер, то ОС присоединит данные к нему, +никакой отсылки отдельным пакетом не будет. Но в этом случае и так нет никакой гарантии, +что какой-то блок сообщения пойдет в начале пакета, на что собственно и заточены DPI. +Разбиение будет производится согласно MSS, который зависит от MTU исходящего интерфейса. +Таким образом DPI, смотрящие в начало поля данных TCP пакета, будут поломаны в любом случае. +Протокол http относится к запрос-ответным протоколам. Новое сообщение посылается только тогда, +когда сервер получил запрос и полностью вернул ответ. Значит запрос фактически был не только отослан, +но и принят другой стороной, а следовательно буфер отсылки пуст, и следующие 2 send приведут +к отсылке сегментов данных разными ip пакетами. +Резюме : tpws гарантирует сплит только за счет раздельных вызовов send, что на практике +вполне достаточно для протоколов http(s). + +tpws может биндаться только к одному ip или ко всем сразу. +Для бинда на все ipv4 укажите "0.0.0.0", на все ipv6 - "::". Без параметров биндаемся на все ipv4 и ipv6. +Параметры --bind-wait* могут помочь в ситуациях, когда нужно взять IP с интерфейса, но его еще нет, он не поднят +или не сконфигурирован. +В разных системах события ifup ловятся по-разному и не гарантируют, что интерфейс уже получил IP адрес определенного типа. +В общем случае не существует единого механизма повеситься на событие типа "на интерфейсе X появился link local address". + +Параметры rcvbuf и sndbuf позволяют установить setsockopt SO_RCVBUF SO_SNDBUF для локального и удаленного соединения. + +Если не указан ни один из параметров модификации содержимого, tpws работает в режиме "tcp proxy mode". +Он отличается тем, что в оба конца применяется splice для переброски данных из одного сокета в другой +без копирования в память процесса. Практически - это то же самое, но может быть чуть побыстрее. +TCP проксирование может быть полезно для обхода блокировок, когда DPI спотыкается на экзотических +хедерах IP или TCP. Вы вряд ли сможете поправить хедеры, исходящие от айфончиков и гаджетиков, +но на linux сможете влиять на них в какой-то степени через sysctl. +Когда соединение проходит через tpws, фактически прокси-сервер сам устанавливает подключение к удаленному +узлу от своего имени, и на это распространяются настройки системы, на которой работает прокси. +tpws можно использовать на мобильном устройстве, раздающем интернет на тарифе сотового оператора, +где раздача запрещена, в socks режиме даже без рута. Соединения от tpws неотличимы от соединений +с самого раздающего устройства. Отличить можно только по содержанию (типа обновлений windows). +Заодно можно и обойти блокировки. 2 зайца одним выстрелом. +Более подробную информацию по вопросу обхода ограничений операторов гуглите на 4pda.ru. + +Режим "--socks" не требует повышенных привилегий (кроме бинда на привилегированные порты 1..1023). +Поддерживаются версии socks 4 и 5 без авторизации. Версия протокола распознается автоматически. +Подключения к IP того же устройства, на котором работает tpws, включая localhost, запрещены. +socks5 позволяет удаленно ресолвить хосты (curl : --socks5-hostname firefox : socks_remote_dns=true). +tpws поддерживает эту возможность, однако используется блокирующий ресолвинг. Пока система +ресолвит хост (это может занять секунды), вся активность останавливается. +tpws полностью работает на асинхронных сокетах, но ресолвинг может попортить эту модель. +С ним возможны атаки DoS на tpws. Если tpws обслуживает множество клиентов, то из-за частого +ресолвинга качество обслуживания может существенно ухудшиться. +Если удаленный ресолвинг создает проблемы, настройте клиенты на локальный ресолвинг, включите опцию +--no-resolve на стороне tpws. + +Параметр --hostpad= добавляет паддинг-хедеров перед Host: на указанное количество байтов. +Если размер слишком большой, то идет разбивка на разные хедеры по 2K. +Общий буфер приема http запроса - 64K, больший паддинг не поддерживается, да и http сервера +такое уже не принимают. +Полезно против DPI, выполняющих реассемблинг TCP с ограниченным буфером. +Если техника работает, то после некоторого количества bytes http запрос начнет проходить до сайта. +Если при этом критический размер padding около MTU, значит скорее всего DPI не выполняет реассемблинг пакетов, и лучше будет использовать обычные опции --split-… +Если все же реассемблинг выполняется, то критический размер будет около размера буфера DPI. Он может быть 4K или 8K, возможны и другие значения. + +--skip-nodelay может быть полезен, чтобы привести MTU к MTU системы, на которой работает tpws. +Это может быть полезно для скрытия факта использования VPN. Пониженный MTU - 1 из способов обнаружения +подозрительного подключения. С tcp proxy ваши соединения неотличимы от тех, что сделал бы сам шлюз. + +Способы получения списка заблокированных IP +------------------------------------------- + +1) Внесите заблокирванные домены в ipset/zapret-hosts-user.txt и запустите ipset/get_user.sh +На выходе получите ipset/zapret-ip-user.txt с IP адресами. + +Cкрипты с названием get_reestr_* оперируют дампом реестра заблокированных сайтов : + +2) ipset/get_reestr_resolve.sh получает список доменов от rublacklist и дальше их ресолвит в ip адреса +в файл ipset/zapret-ip.txt.gz. В этом списке есть готовые IP адреса, но судя во всему они там в точности в том виде, +что вносит в реестр РосКомПозор. Адреса могут меняться, позор не успевает их обновлять, а провайдеры редко +банят по IP : вместо этого они банят http запросы с "нехорошим" заголовком "Host:" вне зависимости +от IP адреса. Поэтому скрипт ресолвит все сам, хотя это и занимает много времени. +Используется мультипоточный ресолвер mdig (собственная разработка). +Реестр РКН уже настолько огромен, что однопоточный ресолв займет вечность, а многопоточный хоть и тоже много времени, +но хотя бы оно конечно. + +3) ipset/get_reestr_ip.txt +взять все IP адреса из реестра и загнать в ipset zapret/zapret6 +на роутерах с <128 Mb RAM может сработать только с TMPDIR на внешнем носителе + +4) ipset/get_reestr_combined.sh. для провайдеров, которые блокируют по IP https, а остальное по DPI. +IP https и IP без домена заносятся в ipset ipban, остальные в ipset zapret. +на роутерах с <128 Mb RAM может сработать только с TMPDIR на внешнем носителе + +Скрипты 3 и 4 убирают из списка мусор. На данный момент мусором считается миллион+ забаненых telegram proxy IP. +Это сокращает размер списка кардинально. Большой объем памяти требуется только для временного хранения скачанного +реестра в /tmp. Потом из списка уберется мусор, он станет значительно легче, поместится в ipset даже на 64 Mb RAM. +Поэтому поможет задание TMPDIR на флэшку. + +Cкрипты с названием get_antifilter_* оперируют списками адресов и масок подсетей с сайтов antifilter.network и antifilter.download : + +5) ipset/get_antifilter_ip.sh. получает лист https://antifilter.network/download/ip.lst. +лист содержит огромное количество ip (> миллиона). +на роутерах с <128 Mb RAM даже не пытайтесь + +7) ipset/get_antifilter_ipsmart.sh. получает лист https://antifilter.network/download/ipsmart.lst. +это умная суммаризация отдельных адресов из ip.lst по маскам от /32 до /22 +количество префиксов измеряется всего лишь десятками тысяч, потому это лучшее решение для роутера с 64 Mb RAM + +7) ipset/get_antifilter_ipsum.sh. получает лист https://antifilter.network/download/ipsum.lst. +это суммаризация отдельных адресов из ip.lst по маске /24 +количество префиксов измеряется всего лишь десятками тысяч, потому можно использовать на роутерах с 64 Mb RAM + +Все варианты рассмотренных скриптов автоматически создают и заполняют ipset. +Варианты 2-7 дополнительно вызывают вариант 1. + +8) ipset/get_config.sh. этот скрипт вызывает то, что прописано в переменной GETLIST из файла config +Если переменная не определена, то ресолвятся лишь листы для ipset nozapret/nozapret6. + +Листы РКН все время изменяются. Возникают новые тенденции. Требования к RAM могут меняться. +Поэтому необходима нечастая, но все же регулярная ревизия что же вообще у вас происходит на роутере. +Или вы можете узнать о проблеме лишь когда у вас начнет постоянно пропадать wifi, и вам придется +его перезагружать каждые 2 часа (метод кувалды). + +Листы zapret-ip.txt и zapret-ipban.txt сохраняются в сжатом виде в файлы .gz. +Это позволяет снизить их размер во много раз и сэкономить место на роутере. + +На роутерах не рекомендуется вызывать эти скрипты чаще раза за 2 суток, поскольку сохранение идет +либо во внутреннюю флэш память роутера, либо в случае extroot - на флэшку. +В обоих случаях слишком частая запись может убить флэшку, но если это произойдет с внутренней +флэш памятью, то вы просто убьете роутер. + +Принудительное обновление ipset выполняет скрипт ipset/create_ipset.sh. +Если передан параметр "no-update", скрипт не обновляет ipset, а только создает его при его отсутствии и заполняет. +Это полезно, когда могут случиться несколько последовательных вызовов скрипта. Нет смысла несколько раз перезаполнять +ipset, это длительная операция на больших листах. Листы можно обновлять раз в несколько суток, и только тогда +вызывать create_ipset без параметра "no-update". Во всех остальных случаях стоит применять "no-update". +Список РКН уже достиг внушительных размеров в сотни тысяч IP адресов. Поэтому для оптимизации ipset +применяется утилита ip2net. Она берет список отдельных IP адресов и пытается интеллектуально создать из него подсети для сокращения +количества адресов. ip2net написан на языке C, поскольку операция ресурсоемкая. Иные способы роутер может не потянуть. +Если ip2net скомпилирован или в каталог ip2net скопирован бинарик, то скрипт create_ipset.sh использует ipset типа hash:net, прогоняя список через ip2net. +В противном случае используется ipset типа hash:ip, список загружается как есть. +Соответственно, если вам не нравится ip2net, просто уберите из каталога ip2net бинарик. + +Можно внести список доменов в ipset/zapret-hosts-user-ipban.txt. Их ip адреса будут помещены +в отдельный ipset "ipban". Он может использоваться для принудительного завертывания всех +соединений на прозрачный proxy "redsocks" или на VPN. + +IPV6 : если включен ipv6, то дополнительно создаются листы с таким же именем, но с "6" на конце перед расширением. +zapret-ip.txt => zapret-ip6.txt +Создаются ipset-ы zapret6 и ipban6. +Листы с antifilter не содержат список ipv6 адресов. + +СИСТЕМА ИСКЛЮЧЕНИЯ IP. Все скрипты ресолвят файл zapret-hosts-user-exclude.txt, создавая zapret-ip-exclude.txt и zapret-ip-exclude6.txt. +Они загоняются в ipset-ы nozapret и nozapret6. Все правила, создаваемые init скриптами, создаются с учетом этих ipset. +Помещенные в них IP не участвуют в процессе. +zapret-hosts-user-exclude.txt может содержать домены, ipv4 и ipv6 адреса или подсети. + +ip2net +------ + +Утилита ip2net предназначена для преобразования ipv4 или ipv6 списка ip в список подсетей +с целью сокращения размера списка. Входные данные берутся из stdin, выходные выдаются в stdout. + -4 ; лист - ipv4 (по умолчанию) + -6 ; лист - ipv6 + --prefix-length=min[-max] ; диапазон рассматриваемых длин префиксов. например : 22-30 (ipv4), 56-64 (ipv6) + --v4-threshold=mul/div ; ipv4 : включать подсети, в которых заполнено по крайней мере mul/div адресов. например : 3/4 + --v6-threshold=N ; ipv6 : минимальное количество ip для создания подсети +В списке могут присутствовать записи вида ip/prefix и ip1-ip2. Такие записи выкидываются в stdout без изменений. +Они принимаются командой ipset. ipset умеет для листов hash:net из ip1-ip2 делать оптимальное покрытие ip/prefix. + +Выбирается подсеть, в которой присутствует указанный минимум адресов. +Для ipv4 минимум задается как процент от размера подсети (mul/div. например, 3/4), для ipv6 минимум задается напрямую. + +Размер подсети выбирается следующим алгоритмом : +Сначала в указанном диапазоне длин префиксов ищутся подсети, в которых количество адресов - максимально. +Если таких сетей найдено несколько, берется наименьшая сеть (префикс больше). +Например, заданы параметры v6_threshold=2 prefix_length=32-64, имеются следующие ipv6 : +1234:5678:aaaa::5 +1234:5678:aaaa::6 +1234:5678:aaac::5 +Результат будет : +1234:5678:aaa8::/45 +Эти адреса так же входят в подсеть /32. Однако, нет смысла проходиться ковровой бомбардировкой, +когда те же самые адреса вполне влезают в /45 и их ровно столько же. +Если изменить v6_threshold=4, то результат будет : +1234:5678:aaaa::5 +1234:5678:aaaa::6 +1234:5678:aaac::5 +То есть ip не объединятся в подсеть, потому что их слишком мало. +Если изменить prefix_length=56-64, результат будет : +1234:5678:aaaa::/64 +1234:5678:aaac::5 + +Требуемое процессорное время для вычислений сильно зависит от ширины диапазона длин префиксов, размера искомых подсетей и длины листа. +Если ip2net думает слишком долго, не используйте слишком большие подсети и уменьшите диапазон длин префиксов. +Учтите, что арифметика mul/div - целочисленная. При превышении разрядной сетки 32 bit результат непредсказуем. +Не надо делать такое : 5000000/10000000. 1/2 - гораздо лучше. + +Фильтрация по именам доменов +---------------------------- + +Альтернативой ipset является использование tpws со списком доменов. +tpws может читать только один hostlist. + +Поддерживаются 2 варианта : +1) Внесите домены для дурения в ipset/zapret-hosts-users.txt. Удалите ipset/zapret-hosts.txt.gz. +Тогда init скрипт будет запускать tpws с листом zapret-hosts-users.txt. + +2) Список доменов РКН может быть получен скриптом ipset/get_reestr_hostlist.sh - кладется в ipset/zapret-hosts.txt.gz. +Этот скрипт автоматически добавляет к списку РКН домены из zapret-hosts-user.txt. +init скрипт будет запускать tpws с листом zapret-hosts.txt.gz. + +При фильтрации по именам доменов tpws должен запускаться без фильтрации по ipset. +Весь трафик http идет через tpws, и он решает нужно ли применять дурение в зависимости от поля Host: в http запросе. +Это создает повышенную нагрузку на систему. +Сам поиск по доменам работает очень быстро, нагрузка связана с прокачиванием объема данных через процесс. +При использовании больших списков, в том числе списка РКН, оцените объем RAM на роутере ! +Если после запуска tpws RAM под завязку или случаются oom, значит нужно отказаться от таких больших списков. + +Проверка провайдера +------------------- + +Перед настройкой нужно провести исследование какую бяку устроил вам ваш провайдер. + +Нужно выяснить не подменяет ли он DNS и какой метод обхода DPI работает. +В этом вам поможет скрипт https://github.com/ValdikSS/blockcheck. + +Если DNS подменяется, но провайдер не перехватывает обращения к сторонним DNS, поменяйте DNS на публичный. +Например : 8.8.8.8, 8.8.4.4, 1.1.1.1, 1.0.0.1, 9.9.9.9 +Если DNS подменяется и провайдер перехватывает обращения к сторонним DNS, настройте dnscrypt. + +Проанализируйте какие методы дурения DPI работают, в соответствии с ними настройте /opt/zapret/config. + + +Выбор параметров +---------------- + +Файл /opt/zapret/config используется различными компонентами системы и содержит основные настройки. +Его нужно просмотреть и при необходимости отредактировать. +Выберите MODE : + +nfqws_ipset - использовать nfqws для модификации трафика на порт 80 только на IP из ipset "zapret" +nfqws_ipset_https - использовать nfqws для модификации трафика на порты 80 и 443 только на IP из ipset "zapret" +nfqws_all - использовать nfqws для модификации трафика на порт 80 для всех IP +nfqws_all_https - использовать nfqws для модификации трафика на порты 80 и 443 для всех IP +nfqws_all_desync - использовать nfqws для атаки десинхронизации DPI на http и https для всех IP +nfqws_ipset_desync - использовать nfqws для атаки десинхронизации DPI на http и https только на IP из ipset "zapret" +nfqws_hostlist_desync - использовать nfqws для атаки десинхронизации DPI на http и https только на хосты из hostlist. +tpws_ipset - использовать tpws для модификации трафика на порт 80 только на IP из ipset "zapret" +tpws_ipset_https - использовать tpws для модификации трафика на порты 80 и 443 только на IP из ipset "zapret" +tpws_all - использовать tpws для модификации трафика на порт 80 для всех IP +tpws_all_https - использовать tpws для модификации трафика на порты 80 и 443 для всех IP +tpws_hostlist - пропускать через tpws весь трафик на порт 80. tpws применяет дурение только к хостам из hostlist. +ipset - только заполнить ipset. ipset может быть применен для заворота трафика на прокси или на VPN +custom - нужно самому запрограммировать запуск демонов в init скрипте и правила iptables + +Можно изменить опции дурения, применяемые демонами nfqws и tpws : + +NFQWS_OPT="--wsize=3 --hostspell=HOST" +TPWS_OPT_HTTP="--hostspell=HOST --split-http-req=method" +TPWS_OPT_HTTPS="--split-pos=3" + +Отдельно настраиваются опции для атаки по десинхронизации DPI : + +DESYNC_MARK=0x40000000 +NFQWS_OPT_DESYNC="--dpi-desync --dpi-desync-ttl=0 --dpi-desync-fooling=badsum --dpi-desync-fwmark=$DESYNC_MARK" + + +Параметр GETLIST указывает инсталятору install_easy.sh какой скрипт дергать +для обновления списка заблокированных ip или хостов. +Он же вызывается через get_config.sh из запланированных заданий (crontab или systemd timer). +Поместите сюда название скрипта, который будете использовать для обновления листов. +Если не нужно, то параметр следует закомментировать. + +Можно индивидуально отключить ipv4 или ipv6. Если параметр закомментирован или не равен "1", +использование протокола разрешено. +#DISABLE_IPV4=1 +DISABLE_IPV6=1 + +Количество потоков для многопоточного DNS ресолвера mdig (1..100). +Чем их больше, тем быстрее, но не обидится ли на долбежку ваш DNS сервер ? +MDIG_THREADS=30 + +Место для хранения временных файлов. При скачивании огромных реестров в /tmp места может не хватить. +Если файловая система на нормальном носителе (не встроенная память роутера), то можно +указать место на флэшке или диске. +TMPDIR=/opt/zapret/tmp + +Опции для создания ipset-ов +IPSET_OPT="hashsize 262144 maxelem 2097152" +ПРО РУГАНЬ в dmesg по поводу нехватки памяти. +Может так случиться, что памяти в системе достаточно, но при попытке заполнить огромный ipset +ядро начинает громко ругаться, ipset заполняется не полностью. +Вероятная причина в том, что превышается hashsize, заданный при создании ipset (create_ipset.sh). +Происходит переаллокация списка, не находится непрерывных фрагментов памяти нужной длины. +Это лечится увеличением hashsize. Но чем больше hashsize, тем больше занимает ipset в памяти. +Задавать слишком большой hashsize для недостаточно больших списков нецелесообразно. + +Опции для вызова ip2net. Отдельно для листов ipv4 и ipv6. +IP2NET_OPT4="--prefix-length=22-30 --v4-threshold=3/4" +IP2NET_OPT6="--prefix-length=56-64 --v6-threshold=5" + +Следующие настройки не актуальны для openwrt : + +Если ваша система работает как роутер, то нужно вписать названия внутреннего и внешнего интерфейсов : +IFACE_LAN=eth0 +IFACE_WAN=eth1 +ВАЖНО : настройка маршрутизации , маскарада и т.д. не входит в задачу zapret. +Включаются только режимы, обеспечивающие перехват транзитного трафика. + +Параметр INIT_APPLY_FW=1 разрешает init скрипту самостоятельно применять правила iptables. +При иных значениях или если параметр закомментирован, правила применены не будут. +Это полезно, если у вас есть система управления фаерволом, в настройки которой и следует прикрутить правила. + +Прикручивание к системе управления фаерволом или своей системе запуска +---------------------------------------------------------------------- + +Если вы используете какую-то систему управления фаерволом, то она может вступать в конфликт +с имеющимся скриптом запуска. При повторном применении правил она могла бы поломать настройки iptables от zapret. +В этом случае правила для iptables должны быть прикручены к вашему фаерволу отдельно от запуска tpws или nfqws. + +Следующие вызовы позволяют применить или убрать правила iptables отдельно : + + /opt/zapret/init.d/sysv/zapret start-fw + /opt/zapret/init.d/sysv/zapret stop-fw + +А так можно запустить или остановить демоны отдельно от фаервола : + + /opt/zapret/init.d/sysv/zapret start-daemons + /opt/zapret/init.d/sysv/zapret stop-daemons + +Вариант custom +-------------- + +custom код вынесен в отдельный shell include +/opt/zapret/init.d/sysv/custom +или +/opt/zapret/init.d/openwrt/custom + +Нужно свой код вписать в функции : +zapret_custom_daemons +zapret_custom_firewall + +В файле custom пишите ваш код, пользуясь хелперами из "functions" или "zapret". +Смотрите как там сделано добавление iptables или запуск демонов. +Используя хелпер функции, вы избавитесь от необходимости учитывать все возможные случаи +типа наличия/отсутствия ipv6, является ли система роутером, имена интерфейсов, ... +Хелперы это учитывают , вам нужно сосредоточиться лишь на фильтрах iptables и +параметрах демонов. + +Код для openwrt и sysv немного отличается. В sysv нужно обрабатывать и запуск, и остановку. +Запуск это или остановка передается в параметре $1 (0 или 1). +В openwrt за остановку демонов отвечает procd, а firewall вычищается при "fw3 restart", +потому нет необходимости реализовывать логику останова. + +При апгрейде нужно сохранить лишь custom, другие файлы править не надо. + +Пример установки на debian-подобную систему +------------------------------------------- + +На debian основано большое количество дистрибутивов linux, включая ubuntu. +Здесь рассматриваются прежде всего Debian 8+ и Ubuntu 16+. +Но с большой вероятностью может сработать и на производных от них. +Главное условие - наличие systemd, apt и нескольких стандартных пакетов в репозитории. + +Установить пакеты : + apt-get update + apt-get install ipset curl dnsutils git + +Скопировать директорию zapret в /opt или скачать через git : + cd /opt + git clone --depth 1 https://github.com/bol-van/zapret + +Запустить автоинсталятор бинариков. Он сам определит рабочую архитектуру и настроит все бинарики. + /opt/zapret/install_bin.sh +АЛЬТЕРНАТИВА : make -C /opt/zapret. Получите динамические бинарики под вашу ось. +Для сборки требуются dev пакеты : zlib1g-dev libcap-dev libnetfilter-queue-dev + +Настроить параметры согласно разделу "Выбор параметров". + +Создать ссылку на service unit в systemd : + ln -fs /opt/zapret/init.d/systemd/zapret.service /lib/systemd/system + +Удалить старые листы, если они были созданы ранее : + /opt/zapret/ipset/clear_lists.sh +По желанию прописать в /opt/zapret/ipset/zapret-hosts-user.txt свои домены. +Выполнить скрипт обновления листа : + /opt/zapret/ipset/get_config.sh +Настроить таймер systemd для обновления листа : + ln -fs /opt/zapret/init.d/systemd/zapret-list-update.service /lib/systemd/system + ln -fs /opt/zapret/init.d/systemd/zapret-list-update.timer /lib/systemd/system + +Принять изменения в systemd : + systemctl daemon-reload + +Включить автозапуск службы : + systemctl enable zapret + +Включить таймер обновления листа : + systemctl enable zapret-list-update.timer + +Запустить службу : + systemctl start zapret + +Попробовать зайти куда-нибудь : http://ej.ru, http://kinozal.tv, http://grani.ru. +Если не работает, то остановить службу zapret, добавить правило в iptables вручную, +запустить nfqws или tpws в терминале под рутом с нужными параметрами. +Пытаться подключаться к заблоченым сайтам, смотреть вывод программы. +Если нет никакой реакции, значит скорее всего указаны неверные параметры или ip назначения нет в ipset. +Если реакция есть, но блокировка не обходится, значит параметры обхода подобраны неверно, или это средство +не работает в вашем случае на вашем провайдере. +Никто и не говорил, что это будет работать везде. +Попробуйте снять дамп в wireshark или "tcpdump -vvv -X host ", посмотрите действительно ли первый +сегмент TCP уходит коротким и меняется ли регистр "Host:". + +Шпаргалка по управлению службой и таймером : + +enable auto start : systemctl enable zapret +disable auto start : systemctl disable zapret +start : sytemctl start zapret +stop : systemctl stop zapret +status, output messages : systemctl status zapret +timer info : systemctl list-timer +delete service : systemctl disable zapret ; rm /lib/systemd/system/zapret.service +delete timer : systemctl disable zapret-list-update.timer ; rm /lib/systemd/system/zapret-list-update.* + +Centos 7+, Fedora +----------------- + +Centos с 7 версии и более-менее новые федоры построены на systemd. +В качестве пакетного менеджера используется yum. + +Установить пакеты : + yum install -y curl ipset dnsutils git + +Далее все аналогично debian. + +OpenSUSE +-------- + +Новые OpenSUSE основаны на systemd и менеджере пакетов zypper. + +Установить пакеты : + zypper --non-interactive install curl ipset + +Далее все аналогично debian, кроме расположения systemd. +В opensuse он находится не в /lib/systemd, а в /usr/lib/systemd. +Правильные команды будут : + + ln -fs /opt/zapret/init.d/systemd/zapret.service /usr/lib/systemd/system + ln -fs /opt/zapret/init.d/systemd/zapret-list-update.service /usr/lib/systemd/system + ln -fs /opt/zapret/init.d/systemd/zapret-list-update.timer /usr/lib/systemd/system + +Arch linux +---------- + +Построен на базе systemd. + +Установить пакеты : + pacman -Syy + pacman --noconfirm -S ipset curl + +Далее все аналогично debian. + +Gentoo +------ + +Эта система использует OpenRC - улучшенную версию sysvinit. +Установка пакетов производится командой : emerge +Пакеты собираются из исходников. + +Требуются все те же ipset, curl, git для скачивания с github. +git и curl по умолчанию могут присутствовать, ipset отсутствует. + + emerge ipset + +Настроить параметры согласно разделу "Выбор параметров". + +Запустить автоинсталятор бинариков. Он сам определит рабочую архитектуру и настроит все бинарики. + /opt/zapret/install_bin.sh +АЛЬТЕРНАТИВА : make -C /opt/zapret. Получите динамические бинарики под вашу ось. + +Удалить старые листы, если они были созданы ранее : + /opt/zapret/ipset/clear_lists.sh +По желанию прописать в /opt/zapret/ipset/zapret-hosts-user.txt свои домены. +Выполнить скрипт обновления листа : + /opt/zapret/ipset/get_config.sh +Зашедулить обновление листа : + crontab -e + Создать строчку "0 12 */2 * * /opt/zapret/ipset/get_config.sh" + +Подключить init скрипт : + + ln -fs /opt/zapret/init.d/sysv/zapret /etc/init.d + rc-update add zapret + +Запустить службу : + + rc-service zapret start + +Шпаргалка по управлению службой : + +enable auto start : rc-update add zapret +disable auto start : rc-update del zapret +start : rc-service zapret start +stop : rc-service zapret stop + +Простая установка +----------------- + +Ты простой юзер ? Не хочешь ни во что вникать, а хочешь нажать и чтобы сразу заработало ? +Пользуешься системой на базе systemd ? Тогда этот вариант для тебя. +Есть шансы, что оно заработает с минимумом усилий. Запусти терминал и в нем вбивай команды : + +# su +<введи пароль рута> +# apt-get update +# apt-get install git +# cd /opt +# git clone --depth 1 https://github.com/bol-van/zapret +# zapret/install_easy.sh + +Надоело ? + +# /opt/zapret/uninstall_easy.sh +# rm -r /opt/zapret + +Это самый необходимый миниум действий. +Если zapret уже куда-то был скачан, то можно запустить install_easy.sh прямо оттуда. +Инсталятор сам запросит рута, скопирует файлы куда надо. +Можно даже запускать инсталятор из "проводника" через функцию "запустить в терминале", если +"проводник" такое позволяет. + +Для более гибкой настройки перед запуском инсталятора следует выполнить раздел "Выбор параметров". + +Если система на базе systemd, но используется не поддерживаемый инсталятором менеджер пакетов +или названия пакетов не соответствуют прописанным в инсталятор, пакеты нужно установить вручную. +Требуется : ipset curl + +ВАЖНО : Хоть инсталятор и спрашивает является ли система роутером, +настройка маршрутизации , маскарада и т.д. не входит в задачу zapret. +Роутер вдруг сам волшебно не поднимется. Предполагается, что роутер вы уже настроили сами. + +В комплекте идут статические бинарики для большинства архитектур. Какой-то из них подойдет +с вероятностью 99%. Но если у вас экзотическая система, инсталятор попробует собрать бинарики сам +через make. Для этого нужны gcc, make и необходимые -dev пакеты. Можно форсировать режим +компиляции следующим вызовом : + + install_easy.sh make + + +openwrt/LEDE +------------ + +Установить дополнительные пакеты : +opkg update +opkg install iptables-mod-extra iptables-mod-nfqueue iptables-mod-filter iptables-mod-ipopt iptables-mod-conntrack-extra ipset curl +(ipv6) opkg install ip6tables-mod-nat +(опционально) opkg install gzip +(опционально) opkg install grep + +ЭКОНОМИЯ МЕСТА : + +gzip от busybox в разы медленней полноценного варианта. gzip используется скриптами получения листов. +grep от busybox катастрофически медленный с опцией -f. она применяется в get_reestr_combined.sh. если вы не собираетесь +пользоваться этим скриптом, gnu grep можно не устанавливать +iptables-mod-nfqueue можно выкинуть, если не будем пользоваться nfqws +curl можно выкинуть, если для получения ip листа будет использоваться только get_user.sh +ipset можно выкинуть, если не будем пользоваться ipset-тами, а будем, например, использовать tpws +со списком доменов. + +Самая главная трудность - скомпилировать программы на C. Это можно сделать на linux x64 при помощи SDK, который +можно скачать с официального сайта openwrt или LEDE. Но процесс кросс компиляции - это всегда сложности. +Недостаточно запустить make как на традиционной linux системе. +Поэтому в binaries имеются готовые статические бинарики для всех самых распространенных архитектур. +Статическая сборка означает, что бинарик не зависит от типа libc (glibc, uclibc или musl) и наличия установленных so. +Его можно использовать сразу. Лишь бы подходил тип CPU. У ARM и MIPS есть несколько версий. +Скорее всего найдется рабочий вариант. Если нет - вам придется собирать самостоятельно. +Для всех поддерживаемых архитектур бинарики запакованы upx. На текущий момент все, кроме mips64. + +Скопировать директорию "zapret" в /opt на роутер. + +Если места достаточно, самый простой способ : + opkg update + opkg install git-http + mkdir /opt + cd /opt + git clone --depth 1 https://github.com/bol-van/zapret + +Если места немного : + opkg update + opkg install openssh-sftp-server unzip + ifconfig br-lan +Скачать на комп с github zip архив кнопкой "Clone or download"->Download ZIP +Скопировать средствами sftp zip архив на роутер в /tmp. + mkdir /opt + cd /opt + unzip /tmp/zapret-master.zip + mv zapret-master zapret + rm /tmp/zapret-master.zip + +Если места совсем мало : + cd /tmp + nc -l -p 1111 >zapret.tar.gz +На linux системе скачать и распаковать zapret. Оставить необходимый минимум файлов. +Запаковать в архив zapret.tar.gz. + md5sum zapret.tar.gz + nc 1111 /{tpws,nfqws,ip2net,mdig} + +После успешной установки можно удалить zapret из tmp для освобождения RAM : + rm -r /tmp/zapret + +Для более гибкой настройки перед запуском инсталятора следует выполнить раздел "Выбор параметров". + +Android +------- + +Без рута забудьте про nfqws и tpws в режиме transparent proxy. tpws будет работать только в режиме --socks. + +Статистики наличия NFQUEUE в стоковых ядрах android у меня нет, но на первом попавшемся устройстве на базе MTK он есть. +Если NFQUEUE есть, то nfqws проверен - он работает. + +В стоковых ядрах нет поддержки ipset. В общем случае сложность задачи по поднятию ipset варьируется от +"не просто" до "почти невозможно". Если только вы не найдете готовое собранное ядро под ваш девайс. + +tpws будет работать в любом случае, он не требует чего-либо особенного. +В android нет /etc/passwd, потому опция --user не будет работать. Вместо нее можно +пользоваться числовыми user id и опцией --uid. +Рекомендую использовать gid 3003 (AID_INET). Иначе можете получить permission denied на создание сокета. +Например : --uid 1:3003 +В iptables укажите : "! --uid-owner 1" вместо "! --uid-owner tpws". +Напишите шелл скрипт с iptables и tpws, запускайте его средствами вашего рут менеджера. +Скрипты автозапуска лежат тут : +magisk : /data/adb/service.d +supersu : /system/su.d + +Я не проверял не прибивают ли новые андроиды iptables по своей прихоти в процессе работы +или при подключении/отключении wifi, mobile data, ... + +Кто силен в разработке под андроид, думаю будет несложно написать простую програмулину +с выбором опций tpws, которая сможет выбрать tpws нужной архитектуры, запустить его +и добавить iptables. Чтобы была возможность автозапуска (после нее применяем настройки +и сразу exit). И чтобы были кнопки start/stop. +Если кто готов такую програмулину написать - все скажут спасибо. +Тогда любой желающий с рутом сможет парой кликов настроить себе tpws. + +Другие прошивки +--------------- + +Для статических бинариков не имеет значения на чем они запущены : PC, android, приставка, роутер, любой другой девайс. +Подойдет любая прошивка, дистрибутив linux. Статические бинарики запустятся на всем. +Им нужно только ядро с необходимыми опциями сборки или модулями. +Но кроме бинариков в проекте используются еще и скрипты, в которых задействуются некоторые +стандартные программы. + +ЗАМЕЧАНИЕ. Как показала практика, на некоторых ядрах бинарики с upx падают в segfault. +Если это ваш случай, скачайте upx и распакуйте бинарики. Распаковать можно на любой системе и любой архитектуре. + +Основные причины почему нельзя просто так взять и установить эту систему на что угодно : + * отсутствие доступа к девайсу через shell + * отсутствие рута + * отсутствие раздела r/w для записи и энергонезависимого хранения файлов + * отсутствие возможности поставить что-то в автозапуск + * отсутствие cron + * недостаток модулей ядра или опций его сборки + * недостаток модулей iptables (/usr/lib/iptables/lib*.so) + * недостаток стандартных программ (типа ipset, curl) или их кастрированность (облегченная замена) + * кастрированный или нестандартный шелл sh + +Если в вашей прошивке есть все необходимое, то вы можете адаптировать zapret под ваш девайс в той или иной степени. +Может быть у вас не получится поднять все части системы, однако вы можете хотя бы попытаться +поднять tpws и завернуть на него через -j REDIRECT весь трафик на порт 80. +Если вам есть куда записать tpws, есть возможность выполнять команды при старте, то как минимум +это вы сделать сможете. Скорее всего поддержка REDIRECT в ядре есть. Она точно есть на любом роутере, +на других устройствах под вопросом. NFQUEUE, ipset на большинстве прошивок отсутствуют из-за ненужности. + +Пересобрать ядро или модули для него будет скорее всего достаточно трудно. +Для этого вам необходимо будет по крайней мере получить исходники вашей прошивки. +User mode компоненты могут быть привнесены относительно безболезненно, если есть место куда их записать. +Специально для девайсов, имеющих область r/w, существует проект entware. +Некоторые прошивки даже имеют возможность его облегченной установки через веб интерфейс. +entware содержит репозиторий user-mode компонент, которые устанавливаются в /opt. +С их помощью можно компенсировать недостаток ПО основной прошивки, за исключением ядра. + +Подробное описание настроек для других прошивок выходит за рамки данного проекта. + +Openwrt является одной из немногих относительно полноценных linux систем для embedded devices. +Она характеризуется следующими вещами, которые и послужили основой выбора именно этой прошивки : + * полный root доступ к девайсу через shell. на заводских прошивках чаще всего отсутствует, на многих альтернативных есть + * корень r/w. это практически уникальная особенность openwrt. заводские и большинство альтернативных прошивок + построены на базе squashfs root (r/o), а конфигурация хранится в специально отформатированной области + встроенной памяти, называемой nvram. не имеющие r/w корня системы сильно кастрированы. они не имеют + возможности доустановки ПО из репозитория без специальных вывертов и заточены в основном + на чуть более продвинутого, чем обычно, пользователя и управление имеющимся функционалом через веб интерфейс, + но функционал фиксированно ограничен. альтернативные прошивки как правило могут монтировать r/w раздел + в какую-то область файловой системы, заводские обычно могут монтировать лишь флэшки, подключенные к USB, + и не факт, что есть поддержка unix файловых системы. может быть поддержка только fat и ntfs. + * возможность выноса корневой файловой системы на внешний носитель (extroot) или создания на нем оверлея (overlay) + * наличие менеджера пакетов opkg и репозитория софта + * в репозитории есть все модули ядра, их можно доустановить через opkg. ядро пересобирать не нужно. + * в репозитории есть все модули iptables, их можно доустановить через opkg + * в репозитории есть огромное количество стандартных программ и дополнительного софта + * наличие SDK, позволяющего собрать недостающее + +Обход блокировки https +---------------------- + +Как правило трюки с DPI не помогают для обхода блокировки https. +Приходится перенаправлять трафик через сторонний хост. +Предлагается использовать прозрачный редирект через socks5 посредством iptables+redsocks, либо iptables+iproute+vpn. +Настройка варианта с redsocks на openwrt описана в https.txt. +Настройка варианта с iproute+wireguard - в wireguard_iproute_openwrt.txt. + +ИНОГДА (но нечасто) работает трюк со сплитом tls handshake на 2 части. +Это можно сделать все теми же средствами. nfqws или tpws с параметром --split-pos. +--split-pos - единственный параметр, который работает на не-HTTP трафике, все остальное работать не будет. +Попробуйте, может вам повезет. + +ЧАЩЕ на https работает атака DPI desync, но у нее есть свои нюансы настройки. + +Почему стоит вложиться в покупку VPS +------------------------------------ + +VPS - это виртуальный сервер. Существует огромное множество датацентров, предлагающих данную услугу. +На VPS могут выполняться какие угодно задачи. От простого веб сайта до навороченной системы собственной разработки. +Можно использовать VPS и для поднятия собственного vpn или прокси. +Сама широта возможных способов применения , распространенность услуги сводят к минимуму возможности +регуляторов по бану сервисов такого типа. Да, если введут белые списки, то решение загнется, но это будет уже другая +реальность, в которой придется изобретать иные решения. +Пока этого не сделали, никто не будет банить хостинги просто потому , что они предоставляют хостинг услуги. +Вы как индивидуум скорее всего никому не нужны. Подумайте чем вы отличаетесь от известного VPN провайдера. +VPN провайдер предоставляет _простую_ и _доступную_ услугу по обходу блокировок для масс. +Этот факт делает его первоочередной целью блокировки. РКН направит уведомление, после отказа сотрудничать +заблокирует VPN. Предоплаченная сумма пропадет. +У регуляторов нет и никогда не будет ресурсов для тотальной проверки каждого сервера в сети. +Возможен китайский расклад, при котором DPI выявляет vpn протоколы и динамически банит IP серверов, +предоставляющих нелицензированный VPN. Но имея знания, голову, вы всегда можете обфусцировать +vpn трафик или применить другие типы VPN, более устойчивые к анализу на DPI или просто менее широкоизвестные, +а следовательно с меньшей вероятностью обнаруживамые регулятором. +У вас есть свобода делать на вашем VPS все что вы захотите, адаптируясь к новым условиям. +Да, это потребует знаний. Вам выбирать учиться и держать ситуацию под контролем, когда вам ничего запретить +не могут, или покориться системе. + +VPS можно прибрести в множестве мест. Существуют специализированные на поиске предложений VPS порталы. +Например, вот этот : https://vps.today/ +Для персонального VPN сервера обычно достаточно самой минимальной конфигурации, но с безлимитным трафиком или +с большим лимитом по трафику (терабайты). Важен и тип VPS. Openvz подойдет для openvpn, но +вы не поднимете на нем wireguard, ipsec, то есть все, что требует kernel mode. +Для kernel mode требуется тип виртуализации, предполагающий запуск полноценного экземпляра ОС linux +вместе с ядром. Подойдут kvm, xen, hyper-v, vmware. + +По цене можно найти предложения, которые будут дешевле готовой VPN услуги, но при этом вы сам хозяин в своей лавке +и не рискуете попасть под бан регулятора, разве что "заодно" под ковровую бомбардировку с баном миллионов IP. +Кроме того, если вам совсем все кажется сложным, прочитанное вызывает ступор, и вы точно знаете, что ничего +из описанного сделать не сможете, то вы сможете хотя бы использовать динамическое перенаправление портов ssh +для получения шифрованного socks proxy и прописать его в броузер. Знания linux не нужны совсем. +Это вариант наименее напряжный для чайников, хотя и не самый удобный в использовании. diff --git a/docs/wireguard/010-wg-mod.patch b/docs/wireguard/010-wg-mod.patch new file mode 100644 index 0000000..1577da6 --- /dev/null +++ b/docs/wireguard/010-wg-mod.patch @@ -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 + #include + ++ ++// 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(>rash, 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 */ diff --git a/docs/wireguard/wireguard-mod.txt b/docs/wireguard/wireguard-mod.txt new file mode 100644 index 0000000..5cb7e3f --- /dev/null +++ b/docs/wireguard/wireguard-mod.txt @@ -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(>rash, 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 . 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 модуль может не загрузиться +или вызвать стабильные или хаотические падения ядра и перезагрузки (включая вариант беcконечной перезагрузки - bootloop). +Так что перед --force-depends убедитесь, что знаете как лечится такая ситуация, и не стоит это делать при отсутствии физического +доступа к девайсу. + +Когда поднимите линк, и вдруг ничего не будет работать, то посмотрите в wireshark udp пакеты +на порт endpoint. Они не должны начинаться с 0,1,2,3,4. В первых 4 байтах должен быть рандом, +в следующих 4 байтах - значения из измененного enum message_type. Если пакет все еще начинается с 0..4, +значит модуль wireguard оригинальный, что-то не собралось, не скопировалось, не перезапустилось. +В противном случае должен подняться линк, пинги ходить. Значит вы победили, поздравляю. +Регулятору будет намного сложнее поймать ваш VPN. diff --git a/docs/wireguard/wireguard_iproute_openwrt.txt b/docs/wireguard/wireguard_iproute_openwrt.txt new file mode 100644 index 0000000..9e7aa5a --- /dev/null +++ b/docs/wireguard/wireguard_iproute_openwrt.txt @@ -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. В умелых руках творят чудеса. diff --git a/init.d/openwrt/90-zapret b/init.d/openwrt/90-zapret new file mode 100644 index 0000000..44384f7 --- /dev/null +++ b/init.d/openwrt/90-zapret @@ -0,0 +1,8 @@ +#!/bin/sh + +ZAPRET=/etc/init.d/zapret +[ -x "$ZAPRET" ] && [ "$INTERFACE" = "lan" ] && { + [ "$ACTION" = "ifup" ] && { + $ZAPRET enabled && $ZAPRET start + } +} diff --git a/init.d/openwrt/custom b/init.d/openwrt/custom new file mode 100644 index 0000000..2136b74 --- /dev/null +++ b/init.d/openwrt/custom @@ -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 +} diff --git a/init.d/openwrt/firewall.zapret b/init.d/openwrt/firewall.zapret new file mode 100644 index 0000000..3ed9946 --- /dev/null +++ b/init.d/openwrt/firewall.zapret @@ -0,0 +1,3 @@ +. /opt/zapret/init.d/openwrt/functions + +zapret_apply_firewall diff --git a/init.d/openwrt/functions b/init.d/openwrt/functions new file mode 100644 index 0000000..b35ff8e --- /dev/null +++ b/init.d/openwrt/functions @@ -0,0 +1,320 @@ +. /lib/functions/network.sh + +[ -n "$ZAPRET_BASE" ] || ZAPRET_BASE=/opt/zapret +. "$ZAPRET_BASE/config" + +QNUM=200 +TPPORT_HTTP=1188 +TPPORT_HTTPS=1189 +TPWS_USER=daemon +[ -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 "$@" +} +ipt6() +{ + ip6tables -C "$@" 2>/dev/null || ip6tables -I "$@" +} + +# 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 127.0.0.1:$2 + done + ipt prerouting_lan_rule -t nat -p tcp $1 $IPSET_EXCLUDE dst -j DNAT --to 127.0.0.1:$2 + network_get_device DEVICE lan + 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 +} + + +create_ipset() +{ + echo "Creating ipset" + "$IPSET_CR" "$@" +} + + +zapret_apply_firewall() +{ + local rule + local synack="--tcp-flags SYN,ACK SYN,ACK" + local ipset_zapret="-m set --match-set zapret" + local ipset_zapret6="-m set --match-set zapret6" + local desync="-m multiport --dports 80,443 -m connbytes --connbytes-dir=original --connbytes-mode=packets --connbytes 2:4 -m mark ! --mark $DESYNC_MARK/$DESYNC_MARK" + + # always create ipsets. ip_exclude ipset is required + create_ipset no-update + + case "${MODE}" in + tpws_hostlist|tpws_all) + fw_tpws "--dport 80" "--dport 80" $TPPORT_HTTP + ;; + tpws_ipset) + fw_tpws "--dport 80 $ipset_zapret dst" "--dport 80 $ipset_zapret6 dst" $TPPORT_HTTP + ;; + tpws_ipset_https) + fw_tpws "--dport 80 $ipset_zapret dst" "--dport 80 $ipset_zapret6 dst" $TPPORT_HTTP + fw_tpws "--dport 443 $ipset_zapret dst" "--dport 443 $ipset_zapret6 dst" $TPPORT_HTTPS + ;; + tpws_all_https) + fw_tpws "--dport 80" "--dport 80" $TPPORT_HTTP + fw_tpws "--dport 443" "--dport 443" $TPPORT_HTTPS + ;; + nfqws_ipset) + fw_nfqws_pre "--sport 80 $synack $ipset_zapret src" "--sport 80 $synack $ipset_zapret6 src" $QNUM + fw_nfqws_post "--dport 80 $ipset_zapret dst" "--dport 80 $ipset_zapret6 dst" $QNUM + ;; + nfqws_ipset_https) + rule="-m multiport --sports 80,443 $synack" + fw_nfqws_pre "$rule $ipset_zapret src" "$rule $ipset_zapret6 src" $QNUM + fw_nfqws_post "--dport 80 $ipset_zapret dst" "--dport 80 $ipset_zapret6 dst" $QNUM + ;; + nfqws_all) + fw_nfqws_pre "--sport 80 $synack" "--sport 80 $synack" $QNUM + fw_nfqws_post "--dport 80" "--dport 80" $QNUM + ;; + nfqws_all_https) + rule="-m multiport --sports 80,443 $synack" + fw_nfqws_pre "$rule" "$rule" $QNUM + fw_nfqws_post "--dport 80" "--dport 80" $QNUM + ;; + nfqws_all_desync|nfqws_hostlist_desync) + rule="-m multiport --sports 80,443 $synack" + fw_nfqws_pre "$rule" "$rule" $QNUM + fw_nfqws_post "$desync" "$desync" $QNUM + ;; + nfqws_ipset_desync) + rule="-m multiport --sports 80,443 $synack" + fw_nfqws_pre "$rule $ipset_zapret src" "$rule $ipset_zapret6 src" $QNUM + fw_nfqws_post "$desync $ipset_zapret dst" "$desync $ipset_zapret6 dst" $QNUM + ;; + custom) + existf zapret_custom_firewall && zapret_custom_firewall + ;; + esac +} diff --git a/init.d/openwrt/zapret b/init.d/openwrt/zapret new file mode 100755 index 0000000..2b57d6c --- /dev/null +++ b/init.d/openwrt/zapret @@ -0,0 +1,90 @@ +#!/bin/sh /etc/rc.common + +USE_PROCD=1 +# after network +START=21 + +ZAPRET_BASE=/opt/zapret +. "$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_HTTP=1188 +TPPORT_HTTPS=1189 +TPWS="$ZAPRET_BASE/tpws/tpws" +HOSTLIST="$ZAPRET_BASE/ipset/zapret-hosts.txt.gz" +[ -f "$HOSTLIST" ] || HOSTLIST="$ZAPRET_BASE/ipset/zapret-hosts-user.txt" +TPWS_OPT_BASE="--user=$TPWS_USER --bind-addr=127.0.0.1" +TPWS_OPT_BASE6="--user=$TPWS_USER --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="--user=$TPWS_USER --bind-linklocal=prefer --bind-wait-ifup=30 --bind-wait-ip=30 --bind-wait-ip-linklocal=3" +TPWS_OPT_BASE_HTTP="--port=$TPPORT_HTTP" +TPWS_OPT_BASE_HTTPS="--port=$TPPORT_HTTPS" + +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" ] || run_daemon $1 $TPWS "$TPWS_OPT_BASE $2" + [ "$DISABLE_IPV6" = "1" ] || { + run_daemon $((60+$1)) $TPWS "$TPWS_OPT_BASE6 $2" + network_get_device DEVICE lan + [ -n "$DEVICE" ] && run_daemon $((660+$1)) $TPWS "$TPWS_OPT_BASE6_PRE --bind-iface6=$DEVICE $2" + } +} +stop_tpws() +{ + [ "$DISABLE_IPV4" = "1" ] || stop_daemon $1 $TPWS + [ "$DISABLE_IPV6" = "1" ] || { + stop_daemon $((60+$1)) $TPWS + stop_daemon $((660+$1)) $TPWS + } +} + + +start_service() { + case "${MODE}" in + tpws_hostlist) + run_tpws 1 "$TPWS_OPT_BASE_HTTP $TPWS_OPT_HTTP --hostlist=$HOSTLIST" + ;; + tpws_ipset|tpws_all) + run_tpws 1 "$TPWS_OPT_BASE_HTTP $TPWS_OPT_HTTP" + ;; + tpws_ipset_https|tpws_all_https) + run_tpws 1 "$TPWS_OPT_BASE_HTTP $TPWS_OPT_HTTP" + run_tpws 2 "$TPWS_OPT_BASE_HTTPS $TPWS_OPT_HTTPS" + ;; + nfqws_ipset|nfqws_ipset_https|nfqws_all|nfqws_all_https) + run_daemon 1 $NFQWS "$NFQWS_OPT_BASE $NFQWS_OPT" + ;; + nfqws_ipset_desync|nfqws_all_desync) + run_daemon 1 $NFQWS "$NFQWS_OPT_BASE $NFQWS_OPT_DESYNC" + ;; + nfqws_hostlist_desync) + run_daemon 1 $NFQWS "$NFQWS_OPT_BASE $NFQWS_OPT_DESYNC --hostlist=$HOSTLIST" + ;; + custom) + existf zapret_custom_daemons && zapret_custom_daemons $1 + ;; + esac +} diff --git a/init.d/systemd/zapret-list-update.service b/init.d/systemd/zapret-list-update.service new file mode 100644 index 0000000..eeee1b0 --- /dev/null +++ b/init.d/systemd/zapret-list-update.service @@ -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 diff --git a/init.d/systemd/zapret-list-update.timer b/init.d/systemd/zapret-list-update.timer new file mode 100644 index 0000000..29379bd --- /dev/null +++ b/init.d/systemd/zapret-list-update.timer @@ -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 diff --git a/init.d/systemd/zapret.service b/init.d/systemd/zapret.service new file mode 100644 index 0000000..9d3bf41 --- /dev/null +++ b/init.d/systemd/zapret.service @@ -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 diff --git a/init.d/sysv/custom b/init.d/sysv/custom new file mode 100644 index 0000000..ed6183b --- /dev/null +++ b/init.d/sysv/custom @@ -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 +} diff --git a/init.d/sysv/functions b/init.d/sysv/functions new file mode 100644 index 0000000..197939c --- /dev/null +++ b/init.d/sysv/functions @@ -0,0 +1,486 @@ +# 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_HTTP=1188 +TPPORT_HTTPS=1189 +TPWS="$ZAPRET_BASE/tpws/tpws" +HOSTLIST="$ZAPRET_BASE/ipset/zapret-hosts.txt.gz" +[ -f "$HOSTLIST" ] || HOSTLIST="$ZAPRET_BASE/ipset/zapret-hosts-user.txt" +TPWS_OPT_BASE="--user=$WS_USER --bind-addr=127.0.0.1" +TPWS_OPT_BASE6="--user=$WS_USER --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="--user=$WS_USER --bind-linklocal=prefer --bind-wait-ifup=30 --bind-wait-ip=30 --bind-wait-ip-linklocal=3" +TPWS_OPT_BASE_HTTP="--port=$TPPORT_HTTP" +TPWS_OPT_BASE_HTTPS="--port=$TPPORT_HTTPS" + +[ -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 /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 +} +prepare_tpws() +{ + prepare_user + + # otherwise linux kernel will treat 127.0.0.1 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.1 + [ -n "$IFACE_LAN" ] && sysctl -qw net.ipv4.conf.$IFACE_LAN.route_localnet=1 +} +prepare_nfqws() +{ + prepare_user +} +do_tpws() +{ + # $1 : 1 - run, 0 - stop + # $2 : daemon number + # $3 : daemon args + + [ "$1" = "1" ] && prepare_tpws + [ "$DISABLE_IPV4" = "1" ] || do_daemon $1 $2 $TPWS "$TPWS_OPT_BASE $3" + [ "$DISABLE_IPV6" = "1" ] || { + do_daemon $1 $((60+$2)) $TPWS "$TPWS_OPT_BASE6 $3" + [ -n "$IFACE_LAN" ] && do_daemon $1 $((660+$2)) $TPWS "$TPWS_OPT_BASE6_PRE --bind-iface6=$IFACE_LAN $3" + } +} +do_nfqws() +{ + # $1 : 1 - run, 0 - stop + # $2 : daemon number + # $3 : daemon args + + [ "$1" = "1" ] && prepare_nfqws + do_daemon $1 $2 $NFQWS "$NFQWS_OPT_BASE $3" +} + + +create_ipset() +{ + echo "Creating ipset" + "$IPSET_CR" "$@" +} + + + +zapret_do_firewall() +{ + # $1 - 1 - add, 0 - del + local rule + local synack="--tcp-flags SYN,ACK SYN,ACK" + local ipset_zapret="-m set --match-set zapret" + local ipset_zapret6="-m set --match-set zapret6" + local desync="-m multiport --dports 80,443 -m connbytes --connbytes-dir=original --connbytes-mode=packets --connbytes 2:4 -m mark ! --mark $DESYNC_MARK/$DESYNC_MARK" + + # always create ipsets. ip_exclude ipset is required + [ "$1" != "1" ] || create_ipset no-update + + case "${MODE}" in + tpws_hostlist|tpws_all) + fw_tpws $1 "--dport 80" "--dport 80" $TPPORT_HTTP + ;; + tpws_ipset) + [ "$1" = "1" ] && prepare_tpws + fw_tpws $1 "--dport 80 $ipset_zapret dst" "--dport 80 $ipset_zapret6 dst" $TPPORT_HTTP + ;; + tpws_ipset_https) + [ "$1" = "1" ] && prepare_tpws + fw_tpws $1 "--dport 80 $ipset_zapret dst" "--dport 80 $ipset_zapret6 dst" $TPPORT_HTTP + fw_tpws $1 "--dport 443 $ipset_zapret dst" "--dport 443 $ipset_zapret6 dst" $TPPORT_HTTPS + ;; + tpws_all_https) + [ "$1" = "1" ] && prepare_tpws + fw_tpws $1 "--dport 80" "--dport 80" $TPPORT_HTTP + fw_tpws $1 "--dport 443" "--dport 443" $TPPORT_HTTPS + ;; + nfqws_ipset) + fw_nfqws_pre $1 "--sport 80 $synack $ipset_zapret src" "--sport 80 $synack $ipset_zapret6 src" $QNUM + fw_nfqws_post $1 "--dport 80 $ipset_zapret dst" "--dport 80 $ipset_zapret6 dst" $QNUM + ;; + nfqws_ipset_https) + rule="-m multiport --sports 80,443 $synack" + fw_nfqws_pre $1 "$rule $ipset_zapret src" "$rule $ipset_zapret6 src" $QNUM + fw_nfqws_post $1 "--dport 80 $ipset_zapret dst" "--dport 80 $ipset_zapret6 dst" $QNUM + ;; + nfqws_all) + fw_nfqws_pre $1 "--sport 80 $synack" "--sport 80 $synack" $QNUM + fw_nfqws_post $1 "--dport 80" "--dport 80" $QNUM + ;; + nfqws_all_https) + rule="-m multiport --sports 80,443 $synack" + fw_nfqws_pre $1 "$rule" "$rule" $QNUM + fw_nfqws_post $1 "--dport 80" "--dport 80" $QNUM + ;; + nfqws_all_desync|nfqws_hostlist_desync) + rule="-m multiport --sports 80,443 $synack" + fw_nfqws_pre $1 "$rule" "$rule" $QNUM + fw_nfqws_post $1 "$desync" "$desync" $QNUM + ;; + nfqws_ipset_desync) + rule="-m multiport --sports 80,443 $synack" + fw_nfqws_pre $1 "$rule $ipset_zapret src" "$rule $ipset_zapret6 src" $QNUM + fw_nfqws_post $1 "$desync $ipset_zapret dst" "$desync $ipset_zapret6 dst" $QNUM + ;; + custom) + existf zapret_custom_firewall && zapret_custom_firewall $1 + ;; + esac +} +zapret_apply_firewall() +{ + zapret_do_firewall 1 "$@" +} +zapret_unapply_firewall() +{ + zapret_do_firewall 0 "$@" +} + +zapret_do_daemons() +{ + # $1 - 1 - run, 0 - stop + + case "${MODE}" in + tpws_hostlist) + do_tpws $1 1 "$TPWS_OPT_BASE_HTTP $TPWS_OPT_HTTP --hostlist=$HOSTLIST" + ;; + tpws_ipset|tpws_all) + do_tpws $1 1 "$TPWS_OPT_BASE_HTTP $TPWS_OPT_HTTP" + ;; + tpws_ipset_https|tpws_all_https) + do_tpws $1 1 "$TPWS_OPT_BASE_HTTP $TPWS_OPT_HTTP" + do_tpws $1 2 "$TPWS_OPT_BASE_HTTPS $TPWS_OPT_HTTPS" + ;; + nfqws_ipset|nfqws_ipset_https|nfqws_all|nfqws_all_https) + do_nfqws $1 1 "$NFQWS_OPT" + ;; + nfqws_ipset_desync|nfqws_all_desync) + do_nfqws $1 1 "$NFQWS_OPT_DESYNC" + ;; + nfqws_hostlist_desync) + do_nfqws $1 1 "$NFQWS_OPT_DESYNC --hostlist=$HOSTLIST" + ;; + custom) + existf zapret_custom_daemons && zapret_custom_daemons $1 + ;; + esac +} + +zapret_run_daemons() +{ + zapret_do_daemons 1 "$@" +} +zapret_stop_daemons() +{ + zapret_do_daemons 0 "$@" +} diff --git a/init.d/sysv/zapret b/init.d/sysv/zapret new file mode 100755 index 0000000..081b02c --- /dev/null +++ b/init.d/sysv/zapret @@ -0,0 +1,48 @@ +#!/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 + +ZAPRET_BASE=/opt/zapret +. "$ZAPRET_BASE/init.d/sysv/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 diff --git a/install_bin.sh b/install_bin.sh new file mode 100755 index 0000000..f5a1d9b --- /dev/null +++ b/install_bin.sh @@ -0,0 +1,64 @@ +#!/bin/sh + +SCRIPT=$(readlink -f "$0") +EXEDIR=$(dirname "$SCRIPT") +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" +} + +ARCHLIST="my x86_64 x86 aarch64 armhf mips64r2-msb mips32r1-lsb mips32r1-msb ppc" + +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 + ccp $arch/nfqws nfq + ccp $arch/tpws tpws + exit 0 + else + echo $arch is NOT OK + fi + done +fi + +exit 1 diff --git a/install_easy.sh b/install_easy.sh new file mode 100755 index 0000000..12e4c0c --- /dev/null +++ b/install_easy.sh @@ -0,0 +1,815 @@ +#!/bin/sh + +# automated script for easy installing zapret + +SCRIPT=$(readlink -f "$0") +EXEDIR=$(dirname "$SCRIPT") +ZAPRET_BASE=/opt/zapret +ZAPRET_CONFIG=$EXEDIR/config + +. "$ZAPRET_CONFIG" + +GET_LIST="$EXEDIR/ipset/get_config.sh" +GET_LIST_PREFIX=/ipset/get_ +INIT_SCRIPT=/etc/init.d/zapret + +SYSTEMD_SYSTEM_DIR=/lib/systemd/system +[ -d "$SYSTEMD_SYSTEM_DIR" ] || SYSTEMD_SYSTEM_DIR=/usr/lib/systemd/system + +exists() +{ + which $1 >/dev/null 2>/dev/null +} +whichq() +{ + which $1 2>/dev/null +} + +exitp() +{ + local A + + echo + echo press enter to continue + read A + exit $1 +} + +[ $(id -u) -ne "0" ] && { + echo root is required + exists sudo && exec sudo "$0" + exists su && exec su -c "$0" + echo su or sudo not found + exitp 2 +} + +read_yes_no() +{ + # $1 - default (Y/N) + local A + read A + [ -z "$A" ] || ([ "$A" != "Y" ] && [ "$A" != "y" ] && [ "$A" != "N" ] && [ "$A" != "n" ]) && A=$1 + [ "$A" = "Y" ] || [ "$A" = "y" ] +} +ask_yes_no() +{ + # $1 - default (Y/N) + # $2 - text + echo -n "$2 (default : $1) (Y/N) ? " + read_yes_no $1 +} + +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" "$@" +} + +get_dir_inode() +{ + local dir="$1" + [ -L "$dir" ] && dir=$(readlink -f "$dir") + ls -id "$dir" | awk '{print $1}' +} + +md5file() +{ + md5sum "$1" | cut -f1 -d ' ' +} + +random() +{ + # $1 - min, $2 - max + local r rs + if [ -c /dev/urandom ]; then + read rs /dev/null) + [ -z "$M" ] && M="$M_DEFAULT" + echo selected : $M + eval $1="$M" + + [ "$M" != "$M_OLD" ] +} + +write_config_var() +{ + # $1 - mode var + local M + eval M="\$$1" + + if [ -n "$M" ]; then + sed -ri "s/^#?$1=.*$/$1=$M/" "$EXEDIR/config" + else + # write with comment at the beginning + sed -ri "s/^#?$1=.*$/#$1=/" "$EXEDIR/config" + fi +} +select_mode() +{ + echo select MODE : + ask_list MODE "tpws_ipset tpws_ipset_https tpws_all tpws_all_https tpws_hostlist nfqws_ipset nfqws_ipset_https nfqws_all nfqws_all_https nfqws_all_desync nfqws_ipset_desync nfqws_hostlist_desync ipset custom" tpws_ipset_https && write_config_var MODE +} +select_getlist() +{ + # do not touch this in custom mode + [ "$MODE" = "custom" ] && return + + if [ "${MODE%hostlist*}" != "$MODE" ] || [ "${MODE%ipset*}" != "$MODE" ]; then + if ask_yes_no Y "do you want to auto download ip/host list"; then + if [ "${MODE%hostlist*}" != "$MODE" ] ; then + local GL_OLD=$GETLIST + GETLIST="get_reestr_hostlist.sh" + [ "$GL_OLD" != "$GET_LIST" ] && write_config_var GETLIST + else + GETLISTS="get_user.sh get_antifilter_ip.sh get_antifilter_ipsmart.sh get_antifilter_ipsum.sh get_reestr_ip.sh get_reestr_combined.sh get_reestr_resolve.sh" + GETLIST_DEF="get_antifilter_ipsmart.sh" + ask_list GETLIST "$GETLISTS" "$GETLIST_DEF" && write_config_var GETLIST + fi + return + fi + fi + GETLIST="" + write_config_var GETLIST +} +select_ipv6() +{ + local T=N + + [ "$DISABLE_IPV6" != '1' ] && T=Y + local old6=$DISABLE_IPV6 + if ask_yes_no $T "enable ipv6 support"; then + DISABLE_IPV6=0 + else + DISABLE_IPV6=1 + fi + [ "$old6" != "$DISABLE_IPV6" ] && write_config_var DISABLE_IPV6 +} + +ask_config() +{ + select_mode + select_getlist +} + +ask_iface() +{ + # $1 - var to ask + ask_list $1 "$(ls /sys/class/net)" && write_config_var $1 +} + +select_router_iface() +{ + local T=N + [ -n "$IFACE_LAN" ] && [ -n "$IFACE_WAN" ] && T=Y + local old_lan=$IFACE_LAN + local old_wan=$IFACE_WAN + + if ask_yes_no $T "is this system a router"; then + echo LAN interface : + ask_iface IFACE_LAN + echo WAN interface : + ask_iface IFACE_WAN + else + [ -n "$old_lan" ] && { + IFACE_LAN="" + write_config_var IFACE_LAN + } + [ -n "$old_wan" ] && { + IFACE_WAN="" + write_config_var IFACE_WAN + } + fi +} +ask_config_desktop() +{ + select_router_iface +} + +copy_all() +{ + cp -R "$1" "$2" + [ -d "$2/tmp" ] || mkdir "$2/tmp" +} +copy_minimal() +{ + local ARCH=$(get_bin_arch) + local BINDIR="$1/binaries/$ARCH" + + [ -d "$2" ] || mkdir -p "$2" + + mkdir "$2/tpws" "$2/nfq" "$2/ip2net" "$2/mdig" "$2/binaries" "$2/binaries/$ARCH" "$2/tmp" + cp -R "$1/ipset" "$2" + cp -R "$1/init.d" "$2" + cp "$1/config" "$1/install_easy.sh" "$1/uninstall_easy.sh" "$1/install_bin.sh" "$2" + cp "$BINDIR/tpws" "$BINDIR/nfqws" "$BINDIR/ip2net" "$BINDIR/mdig" "$2/binaries/$ARCH" +} + +_backup_settings() +{ + local i=0 + for f in "$@"; do + [ -f "$ZAPRET_BASE/$f" ] && cp -f "$ZAPRET_BASE/$f" "/tmp/zapret-bkp-$i" + i=$(($i+1)) + done +} +_restore_settings() +{ + local i=0 + for f in "$@"; do + [ -f "/tmp/zapret-bkp-$i" ] && mv -f "/tmp/zapret-bkp-$i" "$ZAPRET_BASE/$f" + i=$(($i+1)) + done +} +backup_restore_settings() +{ + # $1 - 1 - backup, 0 - restore + local mode=$1 + on_off_function _backup_settings _restore_settings $mode "config" "init.d/sysv/custom" "init.d/openwrt/custom" +} + +check_location() +{ + # $1 - copy function + + echo \* checking location + + # use inodes in case something is linked + [ -d "$ZAPRET_BASE" ] && [ $(get_dir_inode "$EXEDIR") = $(get_dir_inode "$ZAPRET_BASE") ] || { + echo easy install is supported only from default location : $ZAPRET_BASE + echo currently its run from $EXEDIR + if ask_yes_no N "do you want the installer to copy it for you"; then + local keep=N + if [ -d "$ZAPRET_BASE" ]; then + echo installer found existing $ZAPRET_BASE + echo directory needs to be replaced. config and custom scripts can be kept or replaced with clean version + if ask_yes_no N "do you want to delete all files there and copy this version"; then + ask_yes_no Y "keep config and custom scripts" && keep=Y + [ "$keep" = "Y" ] && backup_restore_settings 1 + rm -r "$ZAPRET_BASE" + else + echo refused to overwrite $ZAPRET_BASE. exiting + exitp 3 + fi + fi + local B=$(dirname "$ZAPRET_BASE") + [ -d "$B" ] || mkdir -p "$B" + $1 "$EXEDIR" "$ZAPRET_BASE" + [ "$keep" = "Y" ] && backup_restore_settings 0 + echo relaunching itself from $ZAPRET_BASE + exec $ZAPRET_BASE/$(basename $0) + else + echo copying aborted. exiting + exitp 3 + fi + } + echo running from $EXEDIR +} + + +check_prerequisites_linux() +{ + echo \* checking prerequisites + + if exists ipset && exists curl ; then + echo everything is present + else + echo \* installing prerequisites + + APTGET=$(whichq apt-get) + YUM=$(whichq yum) + PACMAN=$(whichq pacman) + ZYPPER=$(whichq zypper) + EOPKG=$(whichq eopkg) + if [ -x "$APTGET" ] ; then + "$APTGET" update + "$APTGET" install -y --no-install-recommends ipset curl dnsutils || { + echo could not install prerequisites + exitp 6 + } + elif [ -x "$YUM" ] ; then + "$YUM" -y install curl ipset || { + echo could not install prerequisites + exitp 6 + } + elif [ -x "$PACMAN" ] ; then + "$PACMAN" -Syy + "$PACMAN" --noconfirm -S ipset curl || { + echo could not install prerequisites + exitp 6 + } + elif [ -x "$ZYPPER" ] ; then + "$ZYPPER" --non-interactive install ipset curl || { + echo could not install prerequisites + exitp 6 + } + elif [ -x "$EOPKG" ] ; then + "$EOPKG" -y install ipset curl || { + echo could not install prerequisites + exitp 6 + } + else + echo supported package manager not found + echo you must manually install : ipset curl + exitp 5 + fi + fi +} + + +service_install_systemd() +{ + echo \* installing zapret service + + rm -f "$INIT_SCRIPT" + ln -fs "$EXEDIR/init.d/systemd/zapret.service" "$SYSTEMD_SYSTEM_DIR" + "$SYSTEMCTL" daemon-reload + "$SYSTEMCTL" enable zapret || { + echo could not enable systemd service + exitp 20 + } +} + +service_stop_systemd() +{ + echo \* stopping zapret service + + "$SYSTEMCTL" daemon-reload + "$SYSTEMCTL" disable zapret + "$SYSTEMCTL" stop zapret +} + +service_start_systemd() +{ + echo \* starting zapret service + + "$SYSTEMCTL" start zapret || { + echo could not start zapret service + exitp 30 + } +} + +timer_install_systemd() +{ + echo \* installing zapret-list-update timer + + "$SYSTEMCTL" disable zapret-list-update.timer + "$SYSTEMCTL" stop zapret-list-update.timer + ln -fs "$EXEDIR/init.d/systemd/zapret-list-update.service" "$SYSTEMD_SYSTEM_DIR" + ln -fs "$EXEDIR/init.d/systemd/zapret-list-update.timer" "$SYSTEMD_SYSTEM_DIR" + "$SYSTEMCTL" daemon-reload + "$SYSTEMCTL" enable zapret-list-update.timer || { + echo could not enable zapret-list-update.timer + exitp 20 + } + "$SYSTEMCTL" start zapret-list-update.timer || { + echo could not start zapret-list-update.timer + exitp 30 + } +} + +download_list() +{ + [ -x "$GET_LIST" ] && { + echo \* downloading blocked ip/host list + + # can be txt or txt.gz + "$EXEDIR/ipset/clear_lists.sh" + "$GET_LIST" || { + echo could not download ip list + exitp 25 + } + } +} + +crontab_del_quiet() +{ + exists crontab || return + + CRONTMP=/tmp/cron.tmp + crontab -l >$CRONTMP + if grep -q "$GET_IPLIST_PREFIX" $CRONTMP; then + grep -v "$GET_IPLIST_PREFIX" $CRONTMP >$CRONTMP.2 + crontab $CRONTMP.2 + rm -f $CRONTMP.2 + fi + rm -f $CRONTMP +} + +crontab_add() +{ + # $1 - hour min + # $2 - hour max + [ -x "$GET_LIST" ] && { + echo \* adding crontab entry + + CRONTMP=/tmp/cron.tmp + crontab -l >$CRONTMP + if grep -q "$GET_LIST_PREFIX" $CRONTMP; then + echo some entries already exist in crontab. check if this is corrent : + grep "$GET_LIST_PREFIX" $CRONTMP + else + echo "$(random 0 59) $(random $1 $2) */2 * * $GET_LIST" >>$CRONTMP + crontab $CRONTMP + fi + + rm -f $CRONTMP + } +} + + +install_systemd() +{ + INIT_SCRIPT_SRC=$EXEDIR/init.d/sysv/zapret + + check_bins + check_location copy_all + check_prerequisites_linux + service_stop_systemd + install_binaries + select_ipv6 + ask_config_desktop + ask_config + service_install_systemd + download_list + # in case its left from old version of zapret + crontab_del_quiet + # now we use systemd timers + timer_install_systemd + service_start_systemd +} + + + + +check_kmod() +{ + [ -f "/lib/modules/$(uname -r)/$1.ko" ] +} +check_package_exists_openwrt() +{ + [ -n "$(opkg list $1)" ] +} +check_package_openwrt() +{ + [ -n "$(opkg list-installed $1)" ] +} +check_packages_openwrt() +{ + for pkg in $@; do + check_package_openwrt $pkg || return + done +} + +check_prerequisites_openwrt() +{ + echo \* checking prerequisites + + local PKGS="iptables-mod-extra iptables-mod-nfqueue iptables-mod-filter iptables-mod-ipopt iptables-mod-conntrack-extra ipset curl" + [ "$DISABLE_IPV6" != "1" ] && PKGS="$PKGS ip6tables-mod-nat" + local UPD=0 + + if check_packages_openwrt $PKGS ; then + echo everything is present + else + echo \* installing prerequisites + + opkg update + UPD=1 + opkg install $PKGS || { + echo could not install prerequisites + exitp 6 + } + fi + + [ -x "/usr/bin/gzip" ] || { + echo your system uses default busybox gzip. its several times slower than gnu gzip. + echo ip/host list scripts will run much faster with gnu gzip + echo installer can install gnu gzip but it requires about 100 Kb space + if ask_yes_no N "do you want to install gnu gzip"; then + [ "$UPD" = "0" ] && { + opkg update + UPD=1 + } + opkg install gzip + fi + } + [ -x "/usr/bin/grep" ] || { + echo your system uses default busybox grep. its damn infinite slow with -f option + echo get_combined.sh will be severely impacted + echo installer can install gnu grep but it requires about 0.5 Mb space + if ask_yes_no N "do you want to install gnu grep"; then + [ "$UPD" = "0" ] && { + opkg update + UPD=1 + } + opkg install grep + + # someone reported device partially fail if /bin/grep is absent + # grep package deletes /bin/grep + [ -f /bin/grep ] || ln -s busybox /bin/grep + fi + } +} + +openwrt_fw_section_find() +{ + # $1 - fw include postfix + # echoes section number + + i=0 + while true + do + path=$(uci -q get firewall.@include[$i].path) + [ -n "$path" ] || break + [ "$path" = "$OPENWRT_FW_INCLUDE$1" ] && { + echo $i + return + } + i=$(($i+1)) + done + false + return +} +openwrt_fw_section_del() +{ + # $1 - fw include postfix + + local id=$(openwrt_fw_section_find $1) + [ -n "$id" ] && { + uci delete firewall.@include[$id] && uci commit firewall + rm -f "$OPENWRT_FW_INCLUDE$1" + } +} +openwrt_fw_section_add() +{ + openwrt_fw_section_find || + { + uci add firewall include >/dev/null || return + echo -1 + } +} +openwrt_fw_section_configure() +{ + local id=$(openwrt_fw_section_add $1) + [ -z "$id" ] || + ! uci set firewall.@include[$id].path="$OPENWRT_FW_INCLUDE" || + ! uci set firewall.@include[$id].reload="1" || + ! uci commit firewall && + { + echo could not add firewall include + exitp 50 + } +} + +install_openwrt_firewall() +{ + echo \* installing firewall script $1 + + [ -n "MODE" ] || { + echo should specify MODE in $ZAPRET_CONFIG + exitp 7 + } + + echo "linking : $FW_SCRIPT_SRC => $OPENWRT_FW_INCLUDE" + ln -fs "$FW_SCRIPT_SRC" "$OPENWRT_FW_INCLUDE" + + openwrt_fw_section_configure $1 +} + + +restart_openwrt_firewall() +{ + echo \* restarting firewall + + fw3 -q restart || { + echo could not restart firewall + exitp 30 + } +} + +remove_openwrt_firewall() +{ + echo \* removing firewall script + + openwrt_fw_section_del + # from old zapret versions. now we use single include + openwrt_fw_section_del 6 +} + +install_openwrt_iface_hook() +{ + echo \* installing ifup hook + + ln -fs "$OPENWRT_IFACE_HOOK" /etc/hotplug.d/iface +} + +deoffload_openwrt_firewall() +{ + echo \* checking flow offloading + + local mod=0 + local fo=$(uci -q get firewall.@defaults[0].flow_offloading) + local fo_hw=$(uci -q get firewall.@defaults[0].flow_offloading_hw) + + if [ "$fo_hw" = "1" ] ; then + echo hardware flow offloading detected. its incompatible with zapret. disabling + uci set firewall.@defaults[0].flow_offloading_hw=0 + mod=1 + else + echo hardware flow offloading disabled. ok + fi + if [ "$fo" = "1" ] ; then + echo -n "software flow offloading detected. " + if [ "${MODE%nfqws*}" != "$MODE" ]; then + echo its incompatible with nfqws tcp data tampering. disabling + uci set firewall.@defaults[0].flow_offloading=0 + mod=1 + else + echo its compatible with selected options. not disabling + fi + else + echo software flow offloading disabled. ok + fi + [ "$mod" = "1" ] && uci commit firewall +} + +install_sysv_init() +{ + # $1 - "0"=disable + echo \* installing init script + + [ -x "$INIT_SCRIPT" ] && { + "$INIT_SCRIPT" stop + "$INIT_SCRIPT" disable + } + ln -fs "$INIT_SCRIPT_SRC" "$INIT_SCRIPT" + [ "$1" != "0" ] && "$INIT_SCRIPT" enable +} + +service_start_sysv() +{ + echo \* starting zapret service + + "$INIT_SCRIPT" start || { + echo could not start zapret service + exitp 30 + } +} + + + +install_openwrt() +{ + INIT_SCRIPT_SRC=$EXEDIR/init.d/openwrt/zapret + FW_SCRIPT_SRC=$EXEDIR/init.d/openwrt/firewall.zapret + OPENWRT_FW_INCLUDE=/etc/firewall.zapret + OPENWRT_IFACE_HOOK=$EXEDIR/init.d/openwrt/90-zapret + + check_bins + check_location copy_minimal + select_ipv6 + check_prerequisites_openwrt + install_binaries + ask_config + install_sysv_init + # can be previous firewall preventing access + remove_openwrt_firewall + restart_openwrt_firewall + download_list + # router system : works 24/7. night is the best time + crontab_add 0 6 + service_start_sysv + install_openwrt_iface_hook + install_openwrt_firewall + deoffload_openwrt_firewall + restart_openwrt_firewall +} + + + +# build binaries, do not use precompiled +[ "$1" = "make" ] && FORCE_BUILD=1 + +check_system + +case $SYSTEM in + systemd) + install_systemd + ;; + openwrt) + install_openwrt + ;; +esac + + +exitp 0 diff --git a/ip2net/Makefile b/ip2net/Makefile new file mode 100644 index 0000000..3de0eef --- /dev/null +++ b/ip2net/Makefile @@ -0,0 +1,12 @@ +CC ?= gcc +CFLAGS += -std=c99 -s -O3 +LIBS = +SRC_FILES = *.c + +all: ip2net + +ip2net: $(SRC_FILES) + $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) $(LIBS) + +clean: + rm -f ip2net *.o diff --git a/ip2net/ip2net.c b/ip2net/ip2net.c new file mode 100644 index 0000000..11ddbf4 --- /dev/null +++ b/ip2net/ip2net.c @@ -0,0 +1,396 @@ +// 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 +#include +#include +#include +#include +#include +#include +#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(¶ms, 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=params.pctdiv) + { + fprintf(stderr, "invalid parameter for v4-threshold : %s\n", optarg); + exit(1); + } + break; + case 6: + i = sscanf(optarg, "%u", ¶ms.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= 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; + uint32_t i, subnet_ct, end_ip; + + 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; +} diff --git a/ip2net/qsort.c b/ip2net/qsort.c new file mode 100644 index 0000000..2ee1185 --- /dev/null +++ b/ip2net/qsort.c @@ -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 + . */ + +/* 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 +#include +#include +//#include +#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; + } + } + } + } +} diff --git a/ip2net/qsort.h b/ip2net/qsort.h new file mode 100644 index 0000000..f537ab7 --- /dev/null +++ b/ip2net/qsort.h @@ -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); diff --git a/ipset/antifilter.helper b/ipset/antifilter.helper new file mode 100644 index 0000000..285775e --- /dev/null +++ b/ipset/antifilter.helper @@ -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=$(wc -c "$ZIPLISTTMP" | cut -f 1 -d ' ') + if test $dlsize -lt 204800; then + echo list file is too small. can be bad. + exit 2 + fi + cat "$ZIPLISTTMP" | zz "$2" + rm -f "$ZIPLISTTMP" "$2" + } + } +} diff --git a/ipset/clear_lists.sh b/ipset/clear_lists.sh new file mode 100755 index 0000000..3778871 --- /dev/null +++ b/ipset/clear_lists.sh @@ -0,0 +1,8 @@ +#!/bin/sh + +SCRIPT=$(readlink -f "$0") +EXEDIR=$(dirname "$SCRIPT") + +. "$EXEDIR/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"* diff --git a/ipset/create_ipset.sh b/ipset/create_ipset.sh new file mode 100755 index 0000000..5dc239a --- /dev/null +++ b/ipset/create_ipset.sh @@ -0,0 +1,145 @@ +#!/bin/sh +# create ipset from resolved ip's +# $1=no-update - do not update ipset, only create if its absent + +SCRIPT=$(readlink -f "$0") +EXEDIR=$(dirname "$SCRIPT") + +[ -z "$IPSET_OPT" ] && IPSET_OPT="hashsize 262144 maxelem 2097152" +[ -z "$IPSET_OPT_EXCLUDE" ] && IPSET_OPT_EXCLUDE="hashsize 1024 maxelem 65536" + +IP2NET="$EXEDIR/../ip2net/ip2net" + +. "$EXEDIR/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 + 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 +} + + +sortu() +{ + sort -u +} +ip2net4() +{ + "$IP2NET" -4 $IP2NET_OPT4 +} +ip2net6() +{ + "$IP2NET" -6 $IP2NET_OPT6 +} +ipset_get_script() +{ + # $1 - filename + # $2 - ipset name + # $3 - "6" = ipv6 + local filter=sortu + [ -x "$IP2NET" ] && filter=ip2net$3 + zzcat "$1" | $filter | sed -nre "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" + [ -x "$IP2NET" ] && T="$T, ip2net" + [ "$svram" = "1" ] && T="$T, saveram" + T="$T) : $f" + echo $T + + if [ "$svram" = "1" ]; then + ipset_get_script "$1" "$2" "$3" >"$IPSET_CMD" + ipset_restore_chunked "$IPSET_CMD" $IPSET_SAVERAM_CHUNK_SIZE + rm -f "$IPSET_CMD" + else + ipset_get_script "$1" "$2" "$3" | ipset -! restore + fi +} + +create_ipset() +{ + local IPSTYPE + if [ -x "$IP2NET" ]; then + IPSTYPE=hash:net + else + IPSTYPE=$3 + fi + if [ "$1" -eq "6" ]; then + FAMILY=inet6 + else + FAMILY=inet + fi + ipset create $2 $IPSTYPE $4 family $FAMILY 2>/dev/null || { + [ "$NO_UPDATE" = "1" ] && return + } + ipset flush $2 + for f in "$5" "$6" ; do + ipset_restore "$f" "$2" $1 + done + return 0 +} + +oom_adjust_high + +# 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 +} + +[ "$DISABLE_IPV4" != "1" ] && { + create_ipset 4 $ZIPSET hash:ip "$IPSET_OPT" "$ZIPLIST" "$ZIPLIST_USER" + create_ipset 4 $ZIPSET_IPBAN hash:ip "$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:ip "$IPSET_OPT" "$ZIPLIST6" "$ZIPLIST_USER6" + create_ipset 6 $ZIPSET_IPBAN6 hash:ip "$IPSET_OPT" "$ZIPLIST_IPBAN6" "$ZIPLIST_USER_IPBAN6" + create_ipset 6 $ZIPSET_EXCLUDE6 hash:net "$IPSET_OPT_EXCLUDE" "$ZIPLIST_EXCLUDE6" +} + +true diff --git a/ipset/def.sh b/ipset/def.sh new file mode 100644 index 0000000..c3165b4 --- /dev/null +++ b/ipset/def.sh @@ -0,0 +1,103 @@ +. "$EXEDIR/../config" + +[ -z "$TMPDIR" ] && TMPDIR=/tmp +ZIPSET=zapret +ZIPSET6=zapret6 +ZIPSET_EXCLUDE=nozapret +ZIPSET_EXCLUDE6=nozapret6 +ZIPLIST="$EXEDIR/zapret-ip.txt" +ZIPLIST6="$EXEDIR/zapret-ip6.txt" +ZIPLIST_EXCLUDE="$EXEDIR/zapret-ip-exclude.txt" +ZIPLIST_EXCLUDE6="$EXEDIR/zapret-ip-exclude6.txt" +ZIPLIST_USER="$EXEDIR/zapret-ip-user.txt" +ZIPLIST_USER6="$EXEDIR/zapret-ip-user6.txt" +ZUSERLIST="$EXEDIR/zapret-hosts-user.txt" +ZHOSTLIST="$EXEDIR/zapret-hosts.txt" + +ZIPSET_IPBAN=ipban +ZIPSET_IPBAN6=ipban6 +ZIPLIST_IPBAN="$EXEDIR/zapret-ip-ipban.txt" +ZIPLIST_IPBAN6="$EXEDIR/zapret-ip-ipban6.txt" +ZIPLIST_USER_IPBAN="$EXEDIR/zapret-ip-user-ipban.txt" +ZIPLIST_USER_IPBAN6="$EXEDIR/zapret-ip-user-ipban6.txt" +ZUSERLIST_IPBAN="$EXEDIR/zapret-hosts-user-ipban.txt" +ZUSERLIST_EXCLUDE="$EXEDIR/zapret-hosts-user-exclude.txt" + +MDIG="$EXEDIR/../mdig/mdig" +[ -z "$MDIG_THREADS" ] && MDIG_THREADS=30 + +zzexist() +{ + [ -f "$1.gz" ] || [ -f "$1" ] +} +zzcat() +{ + if [ -f "$1.gz" ]; then + gunzip -c "$1.gz" + else + cat "$1" + fi +} +zz() +{ + gzip -c >"$1.gz" +} +zzsize() +{ + local f="$1" + [ -f "$1.gz" ] && f="$1.gz" + wc -c <"$f" +} + +digger() +{ + # $1 - hostlist + # $2 - family (4|6) + >&2 echo digging $(wc -l <"$1") 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() +{ + 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" + } +} diff --git a/ipset/get_antifilter_ip.sh b/ipset/get_antifilter_ip.sh new file mode 100755 index 0000000..b393ee3 --- /dev/null +++ b/ipset/get_antifilter_ip.sh @@ -0,0 +1,14 @@ +#!/bin/sh + +SCRIPT=$(readlink -f "$0") +EXEDIR=$(dirname "$SCRIPT") + +. "$EXEDIR/def.sh" + +getuser + +. "$EXEDIR/antifilter.helper" + +get_antifilter https://antifilter.network/download/ip.lst "$ZIPLIST" + +"$EXEDIR/create_ipset.sh" diff --git a/ipset/get_antifilter_ipsmart.sh b/ipset/get_antifilter_ipsmart.sh new file mode 100755 index 0000000..f27b61a --- /dev/null +++ b/ipset/get_antifilter_ipsmart.sh @@ -0,0 +1,14 @@ +#!/bin/sh + +SCRIPT=$(readlink -f "$0") +EXEDIR=$(dirname "$SCRIPT") + +. "$EXEDIR/def.sh" + +getuser + +. "$EXEDIR/antifilter.helper" + +get_antifilter https://antifilter.network/download/ipsmart.lst "$ZIPLIST" + +"$EXEDIR/create_ipset.sh" diff --git a/ipset/get_antifilter_ipsum.sh b/ipset/get_antifilter_ipsum.sh new file mode 100755 index 0000000..a76037a --- /dev/null +++ b/ipset/get_antifilter_ipsum.sh @@ -0,0 +1,14 @@ +#!/bin/sh + +SCRIPT=$(readlink -f "$0") +EXEDIR=$(dirname "$SCRIPT") + +. "$EXEDIR/def.sh" + +getuser + +. "$EXEDIR/antifilter.helper" + +get_antifilter https://antifilter.network/download/ipsum.lst "$ZIPLIST" + +"$EXEDIR/create_ipset.sh" diff --git a/ipset/get_config.sh b/ipset/get_config.sh new file mode 100755 index 0000000..500db9e --- /dev/null +++ b/ipset/get_config.sh @@ -0,0 +1,10 @@ +#!/bin/sh +# run script specified in config + +SCRIPT=$(readlink -f "$0") +EXEDIR=$(dirname "$SCRIPT") + +. "$EXEDIR/../config" + +[ -z "$GETLIST" ] && GETLIST=get_exclude.sh +[ -x "$EXEDIR/$GETLIST" ] && exec "$EXEDIR/$GETLIST" diff --git a/ipset/get_exclude.sh b/ipset/get_exclude.sh new file mode 100755 index 0000000..786c4b4 --- /dev/null +++ b/ipset/get_exclude.sh @@ -0,0 +1,11 @@ +#!/bin/sh +# resolve user host list + +SCRIPT=$(readlink -f "$0") +EXEDIR=$(dirname "$SCRIPT") + +. "$EXEDIR/def.sh" + +getexclude + +"$EXEDIR/create_ipset.sh" diff --git a/ipset/get_reestr_combined.sh b/ipset/get_reestr_combined.sh new file mode 100755 index 0000000..46abc7d --- /dev/null +++ b/ipset/get_reestr_combined.sh @@ -0,0 +1,59 @@ +#!/bin/sh + +SCRIPT=$(readlink -f "$0") +EXEDIR=$(dirname "$SCRIPT") + +. "$EXEDIR/def.sh" + +ZREESTR="$TMPDIR/reestr.txt" +#ZURL_REESTR=https://reestr.rublacklist.net/api/current +ZURL_REESTR=https://raw.githubusercontent.com/zapret-info/z-i/master/dump.csv + +getuser + +dig_reestr() +{ + # $1 - grep ipmask + # $2 - iplist + # $3 - ipban list + + local DOMMASK='^.*;[^ ;:/]+\.[^ ;:/]+;' + local TMP="$TMPDIR/tmp.txt" + # find entries with https or without domain name - they should be banned by IP + # 2971-18 is TELEGRAM. lots of proxy IPs banned, list grows very large + (grep -avE "$DOMMASK" "$ZREESTR" ; grep -a "https://" "$ZREESTR") | + grep -av "2971-18" | + grep -oE "$1" | cut_local | sort -u >$TMP + + cat "$TMP" | zz "$3" + + # other IPs go to regular zapret list + grep -av "2971-18" "$ZREESTR" | grep -oE "$1" | cut_local | grep -xvFf "$TMP" | sort -u | zz "$2" + + rm -f "$TMP" +} + + +curl -k --fail --max-time 150 --connect-timeout 5 --retry 3 --max-filesize 251658240 "$ZURL_REESTR" -o "$ZREESTR" || +{ + echo reestr list download failed + exit 2 +} +dlsize=$(wc -c "$ZREESTR" | cut -f 1 -d ' ') +if test $dlsize -lt 1048576; then + echo reestr ip list is too small. can be bad. + exit 2 +fi +#sed -i 's/\\n/\r\n/g' $ZREESTR + +[ "$DISABLE_IPV4" != "1" ] && { + dig_reestr '([0-9]{1,3}\.){3}[0-9]{1,3}(/[0-9]+)?' "$ZIPLIST" "$ZIPLIST_IPBAN" +} + +[ "$DISABLE_IPV6" != "1" ] && { + dig_reestr '([0-9,a-f,A-F]{1,4}:){7}[0-9,a-f,A-F]{1,4}(/[0-9]+)?' "$ZIPLIST6" "$ZIPLIST_IPBAN6" +} + +rm -f "$ZREESTR" + +"$EXEDIR/create_ipset.sh" diff --git a/ipset/get_reestr_hostlist.sh b/ipset/get_reestr_hostlist.sh new file mode 100755 index 0000000..b07554c --- /dev/null +++ b/ipset/get_reestr_hostlist.sh @@ -0,0 +1,33 @@ +#!/bin/sh + +SCRIPT=$(readlink -f "$0") +EXEDIR=$(dirname "$SCRIPT") + +. "$EXEDIR/def.sh" + +# useful in case ipban set is used in custom scripts +getuser +"$EXEDIR/create_ipset.sh" + +ZREESTR="$TMPDIR/zapret.txt" +#ZURL=https://reestr.rublacklist.net/api/current +ZURL=https://raw.githubusercontent.com/zapret-info/z-i/master/dump.csv + +curl -k --fail --max-time 150 --connect-timeout 5 --retry 3 --max-filesize 251658240 "$ZURL" >"$ZREESTR" || +{ + echo reestr list download failed + exit 2 +} +dlsize=$(wc -c "$ZREESTR" | cut -f 1 -d ' ') +if test $dlsize -lt 204800; then + echo list file is too small. can be bad. + exit 2 +fi +(cut -s -f2 -d';' "$ZREESTR" | grep -a . | sed -re 's/^\*\.(.+)$/\1/' | awk '{ print tolower($0) }' ; cat "$ZUSERLIST" ) | sort -u | zz "$ZHOSTLIST" +rm -f "$ZREESTR" + +# force daemons to reload hostlist if they are running +killall -HUP tpws 2>/dev/null +killall -HUP nfqws 2>/dev/null + +exit 0 diff --git a/ipset/get_reestr_ip.sh b/ipset/get_reestr_ip.sh new file mode 100755 index 0000000..2056b81 --- /dev/null +++ b/ipset/get_reestr_ip.sh @@ -0,0 +1,47 @@ +#!/bin/sh + +SCRIPT=$(readlink -f "$0") +EXEDIR=$(dirname "$SCRIPT") + +. "$EXEDIR/def.sh" + +ZREESTR="$TMPDIR/reestr.txt" +#ZURL_REESTR=https://reestr.rublacklist.net/api/current +ZURL_REESTR=https://raw.githubusercontent.com/zapret-info/z-i/master/dump.csv + +getuser + +dig_reestr() +{ + # $1 - grep ipmask + # $2 - iplist + + # 2971-18 is TELEGRAM. lots of proxy IPs banned, list grows very large + grep -av "2971-18" "$ZREESTR" | grep -oE "$1" | cut_local | sort -u | zz "$2" +} + + +# assume all https banned by ip +curl -k --fail --max-time 150 --connect-timeout 5 --retry 3 --max-filesize 251658240 "$ZURL_REESTR" -o "$ZREESTR" || +{ + echo reestr list download failed + exit 2 +} +dlsize=$(wc -c "$ZREESTR" | cut -f 1 -d ' ') +if test $dlsize -lt 1048576; then + echo reestr ip list is too small. can be bad. + exit 2 +fi +#sed -i 's/\\n/\r\n/g' $ZREESTR + +[ "$DISABLE_IPV4" != "1" ] && { + dig_reestr '([0-9]{1,3}\.){3}[0-9]{1,3}(/[0-9]+)?' "$ZIPLIST" +} + +[ "$DISABLE_IPV6" != "1" ] && { + dig_reestr '([0-9,a-f,A-F]{1,4}:){7}[0-9,a-f,A-F]{1,4}(/[0-9]+)?' "$ZIPLIST6" +} + +rm -f "$ZREESTR" + +"$EXEDIR/create_ipset.sh" diff --git a/ipset/get_reestr_resolve.sh b/ipset/get_reestr_resolve.sh new file mode 100755 index 0000000..de7bdc9 --- /dev/null +++ b/ipset/get_reestr_resolve.sh @@ -0,0 +1,54 @@ +#!/bin/sh + +SCRIPT=$(readlink -f "$0") +EXEDIR=$(dirname "$SCRIPT") + +. "$EXEDIR/def.sh" + +ZREESTR="$TMPDIR/zapret.txt" +ZDIG="$TMPDIR/zapret-dig.txt" +ZIPLISTTMP="$TMPDIR/zapret-ip.txt" +#ZURL=https://reestr.rublacklist.net/api/current +ZURL=https://raw.githubusercontent.com/zapret-info/z-i/master/dump.csv + +getuser + +# both disabled +[ "$DISABLE_IPV4" = "1" ] && [ "$DISABLE_IPV6" = "1" ] && exit 0 + +curl -k --fail --max-time 150 --connect-timeout 5 --retry 3 --max-filesize 251658240 "$ZURL" >"$ZREESTR" || +{ + echo reestr list download failed + exit 2 +} +dlsize=$(wc -c "$ZREESTR" | cut -f 1 -d ' ') +if test $dlsize -lt 204800; then + echo list file is too small. can be bad. + exit 2 +fi +echo preparing dig list .. +#sed -i 's/\\n/\r\n/g' $ZREESTR +#sed -nre 's/^[^;]*;([^;|\\]{4,250})\;.*$/\1/p' $ZREESTR | sort | uniq >$ZDIG +cut -f2 -d ';' "$ZREESTR" | grep -avE '^$|\*|:' >"$ZDIG" +rm -f "$ZREESTR" + +echo digging started. this can take long ... + +[ "$DISABLE_IPV4" != "1" ] && { + digger "$ZDIG" 4 | cut_local >"$ZIPLISTTMP" || { + rm -f "$ZDIG" + exit 1 + } + sort -u "$ZIPLISTTMP" | zz "$ZIPLIST" + rm -f "$ZIPLISTTMP" +} +[ "$DISABLE_IPV6" != "1" ] && { + digger "$ZDIG" 6 | cut_local6 >"$ZIPLISTTMP" || { + rm -f "$ZDIG" + exit 1 + } + sort -u "$ZIPLISTTMP" | zz "$ZIPLIST6" + rm -f "$ZIPLISTTMP" +} +rm -f "$ZDIG" +"$EXEDIR/create_ipset.sh" diff --git a/ipset/get_user.sh b/ipset/get_user.sh new file mode 100755 index 0000000..61b2c16 --- /dev/null +++ b/ipset/get_user.sh @@ -0,0 +1,11 @@ +#!/bin/sh +# resolve user host list + +SCRIPT=$(readlink -f "$0") +EXEDIR=$(dirname "$SCRIPT") + +. "$EXEDIR/def.sh" + +getuser + +"$EXEDIR/create_ipset.sh" diff --git a/ipset/zapret-hosts-user-exclude.txt b/ipset/zapret-hosts-user-exclude.txt new file mode 100644 index 0000000..c34ec7f --- /dev/null +++ b/ipset/zapret-hosts-user-exclude.txt @@ -0,0 +1,6 @@ +10.0.0.0/8 +172.16.0.0/12 +192.168.0.0/16 +169.254.0.0/16 +fc00::/7 +fe80::/10 diff --git a/ipset/zapret-hosts-user-ipban.txt b/ipset/zapret-hosts-user-ipban.txt new file mode 100644 index 0000000..a2049ed --- /dev/null +++ b/ipset/zapret-hosts-user-ipban.txt @@ -0,0 +1,2 @@ +kinozal.tv +rutracker.org diff --git a/ipset/zapret-hosts-user.txt b/ipset/zapret-hosts-user.txt new file mode 100644 index 0000000..1162eb8 --- /dev/null +++ b/ipset/zapret-hosts-user.txt @@ -0,0 +1,3 @@ +st.kinozal.tv +s.kinozal.tv +putinhuylo.com diff --git a/mdig/Makefile b/mdig/Makefile new file mode 100644 index 0000000..55a9f9e --- /dev/null +++ b/mdig/Makefile @@ -0,0 +1,12 @@ +CC ?= gcc +CFLAGS += -std=c99 -s -O3 +LIBS = -lpthread +SRC_FILES = *.c + +all: mdig + +mdig: $(SRC_FILES) + $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) $(LIBS) + +clean: + rm -f mdig *.o diff --git a/mdig/mdig.c b/mdig/mdig.c new file mode 100644 index 0000000..434a2b0 --- /dev/null +++ b/mdig/mdig.c @@ -0,0 +1,337 @@ +// multi thread dns resolver +// domain list stdout +// errors, verbose >stderr +// transparent for valid ip or ip/subnet of allowed address family + +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define RESOLVER_EAGAIN_ATTEMPTS 2 + +static void trimstr(char *s) +{ + char *p; + for (p = s + strlen(s) - 1; p >= s && (*p == '\n' || *p == '\r'); p--) *p = '\0'; +} + +static const char* eai_str(int r) +{ + switch (r) + { + case EAI_NONAME: + return "EAI_NONAME"; + case EAI_AGAIN: + return "EAI_AGAIN"; + case EAI_ADDRFAMILY: + return "EAI_ADDRFAMILY"; + case EAI_BADFLAGS: + return "EAI_BADFLAGS"; + case EAI_FAIL: + return "EAI_FAIL"; + case EAI_MEMORY: + return "EAI_MEMORY"; + case EAI_FAMILY: + return "EAI_FAMILY"; + case EAI_NODATA: + return "EAI_NODATA"; + case EAI_SERVICE: + return "EAI_SERVICE"; + case EAI_SOCKTYPE: + return "EAI_SOCKTYPE"; + case EAI_SYSTEM: + return "EAI_SYSTEM"; + default: + return "UNKNOWN"; + } +} + +#define FAMILY4 1 +#define FAMILY6 2 +static struct +{ + char verbose; + char family; + int threads; + pthread_mutex_t flock; + pthread_mutex_t slock; // stats lock + int stats_every,stats_ct,stats_ct_ok; // stats +} glob; + +// get next domain. return 0 if failure +static char interlocked_get_dom(char *dom, size_t size) +{ + char *s; + pthread_mutex_lock(&glob.flock); + s = fgets(dom, size, stdin); + pthread_mutex_unlock(&glob.flock); + if (!s) return 0; + trimstr(s); + return 1; +} +static void interlocked_fprintf(FILE *stream, const char * format, ...) +{ + va_list args; + va_start(args, format); + pthread_mutex_lock(&glob.flock); + vfprintf(stream, format, args); + pthread_mutex_unlock(&glob.flock); + va_end(args); +} + +#define ELOG(format, ...) interlocked_fprintf(stderr, "[%d] " format "\n", tid, ##__VA_ARGS__) +#define VLOG(format, ...) {if (glob.verbose) ELOG(format, ##__VA_ARGS__);} + +static void print_addrinfo(struct addrinfo *ai) +{ + char str[64]; + while (ai) + { + switch (ai->ai_family) + { + case AF_INET: + if (inet_ntop(ai->ai_family, &((struct sockaddr_in*)ai->ai_addr)->sin_addr, str, sizeof(str))) + interlocked_fprintf(stdout, "%s\n", str); + break; + case AF_INET6: + if (inet_ntop(ai->ai_family, &((struct sockaddr_in6*)ai->ai_addr)->sin6_addr, str, sizeof(str))) + interlocked_fprintf(stdout, "%s\n", str); + break; + } + ai = ai->ai_next; + } +} + +static void stat_print(int ct, int ct_ok) +{ + if (glob.stats_every > 0) + interlocked_fprintf(stderr, "mdig stats : domains=%d success=%d error=%d\n", ct, ct_ok, ct-ct_ok); +} + +static void stat_plus(char is_ok) +{ + int ct,ct_ok; + if (glob.stats_every > 0) + { + pthread_mutex_lock(&glob.slock); + ct = ++glob.stats_ct; + ct_ok = glob.stats_ct_ok+=!!is_ok; + pthread_mutex_unlock(&glob.slock); + + if (!(ct % glob.stats_every)) stat_print(ct,ct_ok); + } +} + +static uint16_t GetAddrFamily(const char *saddr) +{ + struct in_addr a4; + struct in6_addr a6; + + if (inet_pton(AF_INET, saddr, &a4)) + return AF_INET; + else if (inet_pton(AF_INET6, saddr, &a6)) + return AF_INET6; + return 0; +} + +static void *t_resolver(void *arg) +{ + int tid = (int)(size_t)arg; + int i,r; + char dom[256],is_ok; + struct addrinfo hints; + struct addrinfo *result; + + VLOG("started"); + + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_family = (glob.family == FAMILY4) ? AF_INET : (glob.family == FAMILY6) ? AF_INET6 : AF_UNSPEC; + hints.ai_socktype = SOCK_DGRAM; + + while (interlocked_get_dom(dom, sizeof(dom))) + { + if (*dom) + { + uint16_t family; + char *s_mask,s_ip[sizeof(dom)]; + + is_ok=0; + + strncpy(s_ip,dom,sizeof(s_ip)); + s_mask=strchr(s_ip,'/'); + if (s_mask) *s_mask++=0; + family=GetAddrFamily(s_ip); + if (family) + { + if (family==AF_INET && (glob.family & FAMILY4) || family==AF_INET6 && (glob.family & FAMILY6)) + { + unsigned int mask; + bool mask_needed=false; + if (s_mask) + { + if (sscanf(s_mask,"%u",&mask)) + { + switch(family) + { + case AF_INET: is_ok=mask<=32; mask_needed=mask<32; break; + case AF_INET6: is_ok=mask<=128; mask_needed=mask<128; break; + } + } + } + else + is_ok = 1; + if (is_ok) + interlocked_fprintf(stdout, mask_needed ? "%s/%u\n" : "%s\n", s_ip, mask); + else + VLOG("bad ip/subnet %s", dom); + } + else + VLOG("wrong address family %s", s_ip); + } + else + { + VLOG("resolving %s", dom); + for (i = 0; i < RESOLVER_EAGAIN_ATTEMPTS; i++) + { + if (r = getaddrinfo(dom, NULL, &hints, &result)) + { + VLOG("failed to resolve %s : result %d (%s)", dom, r, eai_str(r)); + if (r == EAI_AGAIN) continue; // temporary failure. should retry + } + else + { + print_addrinfo(result); + freeaddrinfo(result); + is_ok=1; + } + break; + } + } + stat_plus(is_ok); + } + } + VLOG("ended"); + return NULL; +} + +static int run_threads() +{ + int i, thread; + pthread_t *t; + + glob.stats_ct=glob.stats_ct_ok=0; + if (pthread_mutex_init(&glob.flock, NULL) != 0) + { + fprintf(stderr, "mutex init failed\n"); + return 10; + } + if (pthread_mutex_init(&glob.slock, NULL) != 0) + { + fprintf(stderr, "mutex init failed\n"); + pthread_mutex_destroy(&glob.flock); + return 10; + } + t = (pthread_t*)malloc(sizeof(pthread_t)*glob.threads); + if (!t) + { + fprintf(stderr, "out of memory\n"); + pthread_mutex_destroy(&glob.slock); + pthread_mutex_destroy(&glob.flock); + return 11; + } + for (thread = 0; thread < glob.threads; thread++) + { + if (pthread_create(t + thread, NULL, t_resolver, (void*)(size_t)thread)) + { + interlocked_fprintf(stderr, "failed to create thread #%d\n", thread); + break; + } + } + for (i = 0; i < thread; i++) + { + pthread_join(t[i], NULL); + } + free(t); + stat_print(glob.stats_ct,glob.stats_ct_ok); + pthread_mutex_destroy(&glob.slock); + pthread_mutex_destroy(&glob.flock); + return thread ? 0 : 12; +} + +static void exithelp() +{ + printf( + " --threads=\n" + " --family=<4|6|46>\t; ipv4, ipv6, ipv4+ipv6\n" + " --verbose\t\t; print query progress to stderr\n" + " --stats=N\t\t; print resolve stats to stderr every N domains\n" + ); + exit(1); +} +int main(int argc, char **argv) +{ + int ret, v, option_index = 0; + + static const struct option long_options[] = { + {"threads",required_argument,0,0}, // optidx=0 + {"family",required_argument,0,0}, // optidx=1 + {"verbose",no_argument,0,0}, // optidx=2 + {"stats",required_argument,0,0}, // optidx=3 + {"help",no_argument,0,0}, // optidx=4 + {NULL,0,NULL,0} + }; + + memset(&glob, 0, sizeof(glob)); + glob.family = FAMILY4; + glob.threads = 1; + while ((v = getopt_long_only(argc, argv, "", long_options, &option_index)) != -1) + { + if (v) exithelp(); + switch (option_index) + { + case 0: /* threads */ + glob.threads = optarg ? atoi(optarg) : 0; + if (glob.threads <= 0 || glob.threads > 100) + { + fprintf(stderr, "thread number must be within 1..100\n"); + return 1; + } + break; + case 1: /* family */ + if (!strcmp(optarg, "4")) + glob.family = FAMILY4; + else if (!strcmp(optarg, "6")) + glob.family = FAMILY6; + else if (!strcmp(optarg, "46")) + glob.family = FAMILY4 | FAMILY6; + else + { + fprintf(stderr, "ip family must be 4,6 or 46\n"); + return 1;; + } + break; + case 2: /* verbose */ + glob.verbose = '\1'; + break; + glob.threads = optarg ? atoi(optarg) : 0; + case 3: /* stats */ + glob.stats_every = optarg ? atoi(optarg) : 0; + break; + case 4: /* help */ + exithelp(); + break; + } + } + return run_threads(); +} diff --git a/nfq/Makefile b/nfq/Makefile new file mode 100644 index 0000000..a138ba7 --- /dev/null +++ b/nfq/Makefile @@ -0,0 +1,12 @@ +CC ?= gcc +CFLAGS += -std=c99 -s -O3 +LIBS = -lnetfilter_queue -lnfnetlink -lcap -lz +SRC_FILES = *.c + +all: nfqws + +nfqws: $(SRC_FILES) + $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) $(LIBS) + +clean: + rm -f nfqws *.o diff --git a/nfq/darkmagic.c b/nfq/darkmagic.c new file mode 100644 index 0000000..f6107d6 --- /dev/null +++ b/nfq/darkmagic.c @@ -0,0 +1,312 @@ +#define _GNU_SOURCE +#include "darkmagic.h" +#include +#include +#include +#include + +uint16_t tcp_checksum(const void *buff, int len, in_addr_t src_addr, in_addr_t dest_addr) +{ + const uint16_t *buf=buff; + uint16_t *ip_src=(uint16_t *)&src_addr, *ip_dst=(uint16_t *)&dest_addr; + uint32_t sum; + int length=len; + + // Calculate the sum + sum = 0; + while (len > 1) + { + sum += *buf++; + if (sum & 0x80000000) + sum = (sum & 0xFFFF) + (sum >> 16); + len -= 2; + } + if ( len & 1 ) + { + // Add the padding if the packet lenght is odd + uint16_t v=0; + *(uint8_t *)&v = *((uint8_t *)buf); + sum += v; + } + + // Add the pseudo-header + sum += *(ip_src++); + sum += *ip_src; + sum += *(ip_dst++); + sum += *ip_dst; + sum += htons(IPPROTO_TCP); + sum += htons(length); + + // Add the carries + while (sum >> 16) + sum = (sum & 0xFFFF) + (sum >> 16); + + // Return the one's complement of sum + return (uint16_t)(~sum); +} +void tcp_fix_checksum(struct tcphdr *tcp,int len, in_addr_t src_addr, in_addr_t dest_addr) +{ + tcp->check = 0; + tcp->check = tcp_checksum(tcp,len,src_addr,dest_addr); +} +uint16_t tcp6_checksum(const void *buff, int len, const struct in6_addr *src_addr, const struct in6_addr *dest_addr) +{ + const uint16_t *buf=buff; + const uint16_t *ip_src=(uint16_t *)src_addr, *ip_dst=(uint16_t *)dest_addr; + uint32_t sum; + int length=len; + + // Calculate the sum + sum = 0; + while (len > 1) + { + sum += *buf++; + if (sum & 0x80000000) + sum = (sum & 0xFFFF) + (sum >> 16); + len -= 2; + } + if ( len & 1 ) + { + // Add the padding if the packet lenght is odd + uint16_t v=0; + *(uint8_t *)&v = *((uint8_t *)buf); + sum += v; + } + + // Add the pseudo-header + sum += *(ip_src++); + sum += *(ip_src++); + sum += *(ip_src++); + sum += *(ip_src++); + sum += *(ip_src++); + sum += *(ip_src++); + sum += *(ip_src++); + sum += *ip_src; + sum += *(ip_dst++); + sum += *(ip_dst++); + sum += *(ip_dst++); + sum += *(ip_dst++); + sum += *(ip_dst++); + sum += *(ip_dst++); + sum += *(ip_dst++); + sum += *ip_dst; + sum += htons(IPPROTO_TCP); + sum += htons(length); + + // Add the carries + while (sum >> 16) + sum = (sum & 0xFFFF) + (sum >> 16); + + // Return the one's complement of sum + return (uint16_t)(~sum); +} +void tcp6_fix_checksum(struct tcphdr *tcp,int len, const struct in6_addr *src_addr, const struct in6_addr *dest_addr) +{ + tcp->check = 0; + tcp->check = tcp6_checksum(tcp,len,src_addr,dest_addr); +} + + + +static void fill_tcphdr(struct tcphdr *tcp, uint8_t tcp_flags, uint32_t seq, uint32_t ack_seq, enum tcp_fooling_mode fooling, uint16_t nsport, uint16_t ndport) +{ + char *tcpopt = (char*)(tcp+1); + memset(tcp,0,sizeof(*tcp)); + tcp->source = nsport; + tcp->dest = ndport; + tcp->seq = seq; + tcp->ack_seq = ack_seq; + tcp->doff = 5; + *((uint8_t*)tcp+13)= tcp_flags; + tcp->window = htons(65535); + if (fooling==TCP_FOOL_MD5SIG) + { + tcp->doff += 5; // +20 bytes + tcpopt[0] = 19; // kind + tcpopt[1] = 18; // len + *(uint32_t*)(tcpopt+2)=random(); + *(uint32_t*)(tcpopt+6)=random(); + *(uint32_t*)(tcpopt+10)=random(); + *(uint32_t*)(tcpopt+14)=random(); + tcpopt[18] = 0; // end + tcpopt[19] = 0; + } +} + +static int rawsend_sock=-1; +void rawsend_cleanup() +{ + if (rawsend_sock!=-1) + { + close(rawsend_sock); + rawsend_sock=-1; + } +} +static void rawsend_socket(int family,uint32_t fwmark) +{ + if (rawsend_sock==-1) + { + int yes=1,pri=6; + rawsend_sock = socket(family, SOCK_RAW, IPPROTO_RAW); + if (rawsend_sock==-1) + perror("rawsend: socket()"); + else if (setsockopt(rawsend_sock, SOL_SOCKET, SO_MARK, &fwmark, sizeof(fwmark)) == -1) + { + perror("rawsend: setsockopt(SO_MARK)"); + rawsend_cleanup(); + } + else if (setsockopt(rawsend_sock, SOL_SOCKET, SO_PRIORITY, &pri, sizeof(pri)) == -1) + { + perror("rawsend: setsockopt(SO_PRIORITY)"); + rawsend_cleanup(); + } + } +} +bool rawsend(struct sockaddr* dst,uint32_t fwmark,const void *data,size_t len) +{ + rawsend_socket(dst->sa_family,fwmark); + if (rawsend_sock==-1) return false; + + int salen = dst->sa_family == AF_INET ? sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6); + struct sockaddr_storage dst2; + memcpy(&dst2,dst,salen); + if (dst->sa_family==AF_INET6) + ((struct sockaddr_in6 *)&dst2)->sin6_port = 0; // or will be EINVAL + + int bytes = sendto(rawsend_sock, data, len, 0, (struct sockaddr*)&dst2, salen); + if (bytes==-1) + { + perror("rawsend: sendto"); + return false; + } + return true; +} +bool prepare_tcp_segment4( + const struct sockaddr_in *src, const struct sockaddr_in *dst, + uint8_t tcp_flags, + uint32_t seq, uint32_t ack_seq, + uint8_t ttl, + enum tcp_fooling_mode fooling, + const void *data, uint16_t len, + char *buf, size_t *buflen) +{ + uint16_t tcpoptlen = 0; + if (fooling==TCP_FOOL_MD5SIG) tcpoptlen=20; + uint16_t pktlen = sizeof(struct iphdr) + sizeof(struct tcphdr) + tcpoptlen + len; + if (pktlen>*buflen) + { + fprintf(stderr,"prepare_tcp_segment : packet len cannot exceed %zu\n",*buflen); + return false; + } + + struct iphdr *ip = (struct iphdr*) buf; + struct tcphdr *tcp = (struct tcphdr*) (ip+1); + + ip->frag_off = 0; + ip->version = 4; + ip->ihl = 5; + ip->tot_len = htons(pktlen); + ip->id = 0; + ip->ttl = ttl; + ip->protocol = IPPROTO_TCP; + ip->saddr = src->sin_addr.s_addr; + ip->daddr = dst->sin_addr.s_addr; + + fill_tcphdr(tcp,tcp_flags,seq,ack_seq,fooling,src->sin_port,dst->sin_port); + + memcpy((char*)tcp+sizeof(struct tcphdr)+tcpoptlen,data,len); + tcp_fix_checksum(tcp,sizeof(struct tcphdr)+tcpoptlen+len,ip->saddr,ip->daddr); + if (fooling==TCP_FOOL_BADSUM) tcp->check^=0xBEAF; + + *buflen = pktlen; + return true; +} + + +bool prepare_tcp_segment6( + const struct sockaddr_in6 *src, const struct sockaddr_in6 *dst, + uint8_t tcp_flags, + uint32_t seq, uint32_t ack_seq, + uint8_t ttl, + enum tcp_fooling_mode fooling, + const void *data, uint16_t len, + char *buf, size_t *buflen) +{ + uint16_t tcpoptlen = 0; + if (fooling==TCP_FOOL_MD5SIG) tcpoptlen=20; + uint16_t payloadlen = sizeof(struct tcphdr) + tcpoptlen + len; + uint16_t pktlen = sizeof(struct ip6_hdr) + payloadlen; + if (pktlen>*buflen) + { + fprintf(stderr,"prepare_tcp_segment : packet len cannot exceed %zu\n",*buflen); + return false; + } + + struct ip6_hdr *ip6 = (struct ip6_hdr*) buf; + struct tcphdr *tcp = (struct tcphdr*) (ip6+1); + + ip6->ip6_ctlun.ip6_un1.ip6_un1_flow = htonl(0x60000000); + ip6->ip6_ctlun.ip6_un1.ip6_un1_plen = htons(payloadlen); + ip6->ip6_ctlun.ip6_un1.ip6_un1_nxt = IPPROTO_TCP; + ip6->ip6_ctlun.ip6_un1.ip6_un1_hlim = ttl; + ip6->ip6_src = src->sin6_addr; + ip6->ip6_dst = dst->sin6_addr; + + fill_tcphdr(tcp,tcp_flags,seq,ack_seq,fooling,src->sin6_port,dst->sin6_port); + + memcpy((char*)tcp+sizeof(struct tcphdr)+tcpoptlen,data,len); + tcp6_fix_checksum(tcp,sizeof(struct tcphdr)+tcpoptlen+len,&ip6->ip6_src,&ip6->ip6_dst); + if (fooling==TCP_FOOL_BADSUM) tcp->check^=0xBEAF; + + *buflen = pktlen; + return true; +} + +bool prepare_tcp_segment( + const struct sockaddr *src, const struct sockaddr *dst, + uint8_t tcp_flags, + uint32_t seq, uint32_t ack_seq, + uint8_t ttl, + enum tcp_fooling_mode fooling, + const void *data, uint16_t len, + char *buf, size_t *buflen) +{ + return (src->sa_family==AF_INET && dst->sa_family==AF_INET) ? + prepare_tcp_segment4((struct sockaddr_in *)src,(struct sockaddr_in *)dst,tcp_flags,seq,ack_seq,ttl,fooling,data,len,buf,buflen) : + (src->sa_family==AF_INET6 && dst->sa_family==AF_INET6) ? + prepare_tcp_segment6((struct sockaddr_in6 *)src,(struct sockaddr_in6 *)dst,tcp_flags,seq,ack_seq,ttl,fooling,data,len,buf,buflen) : + false; +} + + +void extract_endpoints(const struct iphdr *iphdr,const struct ip6_hdr *ip6hdr,const struct tcphdr *tcphdr, struct sockaddr_storage *src, struct sockaddr_storage *dst) +{ + if (iphdr) + { + struct sockaddr_in *si = (struct sockaddr_in*)dst; + si->sin_family = AF_INET; + si->sin_port = tcphdr->dest; + si->sin_addr.s_addr = iphdr->daddr; + + si = (struct sockaddr_in*)src; + si->sin_family = AF_INET; + si->sin_port = tcphdr->source; + si->sin_addr.s_addr = iphdr->saddr; + } + else if (ip6hdr) + { + struct sockaddr_in6 *si = (struct sockaddr_in6*)dst; + si->sin6_family = AF_INET6; + si->sin6_port = tcphdr->dest; + si->sin6_addr = ip6hdr->ip6_dst; + si->sin6_flowinfo = 0; + si->sin6_scope_id = 0; + + si = (struct sockaddr_in6*)src; + si->sin6_family = AF_INET6; + si->sin6_port = tcphdr->source; + si->sin6_addr = ip6hdr->ip6_src; + si->sin6_flowinfo = 0; + si->sin6_scope_id = 0; + } +} diff --git a/nfq/darkmagic.h b/nfq/darkmagic.h new file mode 100644 index 0000000..d9dbe98 --- /dev/null +++ b/nfq/darkmagic.h @@ -0,0 +1,51 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +uint16_t tcp_checksum(const void *buff, int len, in_addr_t src_addr, in_addr_t dest_addr); +void tcp_fix_checksum(struct tcphdr *tcp,int len, in_addr_t src_addr, in_addr_t dest_addr); +uint16_t tcp6_checksum(const void *buff, int len, const struct in6_addr *src_addr, const struct in6_addr *dest_addr); +void tcp6_fix_checksum(struct tcphdr *tcp,int len, const struct in6_addr *src_addr, const struct in6_addr *dest_addr); + +enum tcp_fooling_mode { + TCP_FOOL_NONE=0, + TCP_FOOL_MD5SIG=1, + TCP_FOOL_BADSUM=2 +}; +bool prepare_tcp_segment4( + const struct sockaddr_in *src, const struct sockaddr_in *dst, + uint8_t tcp_flags, + uint32_t seq, uint32_t ack_seq, + uint8_t ttl, + enum tcp_fooling_mode fooling, + const void *data, uint16_t len, + char *buf, size_t *buflen); +bool prepare_tcp_segment6( + const struct sockaddr_in6 *src, const struct sockaddr_in6 *dst, + uint8_t tcp_flags, + uint32_t seq, uint32_t ack_seq, + uint8_t ttl, + enum tcp_fooling_mode fooling, + const void *data, uint16_t len, + char *buf, size_t *buflen); +bool prepare_tcp_segment( + const struct sockaddr *src, const struct sockaddr *dst, + uint8_t tcp_flags, + uint32_t seq, uint32_t ack_seq, + uint8_t ttl, + enum tcp_fooling_mode fooling, + const void *data, uint16_t len, + char *buf, size_t *buflen); + +void extract_endpoints(const struct iphdr *iphdr,const struct ip6_hdr *ip6hdr,const struct tcphdr *tcphdr, struct sockaddr_storage *src, struct sockaddr_storage *dst); + +// auto creates internal socket and uses it for subsequent calls +bool rawsend(struct sockaddr* dst,uint32_t fwmark,const void *data,size_t len); +// cleans up socket autocreated by rawsend +void rawsend_cleanup(); diff --git a/nfq/gzip.c b/nfq/gzip.c new file mode 100644 index 0000000..d536b23 --- /dev/null +++ b/nfq/gzip.c @@ -0,0 +1,82 @@ +#include +#include +#include +#include "gzip.h" + +#define ZCHUNK 16384 +#define BUFMIN 128 +#define BUFCHUNK (1024*128) + +int z_readfile(FILE *F,char **buf,size_t *size) +{ + z_stream zs; + int r; + unsigned char in[ZCHUNK]; + size_t bufsize; + void *newbuf; + + memset(&zs,0,sizeof(zs)); + + *buf = NULL; + bufsize=*size=0; + + r=inflateInit2(&zs,47); + if (r != Z_OK) return r; + + do + { + zs.avail_in = fread(in, 1, sizeof(in), F); + if (ferror(F)) + { + r = Z_ERRNO; + goto zerr; + } + if (!zs.avail_in) break; + zs.next_in = in; + do + { + if ((bufsize-*size) +#include +#include + +int z_readfile(FILE *F,char **buf,size_t *size); +bool is_gzip(FILE* F); diff --git a/nfq/hostlist.c b/nfq/hostlist.c new file mode 100644 index 0000000..b4136d2 --- /dev/null +++ b/nfq/hostlist.c @@ -0,0 +1,112 @@ +#include +#include "hostlist.h" +#include "gzip.h" + + +static bool addpool(strpool **hostlist, char **s, char *end) +{ + char *p; + + // advance until eol lowering all chars + for (p = *s; p +#include "strpool.h" + +bool LoadHostList(strpool **hostlist, char *filename); +bool SearchHostList(strpool *hostlist, const char *host,bool debug); diff --git a/nfq/nfqws.c b/nfq/nfqws.c new file mode 100644 index 0000000..11bceb1 --- /dev/null +++ b/nfq/nfqws.c @@ -0,0 +1,1041 @@ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "darkmagic.h" +#include "hostlist.h" +#include "sec.h" + +#define NF_DROP 0 +#define NF_ACCEPT 1 + + +#define Q_RCVBUF (128*1024) // in bytes +#define Q_MAXLEN 1024 // in packets +#define DPI_DESYNC_FWMARK_DEFAULT 0x40000000 + + +static const char fake_http_request[] = "GET / HTTP/1.1\r\nHost: www.w3.org\r\n" + "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:70.0) Gecko/20100101 Firefox/70.0\r\n" + "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n" + "Accept-Encoding: gzip, deflate\r\n\r\n"; +static const uint8_t fake_https_request[] = { + 0x16, 0x03, 0x01, 0x02, 0x00, 0x01, 0x00, 0x01, 0xfc, 0x03, 0x03, 0x9a, 0x8f, 0xa7, 0x6a, 0x5d, + 0x57, 0xf3, 0x62, 0x19, 0xbe, 0x46, 0x82, 0x45, 0xe2, 0x59, 0x5c, 0xb4, 0x48, 0x31, 0x12, 0x15, + 0x14, 0x79, 0x2c, 0xaa, 0xcd, 0xea, 0xda, 0xf0, 0xe1, 0xfd, 0xbb, 0x20, 0xf4, 0x83, 0x2a, 0x94, + 0xf1, 0x48, 0x3b, 0x9d, 0xb6, 0x74, 0xba, 0x3c, 0x81, 0x63, 0xbc, 0x18, 0xcc, 0x14, 0x45, 0x57, + 0x6c, 0x80, 0xf9, 0x25, 0xcf, 0x9c, 0x86, 0x60, 0x50, 0x31, 0x2e, 0xe9, 0x00, 0x22, 0x13, 0x01, + 0x13, 0x03, 0x13, 0x02, 0xc0, 0x2b, 0xc0, 0x2f, 0xcc, 0xa9, 0xcc, 0xa8, 0xc0, 0x2c, 0xc0, 0x30, + 0xc0, 0x0a, 0xc0, 0x09, 0xc0, 0x13, 0xc0, 0x14, 0x00, 0x33, 0x00, 0x39, 0x00, 0x2f, 0x00, 0x35, + 0x01, 0x00, 0x01, 0x91, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x0d, 0x00, 0x00, 0x0a, 0x77, 0x77, 0x77, + 0x2e, 0x77, 0x33, 0x2e, 0x6f, 0x72, 0x67, 0x00, 0x17, 0x00, 0x00, 0xff, 0x01, 0x00, 0x01, 0x00, + 0x00, 0x0a, 0x00, 0x0e, 0x00, 0x0c, 0x00, 0x1d, 0x00, 0x17, 0x00, 0x18, 0x00, 0x19, 0x01, 0x00, + 0x01, 0x01, 0x00, 0x0b, 0x00, 0x02, 0x01, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x10, 0x00, 0x0e, + 0x00, 0x0c, 0x02, 0x68, 0x32, 0x08, 0x68, 0x74, 0x74, 0x70, 0x2f, 0x31, 0x2e, 0x31, 0x00, 0x05, + 0x00, 0x05, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x33, 0x00, 0x6b, 0x00, 0x69, 0x00, 0x1d, 0x00, + 0x20, 0xb0, 0xe4, 0xda, 0x34, 0xb4, 0x29, 0x8d, 0xd3, 0x5c, 0x70, 0xd3, 0xbe, 0xe8, 0xa7, 0x2a, + 0x6b, 0xe4, 0x11, 0x19, 0x8b, 0x18, 0x9d, 0x83, 0x9a, 0x49, 0x7c, 0x83, 0x7f, 0xa9, 0x03, 0x8c, + 0x3c, 0x00, 0x17, 0x00, 0x41, 0x04, 0x4c, 0x04, 0xa4, 0x71, 0x4c, 0x49, 0x75, 0x55, 0xd1, 0x18, + 0x1e, 0x22, 0x62, 0x19, 0x53, 0x00, 0xde, 0x74, 0x2f, 0xb3, 0xde, 0x13, 0x54, 0xe6, 0x78, 0x07, + 0x94, 0x55, 0x0e, 0xb2, 0x6c, 0xb0, 0x03, 0xee, 0x79, 0xa9, 0x96, 0x1e, 0x0e, 0x98, 0x17, 0x78, + 0x24, 0x44, 0x0c, 0x88, 0x80, 0x06, 0x8b, 0xd4, 0x80, 0xbf, 0x67, 0x7c, 0x37, 0x6a, 0x5b, 0x46, + 0x4c, 0xa7, 0x98, 0x6f, 0xb9, 0x22, 0x00, 0x2b, 0x00, 0x09, 0x08, 0x03, 0x04, 0x03, 0x03, 0x03, + 0x02, 0x03, 0x01, 0x00, 0x0d, 0x00, 0x18, 0x00, 0x16, 0x04, 0x03, 0x05, 0x03, 0x06, 0x03, 0x08, + 0x04, 0x08, 0x05, 0x08, 0x06, 0x04, 0x01, 0x05, 0x01, 0x06, 0x01, 0x02, 0x03, 0x02, 0x01, 0x00, + 0x2d, 0x00, 0x02, 0x01, 0x01, 0x00, 0x1c, 0x00, 0x02, 0x40, 0x01, 0x00, 0x15, 0x00, 0x96, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00 +}; + + +static uint8_t zeropkt[1500]; + + +enum dpi_desync_mode { + DESYNC_NONE=0, + DESYNC_FAKE, + DESYNC_RST, + DESYNC_RSTACK, + DESYNC_DISORDER +}; + + +struct params_s +{ + bool debug; + int wsize; + int qnum; + bool hostcase, hostnospace; + char hostspell[4]; + enum dpi_desync_mode desync_mode; + bool desync_retrans,desync_skip_nosni; + int desync_split_pos; + uint8_t desync_ttl; + enum tcp_fooling_mode desync_tcp_fooling_mode; + uint32_t desync_fwmark; + char hostfile[256]; + strpool *hostlist; +}; + +static struct params_s params; + +#define DLOG(format, ...) {if (params.debug) printf(format, ##__VA_ARGS__);} + + +static bool bHup = false; +static void onhup(int sig) +{ + printf("HUP received !\n"); + if (params.hostlist) + printf("Will reload hostlist on next request\n"); + bHup = true; +} +// should be called in normal execution +static void dohup() +{ + if (bHup) + { + if (params.hostlist) + { + if (!LoadHostList(¶ms.hostlist, params.hostfile)) + { + // what will we do without hostlist ?? sure, gonna die + exit(1); + } + } + bHup = false; + } +} + + +static const uint8_t *find_bin_const(const uint8_t *data, size_t len, const void *blk, size_t blk_len) +{ + while (len >= blk_len) + { + if (!memcmp(data, blk, blk_len)) + return data; + data++; + len--; + } + return NULL; +} +static uint8_t *find_bin(uint8_t *data, size_t len, const void *blk, size_t blk_len) +{ + while (len >= blk_len) + { + if (!memcmp(data, blk, blk_len)) + return data; + data++; + len--; + } + return NULL; +} + + +static void print_sockaddr(const struct sockaddr *sa) +{ + char str[64]; + switch (sa->sa_family) + { + case AF_INET: + if (inet_ntop(sa->sa_family, &((struct sockaddr_in*)sa)->sin_addr, str, sizeof(str))) + printf("%s:%d", str, ntohs(((struct sockaddr_in*)sa)->sin_port)); + break; + case AF_INET6: + if (inet_ntop(sa->sa_family, &((struct sockaddr_in6*)sa)->sin6_addr, str, sizeof(str))) + printf("%s:%d", str, ntohs(((struct sockaddr_in6*)sa)->sin6_port)); + break; + default: + printf("UNKNOWN_FAMILY_%d", sa->sa_family); + } +} + + +static bool proto_check_ipv4(uint8_t *data, size_t len) +{ + return len >= 20 && (data[0] & 0xF0) == 0x40 && + len >= ((data[0] & 0x0F) << 2); +} +// move to transport protocol +static void proto_skip_ipv4(uint8_t **data, size_t *len) +{ + size_t l; + + l = (**data & 0x0F) << 2; + *data += l; + *len -= l; +} +static bool proto_check_tcp(uint8_t *data, size_t len) +{ + return len >= 20 && len >= ((data[12] & 0xF0) >> 2); +} +static void proto_skip_tcp(uint8_t **data, size_t *len) +{ + size_t l; + l = ((*data)[12] & 0xF0) >> 2; + *data += l; + *len -= l; +} + +static bool proto_check_ipv6(uint8_t *data, size_t len) +{ + return len >= 40 && (data[0] & 0xF0) == 0x60 && + (len - 40) >= htons(*(uint16_t*)(data + 4)); // payload length +} +// move to transport protocol +// proto_type = 0 => error +static void proto_skip_ipv6(uint8_t **data, size_t *len, uint8_t *proto_type) +{ + size_t hdrlen; + uint8_t HeaderType; + + *proto_type = 0; // put error in advance + + HeaderType = (*data)[6]; // NextHeader field + *data += 40; *len -= 40; // skip ipv6 base header + while (*len > 0) // need at least one byte for NextHeader field + { + switch (HeaderType) + { + case 0: // Hop-by-Hop Options + case 60: // Destination Options + case 43: // routing + if (*len < 2) return; // error + hdrlen = 8 + ((*data)[1] << 3); + break; + case 44: // fragment + hdrlen = 8; + break; + case 59: // no next header + return; // error + default: + // we found some meaningful payload. it can be tcp, udp, icmp or some another exotic shit + *proto_type = HeaderType; + return; + } + if (*len < hdrlen) return; // error + HeaderType = **data; + // advance to the next header location + *len -= hdrlen; + *data += hdrlen; + } + // we have garbage +} + +static inline bool tcp_synack_segment(const struct tcphdr *tcphdr) +{ + /* check for set bits in TCP hdr */ + return tcphdr->urg == 0 && + tcphdr->ack == 1 && + tcphdr->psh == 0 && + tcphdr->rst == 0 && + tcphdr->syn == 1 && + tcphdr->fin == 0; +} +static inline bool tcp_ack_segment(const struct tcphdr *tcphdr) +{ + /* check for set bits in TCP hdr */ + return tcphdr->urg == 0 && + tcphdr->ack == 1 && + tcphdr->rst == 0 && + tcphdr->syn == 0 && + tcphdr->fin == 0; +} + + +static void tcp_rewrite_winsize(struct tcphdr *tcp, uint16_t winsize) +{ + uint16_t winsize_old; + /* + uint8_t scale_factor=1; + int optlen = (tcp->doff << 2); + uint8_t *opt = (uint8_t*)(tcp+1); + + optlen = optlen>sizeof(struct tcphdr) ? optlen-sizeof(struct tcphdr) : 0; + printf("optslen=%d\n",optlen); + while (optlen) + { + switch(*opt) + { + case 0: break; // end of option list; + case 1: opt++; optlen--; break; // noop + default: + if (optlen<2 || optlen=3) + { + scale_factor=opt[2]; + printf("Found scale factor %u\n",opt[2]); + //opt[2]=0; + } + optlen-=opt[1]; + opt+=opt[1]; + } + } + */ + winsize_old = htons(tcp->window); // << scale_factor; + tcp->window = htons(winsize); + DLOG("Window size change %u => %u\n", winsize_old, winsize) +} + + + +static const char *http_methods[] = { "GET /","POST /","HEAD /","OPTIONS /","PUT /","DELETE /","CONNECT /","TRACE /",NULL }; +static bool IsHttp(const char *data, size_t len) +{ + const char **method; + size_t method_len; + for (method = http_methods; *method; method++) + { + method_len = strlen(*method); + if (method_len <= len && !memcmp(data, *method, method_len)) + return true; + } + return false; +} +static bool HttpExtractHost(const uint8_t *data, size_t len, char *host, size_t len_host) +{ + const uint8_t *p, *s, *e=data+len; + + p = find_bin_const(data, len, "\nHost:", 6); + if (!p) return false; + p+=6; + while(pp) + { + size_t slen = s-p; + if (host && len_host) + { + if (slen>=len_host) slen=len_host-1; + for(size_t i=0;i=6 && data[0]==0x16 && data[1]==0x03 && data[2]==0x01 && data[5]==0x01 && (ntohs(*(uint16_t*)(data+3))+5)<=len; +} +static bool TLSFindExt(const uint8_t *data, size_t len, uint16_t type, const uint8_t **ext, size_t *len_ext) +{ + // +0 + // u8 ContentType: Handshake + // u16 Version: TLS1.0 + // u16 Length + // +5 + // u8 HandshakeType: ClientHello + // u24 Length + // u16 Version + // c[32] random + // u8 SessionIDLength + // + // u16 CipherSuitesLength + // + // u8 CompressionMethodsLength + // + // u16 ExtensionsLength + + size_t l,ll; + + l = 1+2+2+1+3+2+32; + // SessionIDLength + if (len<(l+1)) return false; + ll = data[6]<<16 | data[7]<<8 | data[8]; // HandshakeProtocol length + if (len<(ll+9)) return false; + l += data[l]+1; + // CipherSuitesLength + if (len<(l+2)) return false; + l += ntohs(*(uint16_t*)(data+l))+2; + // CompressionMethodsLength + if (len<(l+1)) return false; + l += data[l]+1; + // ExtensionsLength + if (len<(l+2)) return false; + + data+=l; len-=l; + l=ntohs(*(uint16_t*)data); + data+=2; len-=2; + if (l=4) + { + uint16_t etype=*(uint16_t*)data; + size_t elen=ntohs(*(uint16_t*)(data+2)); + data+=4; l-=4; + if (l=len_host) slen=len_host-1; + for(size_t i=0;i %c%c%c%c:\n", params.hostspell[0], params.hostspell[1], params.hostspell[2], params.hostspell[3]) + memcpy(phost + 2, params.hostspell, 4); + bRet = true; + } + if (params.hostnospace && (pua = find_bin(data, len, "\r\nUser-Agent: ", 14)) && (pua = find_bin(pua + 1, len - (pua - data) - 1, "\r\n", 2))) + { + DLOG("removing space after Host: and adding it to User-Agent:\n") + if (pua > phost) + { + memmove(phost + 7, phost + 8, pua - phost - 8); + phost[pua - phost - 1] = ' '; + } + else + { + memmove(pua + 1, pua, phost - pua + 7); + *pua = ' '; + } + bRet = true; + } + } + return bRet; +} + + + +// result : true - drop original packet, false = dont drop +static bool dpi_desync_packet(const uint8_t *data_pkt, size_t len_pkt, const struct iphdr *iphdr, const struct ip6_hdr *ip6hdr, const struct tcphdr *tcphdr, const uint8_t *data_payload, size_t len_payload) +{ + if (!!iphdr == !!ip6hdr) return false; // one and only one must be present + + if (!tcphdr->syn && len_payload) + { + struct sockaddr_storage src, dst; + const uint8_t *fake; + size_t fake_size; + char host[256]; + bool bHaveHost=false; + + if (IsHttp(data_payload,len_payload)) + { + DLOG("packet contains HTTP request\n") + fake = (uint8_t*)fake_http_request; + fake_size = sizeof(fake_http_request); + if (params.hostlist || params.debug) bHaveHost=HttpExtractHost(data_payload,len_payload,host,sizeof(host)); + } + else if (IsTLSClientHello(data_payload,len_payload)) + { + DLOG("packet contains TLS ClientHello\n") + fake = (uint8_t*)fake_https_request; + fake_size = sizeof(fake_https_request); + if (params.hostlist || params.desync_skip_nosni || params.debug) + { + bHaveHost=TLSHelloExtractHost(data_payload,len_payload,host,sizeof(host)); + if (params.desync_skip_nosni && !bHaveHost) + { + DLOG("Not applying dpi-desync to TLS ClientHello without hostname in the SNI\n") + return false; + } + } + + } + else + return false; + + if (bHaveHost) + { + DLOG("hostname: %s\n",host) + if (params.hostlist && !SearchHostList(params.hostlist,host,params.debug)) + { + DLOG("Not applying dpi-desync to this request\n") + return false; + } + } + + extract_endpoints(iphdr, ip6hdr, tcphdr, &src, &dst); + if (params.debug) + { + printf("dpi desync src="); + print_sockaddr((struct sockaddr *)&src); + printf(" dst="); + print_sockaddr((struct sockaddr *)&dst); + printf("\n"); + } + + uint8_t newdata[1600]; + size_t newlen = sizeof(newdata); + uint8_t ttl_orig = iphdr ? iphdr->ttl : ip6hdr->ip6_ctlun.ip6_un1.ip6_un1_hlim; + uint8_t ttl_fake = params.desync_ttl ? params.desync_ttl : ttl_orig; + uint8_t flags_orig = *((uint8_t*)tcphdr+13); + + switch(params.desync_mode) + { + case DESYNC_FAKE: + if (!prepare_tcp_segment((struct sockaddr *)&src, (struct sockaddr *)&dst, flags_orig, tcphdr->seq, tcphdr->ack_seq, + ttl_fake,params.desync_tcp_fooling_mode, + fake, fake_size, newdata, &newlen)) + { + return false; + } + break; + case DESYNC_RST: + case DESYNC_RSTACK: + if (!prepare_tcp_segment((struct sockaddr *)&src, (struct sockaddr *)&dst, TH_RST | (params.desync_mode==DESYNC_RSTACK ? TH_ACK:0), tcphdr->seq, tcphdr->ack_seq, + ttl_fake,params.desync_tcp_fooling_mode, + NULL, 0, newdata, &newlen)) + { + return false; + } + break; + case DESYNC_DISORDER: + { + size_t split_pos=len_payload>params.desync_split_pos ? params.desync_split_pos : 1; + uint8_t fakeseg[1600]; + size_t fakeseg_len; + + if (split_posseq+split_pos, tcphdr->ack_seq, + ttl_orig,TCP_FOOL_NONE, + data_payload+split_pos, len_payload-split_pos, newdata, &newlen) || + !rawsend((struct sockaddr *)&dst, params.desync_fwmark, newdata, newlen)) + { + return false; + } + } + + + DLOG("sending fake(1) 1st out-of-order tcp segment 0-%zu len=%zu\n",split_pos-1, split_pos) + fakeseg_len = sizeof(fakeseg); + if (!prepare_tcp_segment((struct sockaddr *)&src, (struct sockaddr *)&dst, flags_orig, tcphdr->seq, tcphdr->ack_seq, + ttl_fake,params.desync_tcp_fooling_mode, + zeropkt, split_pos, fakeseg, &fakeseg_len) || + !rawsend((struct sockaddr *)&dst, params.desync_fwmark, fakeseg, fakeseg_len)) + { + return false; + } + + + DLOG("sending 1st out-of-order tcp segment 0-%zu len=%zu\n",split_pos-1, split_pos) + newlen = sizeof(newdata); + if (!prepare_tcp_segment((struct sockaddr *)&src, (struct sockaddr *)&dst, flags_orig, tcphdr->seq, tcphdr->ack_seq, + ttl_orig,TCP_FOOL_NONE, + data_payload, split_pos, newdata, &newlen) || + !rawsend((struct sockaddr *)&dst, params.desync_fwmark, newdata, newlen)) + { + return false; + } + + DLOG("sending fake(2) 1st out-of-order tcp segment 0-%zu len=%zu\n",split_pos-1, split_pos) + if (!rawsend((struct sockaddr *)&dst, params.desync_fwmark, fakeseg, fakeseg_len)) + return false; + + return true; + } + break; + + default: + return false; + } + + if (!rawsend((struct sockaddr *)&dst, params.desync_fwmark, newdata, newlen)) + return false; + + if (params.desync_retrans) + DLOG("dropping packet to force retransmission. len=%zu len_payload=%zu\n", len_pkt, len_payload) + else + { + DLOG("reinjecting original packet. len=%zu len_payload=%zu\n", len_pkt, len_payload) + if (!rawsend((struct sockaddr *)&dst, params.desync_fwmark, data_pkt, len_pkt)) + return false; + } + return true; + } + + return false; +} + + +typedef enum +{ + pass = 0, modify, drop +} packet_process_result; +static packet_process_result processPacketData(uint8_t *data_pkt, size_t len_pkt, uint32_t *mark) +{ + struct iphdr *iphdr = NULL; + struct ip6_hdr *ip6hdr = NULL; + struct tcphdr *tcphdr = NULL; + size_t len = len_pkt, len_tcp; + uint8_t *data = data_pkt; + packet_process_result res = pass; + uint8_t proto; + + if (proto_check_ipv4(data, len)) + { + iphdr = (struct iphdr *) data; + proto = iphdr->protocol; + proto_skip_ipv4(&data, &len); + } + else if (proto_check_ipv6(data, len)) + { + ip6hdr = (struct ip6_hdr *) data; + proto_skip_ipv6(&data, &len, &proto); + } + else + { + // not ipv6 and not ipv4 + return res; + } + + if (proto == IPPROTO_TCP && proto_check_tcp(data, len)) + { + + tcphdr = (struct tcphdr *) data; + len_tcp = len; + proto_skip_tcp(&data, &len); + //DLOG("got TCP packet. payload_len=%d\n",len) + + if (params.desync_mode!=DESYNC_NONE && !(*mark & params.desync_fwmark)) + { + if (dpi_desync_packet(data_pkt, len_pkt, iphdr, ip6hdr, tcphdr, data, len)) + res = drop; + } + + if (res!=drop && modify_tcp_packet(data, len, tcphdr)) + { + if (iphdr) + tcp_fix_checksum(tcphdr, len_tcp, iphdr->saddr, iphdr->daddr); + else + tcp6_fix_checksum(tcphdr, len_tcp, &ip6hdr->ip6_src, &ip6hdr->ip6_dst); + res = modify; + } + } + return res; +} + + +static int cb(struct nfq_q_handle *qh, struct nfgenmsg *nfmsg, + struct nfq_data *nfa, void *cookie) +{ + int id; + size_t len; + struct nfqnl_msg_packet_hdr *ph; + uint8_t *data; + + ph = nfq_get_msg_packet_hdr(nfa); + id = ph ? ntohl(ph->packet_id) : 0; + + uint32_t mark = nfq_get_nfmark(nfa); + len = nfq_get_payload(nfa, &data); + DLOG("packet: id=%d len=%zu\n", id, len) + if (len >= 0) + { + switch (processPacketData(data, len, &mark)) + { + case modify: return nfq_set_verdict2(qh, id, NF_ACCEPT, mark, len, data); + case drop: return nfq_set_verdict2(qh, id, NF_DROP, mark, 0, NULL); + } + } + + return nfq_set_verdict2(qh, id, NF_ACCEPT, mark, 0, NULL); +} + + + +static void exithelp() +{ + printf( + " --debug=0|1\n" + " --qnum=\n" + " --daemon\t\t\t\t; daemonize\n" + " --pidfile=\t\t\t; write pid to file\n" + " --user=\t\t\t; drop root privs\n" + " --uid=uid[:gid]\t\t\t; drop root privs\n" + " --wsize=\t\t\t; set window size. 0 = do not modify\n" + " --hostcase\t\t\t\t; change Host: => host:\n" + " --hostspell\t\t\t\t; exact spelling of \"Host\" header. must be 4 chars. default is \"host\"\n" + " --hostnospace\t\t\t\t; remove space after Host: and add it to User-Agent: to preserve packet size\n" + " --dpi-desync[=]\t\t\t; try to desync dpi state. modes : fake rst rstack disorder\n" + " --dpi-desync-fwmark=\t; override fwmark for desync packet. default = 0x%08X\n" + " --dpi-desync-ttl=\t\t\t; set ttl for desync packet\n" + " --dpi-desync-fooling=none|md5sig|badsum\n" + " --dpi-desync-retrans=0|1\t\t; 0(default)=reinject original data packet after fake 1=drop original data packet to force its retransmission\n" + " --dpi-desync-skip-nosni=0|1\t\t; 1(default)=do not act on ClientHello without SNI (ESNI ?)\n" + " --dpi-desync-split-pos=<1..%d>\t; (for disorder only) split TCP packet at specified position\n" + " --hostlist=\t\t\t; apply dpi desync only to the listed hosts (one host per line, subdomains auto apply)\n", + DPI_DESYNC_FWMARK_DEFAULT,sizeof(zeropkt) + ); + exit(1); +} + +void cleanup_params() +{ + if (params.hostlist) + { + StrPoolDestroy(¶ms.hostlist); + params.hostlist = NULL; + } +} +void exithelp_clean() +{ + cleanup_params(); + exithelp(); +} +void exit_clean(int code) +{ + cleanup_params(); + exit(code); +} + + + +int main(int argc, char **argv) +{ + struct nfq_handle *h; + struct nfq_q_handle *qh; + int fd; + int rv; + char buf[4096] __attribute__((aligned)); + int option_index = 0; + int v; + bool daemon = false; + uid_t uid = 0; + gid_t gid = 0; + char pidfile[256]; + + srand(time(NULL)); + + memset(zeropkt, 0, sizeof(zeropkt)); + + memset(¶ms, 0, sizeof(params)); + memcpy(params.hostspell, "host", 4); // default hostspell + *pidfile = 0; + + params.desync_fwmark = DPI_DESYNC_FWMARK_DEFAULT; + params.desync_skip_nosni = true; + params.desync_split_pos = 3; + + const struct option long_options[] = { + {"debug",optional_argument,0,0}, // optidx=0 + {"qnum",required_argument,0,0}, // optidx=1 + {"daemon",no_argument,0,0}, // optidx=2 + {"pidfile",required_argument,0,0}, // optidx=3 + {"user",required_argument,0,0 }, // optidx=4 + {"uid",required_argument,0,0 }, // optidx=5 + {"wsize",required_argument,0,0}, // optidx=6 + {"hostcase",no_argument,0,0}, // optidx=7 + {"hostspell",required_argument,0,0}, // optidx=8 + {"hostnospace",no_argument,0,0}, // optidx=9 + {"dpi-desync",optional_argument,0,0}, // optidx=10 + {"dpi-desync-fwmark",required_argument,0,0}, // optidx=11 + {"dpi-desync-ttl",required_argument,0,0}, // optidx=12 + {"dpi-desync-fooling",required_argument,0,0}, // optidx=13 + {"dpi-desync-retrans",optional_argument,0,0}, // optidx=14 + {"dpi-desync-skip-nosni",optional_argument,0,0},// optidx=15 + {"dpi-desync-split-pos",required_argument,0,0},// optidx=16 + {"hostlist",required_argument,0,0}, // optidx=17 + {NULL,0,NULL,0} + }; + if (argc < 2) exithelp(); + while ((v = getopt_long_only(argc, argv, "", long_options, &option_index)) != -1) + { + if (v) exithelp(); + switch (option_index) + { + case 0: /* debug */ + params.debug = !optarg || atoi(optarg); + break; + case 1: /* qnum */ + params.qnum = atoi(optarg); + if (params.qnum < 0 || params.qnum>65535) + { + fprintf(stderr, "bad qnum\n"); + exit_clean(1); + } + break; + case 2: /* daemon */ + daemon = true; + break; + case 3: /* pidfile */ + strncpy(pidfile, optarg, sizeof(pidfile)); + pidfile[sizeof(pidfile) - 1] = '\0'; + break; + case 4: /* user */ + { + struct passwd *pwd = getpwnam(optarg); + if (!pwd) + { + fprintf(stderr, "non-existent username supplied\n"); + exit_clean(1); + } + uid = pwd->pw_uid; + gid = pwd->pw_gid; + break; + } + case 5: /* uid */ + gid = 0x7FFFFFFF; // default git. drop gid=0 + if (!sscanf(optarg, "%u:%u", &uid, &gid)) + { + fprintf(stderr, "--uid should be : uid[:gid]\n"); + exit_clean(1); + } + break; + case 6: /* wsize */ + params.wsize = atoi(optarg); + if (params.wsize < 0 || params.wsize>65535) + { + fprintf(stderr, "bad wsize\n"); + exit_clean(1); + } + break; + case 7: /* hostcase */ + params.hostcase = true; + break; + case 8: /* hostspell */ + if (strlen(optarg) != 4) + { + fprintf(stderr, "hostspell must be exactly 4 chars long\n"); + exit_clean(1); + } + params.hostcase = true; + memcpy(params.hostspell, optarg, 4); + break; + case 9: /* hostnospace */ + params.hostnospace = true; + break; + case 10: /* dpi-desync */ + if (!optarg || !strcmp(optarg,"fake")) + params.desync_mode = DESYNC_FAKE; + else if (!strcmp(optarg,"rst")) + params.desync_mode = DESYNC_RST; + else if (!strcmp(optarg,"rstack")) + params.desync_mode = DESYNC_RSTACK; + else if (!strcmp(optarg,"disorder")) + params.desync_mode = DESYNC_DISORDER; + else + { + fprintf(stderr, "invalid dpi-desync mode\n"); + exit_clean(1); + } + break; + case 11: /* dpi-desync */ + params.desync_fwmark = 0; + if (!sscanf(optarg, "0x%X", ¶ms.desync_fwmark)) sscanf(optarg, "%u", ¶ms.desync_fwmark); + if (!params.desync_fwmark) + { + fprintf(stderr, "dpi-desync-fwmark should be decimal or 0xHEX and should not be zero\n"); + exit_clean(1); + } + break; + case 12: /* dpi-desync-ttl */ + params.desync_ttl = (uint8_t)atoi(optarg); + break; + case 13: /* dpi-desync-fooling */ + if (!strcmp(optarg,"none")) + params.desync_tcp_fooling_mode = TCP_FOOL_NONE; + else if (!strcmp(optarg,"md5sig")) + params.desync_tcp_fooling_mode = TCP_FOOL_MD5SIG; + else if (!strcmp(optarg,"badsum")) + params.desync_tcp_fooling_mode = TCP_FOOL_BADSUM; + else + { + fprintf(stderr, "dpi-desync-fooling allowed values : none,md5sig,badsum\n"); + exit_clean(1); + } + break; + case 14: /* dpi-desync-retrans */ + params.desync_retrans = !optarg || atoi(optarg); + break; + case 15: /* dpi-desync-skip-nosni */ + params.desync_skip_nosni = !optarg || atoi(optarg); + break; + case 16: /* dpi-desync-split-pos */ + params.desync_split_pos = atoi(optarg); + if (params.desync_split_pos<1 || params.desync_split_pos>sizeof(zeropkt)) + { + fprintf(stderr, "dpi-desync-split-pos must be within 1..%u range\n",sizeof(zeropkt)); + exit_clean(1); + } + break; + case 17: /* hostlist */ + if (!LoadHostList(¶ms.hostlist, optarg)) + exit_clean(1); + strncpy(params.hostfile,optarg,sizeof(params.hostfile)); + params.hostfile[sizeof(params.hostfile)-1]='\0'; + break; + } + } + + if (params.desync_mode==DESYNC_NONE && params.hostlist) + { + fprintf(stderr, "hostlist is applicable only to dpi-desync\n"); + exit_clean(1); + } + + if (daemon) daemonize(); + + h = NULL; + qh = NULL; + + if (*pidfile && !writepid(pidfile)) + { + fprintf(stderr, "could not write pidfile\n"); + goto exiterr; + } + + printf("opening library handle\n"); + h = nfq_open(); + if (!h) { + fprintf(stderr, "error during nfq_open()\n"); + goto exiterr; + } + + printf("unbinding existing nf_queue handler for AF_INET (if any)\n"); + if (nfq_unbind_pf(h, AF_INET) < 0) { + fprintf(stderr, "error during nfq_unbind_pf()\n"); + goto exiterr; + } + + printf("binding nfnetlink_queue as nf_queue handler for AF_INET\n"); + if (nfq_bind_pf(h, AF_INET) < 0) { + fprintf(stderr, "error during nfq_bind_pf()\n"); + goto exiterr; + } + + printf("binding this socket to queue '%u'\n", params.qnum); + qh = nfq_create_queue(h, params.qnum, &cb, ¶ms); + if (!qh) { + fprintf(stderr, "error during nfq_create_queue()\n"); + goto exiterr; + } + + printf("setting copy_packet mode\n"); + if (nfq_set_mode(qh, NFQNL_COPY_PACKET, 0xffff) < 0) { + fprintf(stderr, "can't set packet_copy mode\n"); + goto exiterr; + } + if (nfq_set_queue_maxlen(qh, Q_MAXLEN) < 0) { + fprintf(stderr, "can't set queue maxlen\n"); + goto exiterr; + } + // accept packets if they cant be handled + if (nfq_set_queue_flags(qh, NFQA_CFG_F_FAIL_OPEN , NFQA_CFG_F_FAIL_OPEN)) + { + fprintf(stderr, "can't set queue flags. errno=%d\n", errno); + // dot not fail. not supported on old linuxes <3.6 + } + + if (!droproot(uid, gid)) goto exiterr; + printf("Running as UID=%u GID=%u\n", getuid(), getgid()); + + signal(SIGHUP, onhup); + + fd = nfq_fd(h); + + // increase socket buffer size. on slow systems reloading hostlist can take a while. + // if too many unhandled packets are received its possible to get "no buffer space available" error + rv = Q_RCVBUF/2; + if (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &rv, sizeof(int)) <0) + { + perror("setsockopt (SO_RCVBUF): "); + goto exiterr; + } + do + { + while ((rv = recv(fd, buf, sizeof(buf), 0)) && rv >= 0) + { + dohup(); + int r = nfq_handle_packet(h, buf, rv); + if (r) fprintf(stderr, "nfq_handle_packet error %d\n", r); + } + fprintf(stderr, "recv: errno %d\n",errno); + perror("recv"); + // do not fail on ENOBUFS + } while(errno==ENOBUFS); + + printf("unbinding from queue 0\n"); + nfq_destroy_queue(qh); + +#ifdef INSANE + /* normally, applications SHOULD NOT issue this command, since + * it detaches other programs/sockets from AF_INET, too ! */ + printf("unbinding from AF_INET\n"); + nfq_unbind_pf(h, AF_INET); +#endif + + printf("closing library handle\n"); + nfq_close(h); + + rawsend_cleanup(); + cleanup_params(); + return 0; + +exiterr: + if (qh) nfq_destroy_queue(qh); + if (h) nfq_close(h); + cleanup_params(); + return 1; +} diff --git a/nfq/sec.c b/nfq/sec.c new file mode 100644 index 0000000..38612d2 --- /dev/null +++ b/nfq/sec.c @@ -0,0 +1,128 @@ +#include +#include +#include "sec.h" +#include +#include +#include + +bool setpcap(cap_value_t *caps, int ncaps) +{ + cap_t capabilities; + + if (!(capabilities = cap_init())) + return false; + + if (ncaps && (cap_set_flag(capabilities, CAP_PERMITTED, ncaps, caps, CAP_SET) || + cap_set_flag(capabilities, CAP_EFFECTIVE, ncaps, caps, CAP_SET))) + { + cap_free(capabilities); + return false; + } + if (cap_set_proc(capabilities)) + { + cap_free(capabilities); + return false; + } + cap_free(capabilities); + return true; +} +int getmaxcap() +{ + int maxcap = CAP_LAST_CAP; + FILE *F = fopen("/proc/sys/kernel/cap_last_cap", "r"); + if (F) + { + int n = fscanf(F, "%d", &maxcap); + fclose(F); + } + return maxcap; + +} +bool dropcaps() +{ + // must have CAP_SETPCAP at the end. its required to clear bounding set + cap_value_t cap_values[] = { CAP_NET_ADMIN,CAP_NET_RAW,CAP_SETPCAP }; + int capct = sizeof(cap_values) / sizeof(*cap_values); + int maxcap = getmaxcap(); + + if (setpcap(cap_values, capct)) + { + for (int cap = 0; cap <= maxcap; cap++) + { + if (cap_drop_bound(cap)) + { + fprintf(stderr, "could not drop cap %d\n", cap); + perror("cap_drop_bound"); + } + } + } + // now without CAP_SETPCAP + if (!setpcap(cap_values, capct - 1)) + { + perror("setpcap"); + return false; + } + return true; +} +bool droproot(uid_t uid, gid_t gid) +{ + if (uid || gid) + { + if (prctl(PR_SET_KEEPCAPS, 1L)) + { + perror("prctl(PR_SET_KEEPCAPS): "); + return false; + } + if (setgid(gid)) + { + perror("setgid: "); + return false; + } + if (setuid(uid)) + { + perror("setuid: "); + return false; + } + } + return dropcaps(); +} + +void daemonize() +{ + int pid; + + pid = fork(); + if (pid == -1) + { + perror("fork: "); + exit(2); + } + else if (pid != 0) + exit(0); + + if (setsid() == -1) + exit(2); + if (chdir("/") == -1) + exit(2); + close(STDIN_FILENO); + close(STDOUT_FILENO); + close(STDERR_FILENO); + /* redirect fd's 0,1,2 to /dev/null */ + open("/dev/null", O_RDWR); + int fd; + /* stdin */ + fd = dup(0); + /* stdout */ + fd = dup(0); + /* stderror */ +} + +bool writepid(const char *filename) +{ + FILE *F; + if (!(F = fopen(filename, "w"))) + return false; + fprintf(F, "%d", getpid()); + fclose(F); + return true; +} diff --git a/nfq/sec.h b/nfq/sec.h new file mode 100644 index 0000000..dce33cc --- /dev/null +++ b/nfq/sec.h @@ -0,0 +1,12 @@ +#pragma once + +#include +#include +#include + +bool setpcap(cap_value_t *caps, int ncaps); +int getmaxcap(); +bool dropcaps(); +bool droproot(uid_t uid, gid_t gid); +void daemonize(); +bool writepid(const char *filename); diff --git a/nfq/strpool.c b/nfq/strpool.c new file mode 100644 index 0000000..41e62a9 --- /dev/null +++ b/nfq/strpool.c @@ -0,0 +1,76 @@ +#define _GNU_SOURCE +#include "strpool.h" +#include +#include + +#undef uthash_nonfatal_oom +#define uthash_nonfatal_oom(elt) ut_oom_recover(elt) + +static bool oom=false; +static void ut_oom_recover(strpool *elem) +{ + oom=true; +} + +// for zero terminated strings +bool StrPoolAddStr(strpool **pp,const char *s) +{ + strpool *elem; + if (!(elem = (strpool*)malloc(sizeof(strpool)))) + return false; + if (!(elem->str = strdup(s))) + { + free(elem); + return false; + } + oom = false; + HASH_ADD_KEYPTR( hh, *pp, elem->str, strlen(elem->str), elem ); + if (oom) + { + free(elem->str); + free(elem); + return false; + } + return true; +} +// for not zero terminated strings +bool StrPoolAddStrLen(strpool **pp,const char *s,size_t slen) +{ + strpool *elem; + if (!(elem = (strpool*)malloc(sizeof(strpool)))) + return false; + if (!(elem->str = malloc(slen+1))) + { + free(elem); + return false; + } + memcpy(elem->str,s,slen); + elem->str[slen]=0; + oom = false; + HASH_ADD_KEYPTR( hh, *pp, elem->str, strlen(elem->str), elem ); + if (oom) + { + free(elem->str); + free(elem); + return false; + } + return true; +} + +bool StrPoolCheckStr(strpool *p,const char *s) +{ + strpool *elem; + HASH_FIND_STR( p, s, elem); + return elem!=NULL; +} + +void StrPoolDestroy(strpool **p) +{ + strpool *elem,*tmp; + HASH_ITER(hh, *p, elem, tmp) { + free(elem->str); + HASH_DEL(*p, elem); + free(elem); + } + *p = NULL; +} diff --git a/nfq/strpool.h b/nfq/strpool.h new file mode 100644 index 0000000..5932ba3 --- /dev/null +++ b/nfq/strpool.h @@ -0,0 +1,19 @@ +#pragma once + +#include +#include + +//#define HASH_BLOOM 20 +#define HASH_NONFATAL_OOM 1 +#define HASH_FUNCTION HASH_BER +#include "uthash.h" + +typedef struct strpool { + char *str; /* key */ + UT_hash_handle hh; /* makes this structure hashable */ +} strpool; + +void StrPoolDestroy(strpool **p); +bool StrPoolAddStr(strpool **pp,const char *s); +bool StrPoolAddStrLen(strpool **pp,const char *s,size_t slen); +bool StrPoolCheckStr(strpool *p,const char *s); diff --git a/nfq/uthash.h b/nfq/uthash.h new file mode 100644 index 0000000..f34c1f9 --- /dev/null +++ b/nfq/uthash.h @@ -0,0 +1,1217 @@ +/* +Copyright (c) 2003-2018, Troy D. Hanson http://troydhanson.github.com/uthash/ +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER +OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef UTHASH_H +#define UTHASH_H + +#define UTHASH_VERSION 2.0.2 + +#include /* memcmp, memset, strlen */ +#include /* ptrdiff_t */ +#include /* exit */ + +/* These macros use decltype or the earlier __typeof GNU extension. + As decltype is only available in newer compilers (VS2010 or gcc 4.3+ + when compiling c++ source) this code uses whatever method is needed + or, for VS2008 where neither is available, uses casting workarounds. */ +#if !defined(DECLTYPE) && !defined(NO_DECLTYPE) +#if defined(_MSC_VER) /* MS compiler */ +#if _MSC_VER >= 1600 && defined(__cplusplus) /* VS2010 or newer in C++ mode */ +#define DECLTYPE(x) (decltype(x)) +#else /* VS2008 or older (or VS2010 in C mode) */ +#define NO_DECLTYPE +#endif +#elif defined(__BORLANDC__) || defined(__ICCARM__) || defined(__LCC__) || defined(__WATCOMC__) +#define NO_DECLTYPE +#else /* GNU, Sun and other compilers */ +#define DECLTYPE(x) (__typeof(x)) +#endif +#endif + +#ifdef NO_DECLTYPE +#define DECLTYPE(x) +#define DECLTYPE_ASSIGN(dst,src) \ +do { \ + char **_da_dst = (char**)(&(dst)); \ + *_da_dst = (char*)(src); \ +} while (0) +#else +#define DECLTYPE_ASSIGN(dst,src) \ +do { \ + (dst) = DECLTYPE(dst)(src); \ +} while (0) +#endif + +/* a number of the hash function use uint32_t which isn't defined on Pre VS2010 */ +#if defined(_WIN32) +#if defined(_MSC_VER) && _MSC_VER >= 1600 +#include +#elif defined(__WATCOMC__) || defined(__MINGW32__) || defined(__CYGWIN__) +#include +#else +typedef unsigned int uint32_t; +typedef unsigned char uint8_t; +#endif +#elif defined(__GNUC__) && !defined(__VXWORKS__) +#include +#else +typedef unsigned int uint32_t; +typedef unsigned char uint8_t; +#endif + +#ifndef uthash_malloc +#define uthash_malloc(sz) malloc(sz) /* malloc fcn */ +#endif +#ifndef uthash_free +#define uthash_free(ptr,sz) free(ptr) /* free fcn */ +#endif +#ifndef uthash_bzero +#define uthash_bzero(a,n) memset(a,'\0',n) +#endif +#ifndef uthash_memcmp +#define uthash_memcmp(a,b,n) memcmp(a,b,n) +#endif +#ifndef uthash_strlen +#define uthash_strlen(s) strlen(s) +#endif + +#ifndef uthash_noexpand_fyi +#define uthash_noexpand_fyi(tbl) /* can be defined to log noexpand */ +#endif +#ifndef uthash_expand_fyi +#define uthash_expand_fyi(tbl) /* can be defined to log expands */ +#endif + +#ifndef HASH_NONFATAL_OOM +#define HASH_NONFATAL_OOM 0 +#endif + +#if HASH_NONFATAL_OOM +/* malloc failures can be recovered from */ + +#ifndef uthash_nonfatal_oom +#define uthash_nonfatal_oom(obj) do {} while (0) /* non-fatal OOM error */ +#endif + +#define HASH_RECORD_OOM(oomed) do { (oomed) = 1; } while (0) +#define IF_HASH_NONFATAL_OOM(x) x + +#else +/* malloc failures result in lost memory, hash tables are unusable */ + +#ifndef uthash_fatal +#define uthash_fatal(msg) exit(-1) /* fatal OOM error */ +#endif + +#define HASH_RECORD_OOM(oomed) uthash_fatal("out of memory") +#define IF_HASH_NONFATAL_OOM(x) + +#endif + +/* initial number of buckets */ +#define HASH_INITIAL_NUM_BUCKETS 32U /* initial number of buckets */ +#define HASH_INITIAL_NUM_BUCKETS_LOG2 5U /* lg2 of initial number of buckets */ +#define HASH_BKT_CAPACITY_THRESH 10U /* expand when bucket count reaches */ + +/* calculate the element whose hash handle address is hhp */ +#define ELMT_FROM_HH(tbl,hhp) ((void*)(((char*)(hhp)) - ((tbl)->hho))) +/* calculate the hash handle from element address elp */ +#define HH_FROM_ELMT(tbl,elp) ((UT_hash_handle *)(((char*)(elp)) + ((tbl)->hho))) + +#define HASH_ROLLBACK_BKT(hh, head, itemptrhh) \ +do { \ + struct UT_hash_handle *_hd_hh_item = (itemptrhh); \ + unsigned _hd_bkt; \ + HASH_TO_BKT(_hd_hh_item->hashv, (head)->hh.tbl->num_buckets, _hd_bkt); \ + (head)->hh.tbl->buckets[_hd_bkt].count++; \ + _hd_hh_item->hh_next = NULL; \ + _hd_hh_item->hh_prev = NULL; \ +} while (0) + +#define HASH_VALUE(keyptr,keylen,hashv) \ +do { \ + HASH_FCN(keyptr, keylen, hashv); \ +} while (0) + +#define HASH_FIND_BYHASHVALUE(hh,head,keyptr,keylen,hashval,out) \ +do { \ + (out) = NULL; \ + if (head) { \ + unsigned _hf_bkt; \ + HASH_TO_BKT(hashval, (head)->hh.tbl->num_buckets, _hf_bkt); \ + if (HASH_BLOOM_TEST((head)->hh.tbl, hashval) != 0) { \ + HASH_FIND_IN_BKT((head)->hh.tbl, hh, (head)->hh.tbl->buckets[ _hf_bkt ], keyptr, keylen, hashval, out); \ + } \ + } \ +} while (0) + +#define HASH_FIND(hh,head,keyptr,keylen,out) \ +do { \ + unsigned _hf_hashv; \ + HASH_VALUE(keyptr, keylen, _hf_hashv); \ + HASH_FIND_BYHASHVALUE(hh, head, keyptr, keylen, _hf_hashv, out); \ +} while (0) + +#ifdef HASH_BLOOM +#define HASH_BLOOM_BITLEN (1UL << HASH_BLOOM) +#define HASH_BLOOM_BYTELEN (HASH_BLOOM_BITLEN/8UL) + (((HASH_BLOOM_BITLEN%8UL)!=0UL) ? 1UL : 0UL) +#define HASH_BLOOM_MAKE(tbl,oomed) \ +do { \ + (tbl)->bloom_nbits = HASH_BLOOM; \ + (tbl)->bloom_bv = (uint8_t*)uthash_malloc(HASH_BLOOM_BYTELEN); \ + if (!(tbl)->bloom_bv) { \ + HASH_RECORD_OOM(oomed); \ + } else { \ + uthash_bzero((tbl)->bloom_bv, HASH_BLOOM_BYTELEN); \ + (tbl)->bloom_sig = HASH_BLOOM_SIGNATURE; \ + } \ +} while (0) + +#define HASH_BLOOM_FREE(tbl) \ +do { \ + uthash_free((tbl)->bloom_bv, HASH_BLOOM_BYTELEN); \ +} while (0) + +#define HASH_BLOOM_BITSET(bv,idx) (bv[(idx)/8U] |= (1U << ((idx)%8U))) +#define HASH_BLOOM_BITTEST(bv,idx) (bv[(idx)/8U] & (1U << ((idx)%8U))) + +#define HASH_BLOOM_ADD(tbl,hashv) \ + HASH_BLOOM_BITSET((tbl)->bloom_bv, ((hashv) & (uint32_t)((1UL << (tbl)->bloom_nbits) - 1U))) + +#define HASH_BLOOM_TEST(tbl,hashv) \ + HASH_BLOOM_BITTEST((tbl)->bloom_bv, ((hashv) & (uint32_t)((1UL << (tbl)->bloom_nbits) - 1U))) + +#else +#define HASH_BLOOM_MAKE(tbl,oomed) +#define HASH_BLOOM_FREE(tbl) +#define HASH_BLOOM_ADD(tbl,hashv) +#define HASH_BLOOM_TEST(tbl,hashv) (1) +#define HASH_BLOOM_BYTELEN 0U +#endif + +#define HASH_MAKE_TABLE(hh,head,oomed) \ +do { \ + (head)->hh.tbl = (UT_hash_table*)uthash_malloc(sizeof(UT_hash_table)); \ + if (!(head)->hh.tbl) { \ + HASH_RECORD_OOM(oomed); \ + } else { \ + uthash_bzero((head)->hh.tbl, sizeof(UT_hash_table)); \ + (head)->hh.tbl->tail = &((head)->hh); \ + (head)->hh.tbl->num_buckets = HASH_INITIAL_NUM_BUCKETS; \ + (head)->hh.tbl->log2_num_buckets = HASH_INITIAL_NUM_BUCKETS_LOG2; \ + (head)->hh.tbl->hho = (char*)(&(head)->hh) - (char*)(head); \ + (head)->hh.tbl->buckets = (UT_hash_bucket*)uthash_malloc( \ + HASH_INITIAL_NUM_BUCKETS * sizeof(struct UT_hash_bucket)); \ + (head)->hh.tbl->signature = HASH_SIGNATURE; \ + if (!(head)->hh.tbl->buckets) { \ + HASH_RECORD_OOM(oomed); \ + uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \ + } else { \ + uthash_bzero((head)->hh.tbl->buckets, \ + HASH_INITIAL_NUM_BUCKETS * sizeof(struct UT_hash_bucket)); \ + HASH_BLOOM_MAKE((head)->hh.tbl, oomed); \ + IF_HASH_NONFATAL_OOM( \ + if (oomed) { \ + uthash_free((head)->hh.tbl->buckets, \ + HASH_INITIAL_NUM_BUCKETS*sizeof(struct UT_hash_bucket)); \ + uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \ + } \ + ) \ + } \ + } \ +} while (0) + +#define HASH_REPLACE_BYHASHVALUE_INORDER(hh,head,fieldname,keylen_in,hashval,add,replaced,cmpfcn) \ +do { \ + (replaced) = NULL; \ + HASH_FIND_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, replaced); \ + if (replaced) { \ + HASH_DELETE(hh, head, replaced); \ + } \ + HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh, head, &((add)->fieldname), keylen_in, hashval, add, cmpfcn); \ +} while (0) + +#define HASH_REPLACE_BYHASHVALUE(hh,head,fieldname,keylen_in,hashval,add,replaced) \ +do { \ + (replaced) = NULL; \ + HASH_FIND_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, replaced); \ + if (replaced) { \ + HASH_DELETE(hh, head, replaced); \ + } \ + HASH_ADD_KEYPTR_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, add); \ +} while (0) + +#define HASH_REPLACE(hh,head,fieldname,keylen_in,add,replaced) \ +do { \ + unsigned _hr_hashv; \ + HASH_VALUE(&((add)->fieldname), keylen_in, _hr_hashv); \ + HASH_REPLACE_BYHASHVALUE(hh, head, fieldname, keylen_in, _hr_hashv, add, replaced); \ +} while (0) + +#define HASH_REPLACE_INORDER(hh,head,fieldname,keylen_in,add,replaced,cmpfcn) \ +do { \ + unsigned _hr_hashv; \ + HASH_VALUE(&((add)->fieldname), keylen_in, _hr_hashv); \ + HASH_REPLACE_BYHASHVALUE_INORDER(hh, head, fieldname, keylen_in, _hr_hashv, add, replaced, cmpfcn); \ +} while (0) + +#define HASH_APPEND_LIST(hh, head, add) \ +do { \ + (add)->hh.next = NULL; \ + (add)->hh.prev = ELMT_FROM_HH((head)->hh.tbl, (head)->hh.tbl->tail); \ + (head)->hh.tbl->tail->next = (add); \ + (head)->hh.tbl->tail = &((add)->hh); \ +} while (0) + +#define HASH_AKBI_INNER_LOOP(hh,head,add,cmpfcn) \ +do { \ + do { \ + if (cmpfcn(DECLTYPE(head)(_hs_iter), add) > 0) { \ + break; \ + } \ + } while ((_hs_iter = HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->next)); \ +} while (0) + +#ifdef NO_DECLTYPE +#undef HASH_AKBI_INNER_LOOP +#define HASH_AKBI_INNER_LOOP(hh,head,add,cmpfcn) \ +do { \ + char *_hs_saved_head = (char*)(head); \ + do { \ + DECLTYPE_ASSIGN(head, _hs_iter); \ + if (cmpfcn(head, add) > 0) { \ + DECLTYPE_ASSIGN(head, _hs_saved_head); \ + break; \ + } \ + DECLTYPE_ASSIGN(head, _hs_saved_head); \ + } while ((_hs_iter = HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->next)); \ +} while (0) +#endif + +#if HASH_NONFATAL_OOM + +#define HASH_ADD_TO_TABLE(hh,head,keyptr,keylen_in,hashval,add,oomed) \ +do { \ + if (!(oomed)) { \ + unsigned _ha_bkt; \ + (head)->hh.tbl->num_items++; \ + HASH_TO_BKT(hashval, (head)->hh.tbl->num_buckets, _ha_bkt); \ + HASH_ADD_TO_BKT((head)->hh.tbl->buckets[_ha_bkt], hh, &(add)->hh, oomed); \ + if (oomed) { \ + HASH_ROLLBACK_BKT(hh, head, &(add)->hh); \ + HASH_DELETE_HH(hh, head, &(add)->hh); \ + (add)->hh.tbl = NULL; \ + uthash_nonfatal_oom(add); \ + } else { \ + HASH_BLOOM_ADD((head)->hh.tbl, hashval); \ + HASH_EMIT_KEY(hh, head, keyptr, keylen_in); \ + } \ + } else { \ + (add)->hh.tbl = NULL; \ + uthash_nonfatal_oom(add); \ + } \ +} while (0) + +#else + +#define HASH_ADD_TO_TABLE(hh,head,keyptr,keylen_in,hashval,add,oomed) \ +do { \ + unsigned _ha_bkt; \ + (head)->hh.tbl->num_items++; \ + HASH_TO_BKT(hashval, (head)->hh.tbl->num_buckets, _ha_bkt); \ + HASH_ADD_TO_BKT((head)->hh.tbl->buckets[_ha_bkt], hh, &(add)->hh, oomed); \ + HASH_BLOOM_ADD((head)->hh.tbl, hashval); \ + HASH_EMIT_KEY(hh, head, keyptr, keylen_in); \ +} while (0) + +#endif + + +#define HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh,head,keyptr,keylen_in,hashval,add,cmpfcn) \ +do { \ + IF_HASH_NONFATAL_OOM( int _ha_oomed = 0; ) \ + (add)->hh.hashv = (hashval); \ + (add)->hh.key = (char*) (keyptr); \ + (add)->hh.keylen = (unsigned) (keylen_in); \ + if (!(head)) { \ + (add)->hh.next = NULL; \ + (add)->hh.prev = NULL; \ + HASH_MAKE_TABLE(hh, add, _ha_oomed); \ + IF_HASH_NONFATAL_OOM( if (!_ha_oomed) { ) \ + (head) = (add); \ + IF_HASH_NONFATAL_OOM( } ) \ + } else { \ + void *_hs_iter = (head); \ + (add)->hh.tbl = (head)->hh.tbl; \ + HASH_AKBI_INNER_LOOP(hh, head, add, cmpfcn); \ + if (_hs_iter) { \ + (add)->hh.next = _hs_iter; \ + if (((add)->hh.prev = HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->prev)) { \ + HH_FROM_ELMT((head)->hh.tbl, (add)->hh.prev)->next = (add); \ + } else { \ + (head) = (add); \ + } \ + HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->prev = (add); \ + } else { \ + HASH_APPEND_LIST(hh, head, add); \ + } \ + } \ + HASH_ADD_TO_TABLE(hh, head, keyptr, keylen_in, hashval, add, _ha_oomed); \ + HASH_FSCK(hh, head, "HASH_ADD_KEYPTR_BYHASHVALUE_INORDER"); \ +} while (0) + +#define HASH_ADD_KEYPTR_INORDER(hh,head,keyptr,keylen_in,add,cmpfcn) \ +do { \ + unsigned _hs_hashv; \ + HASH_VALUE(keyptr, keylen_in, _hs_hashv); \ + HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh, head, keyptr, keylen_in, _hs_hashv, add, cmpfcn); \ +} while (0) + +#define HASH_ADD_BYHASHVALUE_INORDER(hh,head,fieldname,keylen_in,hashval,add,cmpfcn) \ + HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh, head, &((add)->fieldname), keylen_in, hashval, add, cmpfcn) + +#define HASH_ADD_INORDER(hh,head,fieldname,keylen_in,add,cmpfcn) \ + HASH_ADD_KEYPTR_INORDER(hh, head, &((add)->fieldname), keylen_in, add, cmpfcn) + +#define HASH_ADD_KEYPTR_BYHASHVALUE(hh,head,keyptr,keylen_in,hashval,add) \ +do { \ + IF_HASH_NONFATAL_OOM( int _ha_oomed = 0; ) \ + (add)->hh.hashv = (hashval); \ + (add)->hh.key = (char*) (keyptr); \ + (add)->hh.keylen = (unsigned) (keylen_in); \ + if (!(head)) { \ + (add)->hh.next = NULL; \ + (add)->hh.prev = NULL; \ + HASH_MAKE_TABLE(hh, add, _ha_oomed); \ + IF_HASH_NONFATAL_OOM( if (!_ha_oomed) { ) \ + (head) = (add); \ + IF_HASH_NONFATAL_OOM( } ) \ + } else { \ + (add)->hh.tbl = (head)->hh.tbl; \ + HASH_APPEND_LIST(hh, head, add); \ + } \ + HASH_ADD_TO_TABLE(hh, head, keyptr, keylen_in, hashval, add, _ha_oomed); \ + HASH_FSCK(hh, head, "HASH_ADD_KEYPTR_BYHASHVALUE"); \ +} while (0) + +#define HASH_ADD_KEYPTR(hh,head,keyptr,keylen_in,add) \ +do { \ + unsigned _ha_hashv; \ + HASH_VALUE(keyptr, keylen_in, _ha_hashv); \ + HASH_ADD_KEYPTR_BYHASHVALUE(hh, head, keyptr, keylen_in, _ha_hashv, add); \ +} while (0) + +#define HASH_ADD_BYHASHVALUE(hh,head,fieldname,keylen_in,hashval,add) \ + HASH_ADD_KEYPTR_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, add) + +#define HASH_ADD(hh,head,fieldname,keylen_in,add) \ + HASH_ADD_KEYPTR(hh, head, &((add)->fieldname), keylen_in, add) + +#define HASH_TO_BKT(hashv,num_bkts,bkt) \ +do { \ + bkt = ((hashv) & ((num_bkts) - 1U)); \ +} while (0) + +/* delete "delptr" from the hash table. + * "the usual" patch-up process for the app-order doubly-linked-list. + * The use of _hd_hh_del below deserves special explanation. + * These used to be expressed using (delptr) but that led to a bug + * if someone used the same symbol for the head and deletee, like + * HASH_DELETE(hh,users,users); + * We want that to work, but by changing the head (users) below + * we were forfeiting our ability to further refer to the deletee (users) + * in the patch-up process. Solution: use scratch space to + * copy the deletee pointer, then the latter references are via that + * scratch pointer rather than through the repointed (users) symbol. + */ +#define HASH_DELETE(hh,head,delptr) \ + HASH_DELETE_HH(hh, head, &(delptr)->hh) + +#define HASH_DELETE_HH(hh,head,delptrhh) \ +do { \ + struct UT_hash_handle *_hd_hh_del = (delptrhh); \ + if ((_hd_hh_del->prev == NULL) && (_hd_hh_del->next == NULL)) { \ + HASH_BLOOM_FREE((head)->hh.tbl); \ + uthash_free((head)->hh.tbl->buckets, \ + (head)->hh.tbl->num_buckets * sizeof(struct UT_hash_bucket)); \ + uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \ + (head) = NULL; \ + } else { \ + unsigned _hd_bkt; \ + if (_hd_hh_del == (head)->hh.tbl->tail) { \ + (head)->hh.tbl->tail = HH_FROM_ELMT((head)->hh.tbl, _hd_hh_del->prev); \ + } \ + if (_hd_hh_del->prev != NULL) { \ + HH_FROM_ELMT((head)->hh.tbl, _hd_hh_del->prev)->next = _hd_hh_del->next; \ + } else { \ + DECLTYPE_ASSIGN(head, _hd_hh_del->next); \ + } \ + if (_hd_hh_del->next != NULL) { \ + HH_FROM_ELMT((head)->hh.tbl, _hd_hh_del->next)->prev = _hd_hh_del->prev; \ + } \ + HASH_TO_BKT(_hd_hh_del->hashv, (head)->hh.tbl->num_buckets, _hd_bkt); \ + HASH_DEL_IN_BKT((head)->hh.tbl->buckets[_hd_bkt], _hd_hh_del); \ + (head)->hh.tbl->num_items--; \ + } \ + HASH_FSCK(hh, head, "HASH_DELETE_HH"); \ +} while (0) + +/* convenience forms of HASH_FIND/HASH_ADD/HASH_DEL */ +#define HASH_FIND_STR(head,findstr,out) \ +do { \ + unsigned _uthash_hfstr_keylen = (unsigned)uthash_strlen(findstr); \ + HASH_FIND(hh, head, findstr, _uthash_hfstr_keylen, out); \ +} while (0) +#define HASH_ADD_STR(head,strfield,add) \ +do { \ + unsigned _uthash_hastr_keylen = (unsigned)uthash_strlen((add)->strfield); \ + HASH_ADD(hh, head, strfield[0], _uthash_hastr_keylen, add); \ +} while (0) +#define HASH_REPLACE_STR(head,strfield,add,replaced) \ +do { \ + unsigned _uthash_hrstr_keylen = (unsigned)uthash_strlen((add)->strfield); \ + HASH_REPLACE(hh, head, strfield[0], _uthash_hrstr_keylen, add, replaced); \ +} while (0) +#define HASH_FIND_INT(head,findint,out) \ + HASH_FIND(hh,head,findint,sizeof(int),out) +#define HASH_ADD_INT(head,intfield,add) \ + HASH_ADD(hh,head,intfield,sizeof(int),add) +#define HASH_REPLACE_INT(head,intfield,add,replaced) \ + HASH_REPLACE(hh,head,intfield,sizeof(int),add,replaced) +#define HASH_FIND_PTR(head,findptr,out) \ + HASH_FIND(hh,head,findptr,sizeof(void *),out) +#define HASH_ADD_PTR(head,ptrfield,add) \ + HASH_ADD(hh,head,ptrfield,sizeof(void *),add) +#define HASH_REPLACE_PTR(head,ptrfield,add,replaced) \ + HASH_REPLACE(hh,head,ptrfield,sizeof(void *),add,replaced) +#define HASH_DEL(head,delptr) \ + HASH_DELETE(hh,head,delptr) + +/* HASH_FSCK checks hash integrity on every add/delete when HASH_DEBUG is defined. + * This is for uthash developer only; it compiles away if HASH_DEBUG isn't defined. + */ +#ifdef HASH_DEBUG +#define HASH_OOPS(...) do { fprintf(stderr,__VA_ARGS__); exit(-1); } while (0) +#define HASH_FSCK(hh,head,where) \ +do { \ + struct UT_hash_handle *_thh; \ + if (head) { \ + unsigned _bkt_i; \ + unsigned _count = 0; \ + char *_prev; \ + for (_bkt_i = 0; _bkt_i < (head)->hh.tbl->num_buckets; ++_bkt_i) { \ + unsigned _bkt_count = 0; \ + _thh = (head)->hh.tbl->buckets[_bkt_i].hh_head; \ + _prev = NULL; \ + while (_thh) { \ + if (_prev != (char*)(_thh->hh_prev)) { \ + HASH_OOPS("%s: invalid hh_prev %p, actual %p\n", \ + (where), (void*)_thh->hh_prev, (void*)_prev); \ + } \ + _bkt_count++; \ + _prev = (char*)(_thh); \ + _thh = _thh->hh_next; \ + } \ + _count += _bkt_count; \ + if ((head)->hh.tbl->buckets[_bkt_i].count != _bkt_count) { \ + HASH_OOPS("%s: invalid bucket count %u, actual %u\n", \ + (where), (head)->hh.tbl->buckets[_bkt_i].count, _bkt_count); \ + } \ + } \ + if (_count != (head)->hh.tbl->num_items) { \ + HASH_OOPS("%s: invalid hh item count %u, actual %u\n", \ + (where), (head)->hh.tbl->num_items, _count); \ + } \ + _count = 0; \ + _prev = NULL; \ + _thh = &(head)->hh; \ + while (_thh) { \ + _count++; \ + if (_prev != (char*)_thh->prev) { \ + HASH_OOPS("%s: invalid prev %p, actual %p\n", \ + (where), (void*)_thh->prev, (void*)_prev); \ + } \ + _prev = (char*)ELMT_FROM_HH((head)->hh.tbl, _thh); \ + _thh = (_thh->next ? HH_FROM_ELMT((head)->hh.tbl, _thh->next) : NULL); \ + } \ + if (_count != (head)->hh.tbl->num_items) { \ + HASH_OOPS("%s: invalid app item count %u, actual %u\n", \ + (where), (head)->hh.tbl->num_items, _count); \ + } \ + } \ +} while (0) +#else +#define HASH_FSCK(hh,head,where) +#endif + +/* When compiled with -DHASH_EMIT_KEYS, length-prefixed keys are emitted to + * the descriptor to which this macro is defined for tuning the hash function. + * The app can #include to get the prototype for write(2). */ +#ifdef HASH_EMIT_KEYS +#define HASH_EMIT_KEY(hh,head,keyptr,fieldlen) \ +do { \ + unsigned _klen = fieldlen; \ + write(HASH_EMIT_KEYS, &_klen, sizeof(_klen)); \ + write(HASH_EMIT_KEYS, keyptr, (unsigned long)fieldlen); \ +} while (0) +#else +#define HASH_EMIT_KEY(hh,head,keyptr,fieldlen) +#endif + +/* default to Jenkin's hash unless overridden e.g. DHASH_FUNCTION=HASH_SAX */ +#ifdef HASH_FUNCTION +#define HASH_FCN HASH_FUNCTION +#else +#define HASH_FCN HASH_JEN +#endif + +/* The Bernstein hash function, used in Perl prior to v5.6. Note (x<<5+x)=x*33. */ +#define HASH_BER(key,keylen,hashv) \ +do { \ + unsigned _hb_keylen = (unsigned)keylen; \ + const unsigned char *_hb_key = (const unsigned char*)(key); \ + (hashv) = 0; \ + while (_hb_keylen-- != 0U) { \ + (hashv) = (((hashv) << 5) + (hashv)) + *_hb_key++; \ + } \ +} while (0) + + +/* SAX/FNV/OAT/JEN hash functions are macro variants of those listed at + * http://eternallyconfuzzled.com/tuts/algorithms/jsw_tut_hashing.aspx */ +#define HASH_SAX(key,keylen,hashv) \ +do { \ + unsigned _sx_i; \ + const unsigned char *_hs_key = (const unsigned char*)(key); \ + hashv = 0; \ + for (_sx_i=0; _sx_i < keylen; _sx_i++) { \ + hashv ^= (hashv << 5) + (hashv >> 2) + _hs_key[_sx_i]; \ + } \ +} while (0) +/* FNV-1a variation */ +#define HASH_FNV(key,keylen,hashv) \ +do { \ + unsigned _fn_i; \ + const unsigned char *_hf_key = (const unsigned char*)(key); \ + (hashv) = 2166136261U; \ + for (_fn_i=0; _fn_i < keylen; _fn_i++) { \ + hashv = hashv ^ _hf_key[_fn_i]; \ + hashv = hashv * 16777619U; \ + } \ +} while (0) + +#define HASH_OAT(key,keylen,hashv) \ +do { \ + unsigned _ho_i; \ + const unsigned char *_ho_key=(const unsigned char*)(key); \ + hashv = 0; \ + for(_ho_i=0; _ho_i < keylen; _ho_i++) { \ + hashv += _ho_key[_ho_i]; \ + hashv += (hashv << 10); \ + hashv ^= (hashv >> 6); \ + } \ + hashv += (hashv << 3); \ + hashv ^= (hashv >> 11); \ + hashv += (hashv << 15); \ +} while (0) + +#define HASH_JEN_MIX(a,b,c) \ +do { \ + a -= b; a -= c; a ^= ( c >> 13 ); \ + b -= c; b -= a; b ^= ( a << 8 ); \ + c -= a; c -= b; c ^= ( b >> 13 ); \ + a -= b; a -= c; a ^= ( c >> 12 ); \ + b -= c; b -= a; b ^= ( a << 16 ); \ + c -= a; c -= b; c ^= ( b >> 5 ); \ + a -= b; a -= c; a ^= ( c >> 3 ); \ + b -= c; b -= a; b ^= ( a << 10 ); \ + c -= a; c -= b; c ^= ( b >> 15 ); \ +} while (0) + +#define HASH_JEN(key,keylen,hashv) \ +do { \ + unsigned _hj_i,_hj_j,_hj_k; \ + unsigned const char *_hj_key=(unsigned const char*)(key); \ + hashv = 0xfeedbeefu; \ + _hj_i = _hj_j = 0x9e3779b9u; \ + _hj_k = (unsigned)(keylen); \ + while (_hj_k >= 12U) { \ + _hj_i += (_hj_key[0] + ( (unsigned)_hj_key[1] << 8 ) \ + + ( (unsigned)_hj_key[2] << 16 ) \ + + ( (unsigned)_hj_key[3] << 24 ) ); \ + _hj_j += (_hj_key[4] + ( (unsigned)_hj_key[5] << 8 ) \ + + ( (unsigned)_hj_key[6] << 16 ) \ + + ( (unsigned)_hj_key[7] << 24 ) ); \ + hashv += (_hj_key[8] + ( (unsigned)_hj_key[9] << 8 ) \ + + ( (unsigned)_hj_key[10] << 16 ) \ + + ( (unsigned)_hj_key[11] << 24 ) ); \ + \ + HASH_JEN_MIX(_hj_i, _hj_j, hashv); \ + \ + _hj_key += 12; \ + _hj_k -= 12U; \ + } \ + hashv += (unsigned)(keylen); \ + switch ( _hj_k ) { \ + case 11: hashv += ( (unsigned)_hj_key[10] << 24 ); /* FALLTHROUGH */ \ + case 10: hashv += ( (unsigned)_hj_key[9] << 16 ); /* FALLTHROUGH */ \ + case 9: hashv += ( (unsigned)_hj_key[8] << 8 ); /* FALLTHROUGH */ \ + case 8: _hj_j += ( (unsigned)_hj_key[7] << 24 ); /* FALLTHROUGH */ \ + case 7: _hj_j += ( (unsigned)_hj_key[6] << 16 ); /* FALLTHROUGH */ \ + case 6: _hj_j += ( (unsigned)_hj_key[5] << 8 ); /* FALLTHROUGH */ \ + case 5: _hj_j += _hj_key[4]; /* FALLTHROUGH */ \ + case 4: _hj_i += ( (unsigned)_hj_key[3] << 24 ); /* FALLTHROUGH */ \ + case 3: _hj_i += ( (unsigned)_hj_key[2] << 16 ); /* FALLTHROUGH */ \ + case 2: _hj_i += ( (unsigned)_hj_key[1] << 8 ); /* FALLTHROUGH */ \ + case 1: _hj_i += _hj_key[0]; \ + } \ + HASH_JEN_MIX(_hj_i, _hj_j, hashv); \ +} while (0) + +/* The Paul Hsieh hash function */ +#undef get16bits +#if (defined(__GNUC__) && defined(__i386__)) || defined(__WATCOMC__) \ + || defined(_MSC_VER) || defined (__BORLANDC__) || defined (__TURBOC__) +#define get16bits(d) (*((const uint16_t *) (d))) +#endif + +#if !defined (get16bits) +#define get16bits(d) ((((uint32_t)(((const uint8_t *)(d))[1])) << 8) \ + +(uint32_t)(((const uint8_t *)(d))[0]) ) +#endif +#define HASH_SFH(key,keylen,hashv) \ +do { \ + unsigned const char *_sfh_key=(unsigned const char*)(key); \ + uint32_t _sfh_tmp, _sfh_len = (uint32_t)keylen; \ + \ + unsigned _sfh_rem = _sfh_len & 3U; \ + _sfh_len >>= 2; \ + hashv = 0xcafebabeu; \ + \ + /* Main loop */ \ + for (;_sfh_len > 0U; _sfh_len--) { \ + hashv += get16bits (_sfh_key); \ + _sfh_tmp = ((uint32_t)(get16bits (_sfh_key+2)) << 11) ^ hashv; \ + hashv = (hashv << 16) ^ _sfh_tmp; \ + _sfh_key += 2U*sizeof (uint16_t); \ + hashv += hashv >> 11; \ + } \ + \ + /* Handle end cases */ \ + switch (_sfh_rem) { \ + case 3: hashv += get16bits (_sfh_key); \ + hashv ^= hashv << 16; \ + hashv ^= (uint32_t)(_sfh_key[sizeof (uint16_t)]) << 18; \ + hashv += hashv >> 11; \ + break; \ + case 2: hashv += get16bits (_sfh_key); \ + hashv ^= hashv << 11; \ + hashv += hashv >> 17; \ + break; \ + case 1: hashv += *_sfh_key; \ + hashv ^= hashv << 10; \ + hashv += hashv >> 1; \ + } \ + \ + /* Force "avalanching" of final 127 bits */ \ + hashv ^= hashv << 3; \ + hashv += hashv >> 5; \ + hashv ^= hashv << 4; \ + hashv += hashv >> 17; \ + hashv ^= hashv << 25; \ + hashv += hashv >> 6; \ +} while (0) + +#ifdef HASH_USING_NO_STRICT_ALIASING +/* The MurmurHash exploits some CPU's (x86,x86_64) tolerance for unaligned reads. + * For other types of CPU's (e.g. Sparc) an unaligned read causes a bus error. + * MurmurHash uses the faster approach only on CPU's where we know it's safe. + * + * Note the preprocessor built-in defines can be emitted using: + * + * gcc -m64 -dM -E - < /dev/null (on gcc) + * cc -## a.c (where a.c is a simple test file) (Sun Studio) + */ +#if (defined(__i386__) || defined(__x86_64__) || defined(_M_IX86)) +#define MUR_GETBLOCK(p,i) p[i] +#else /* non intel */ +#define MUR_PLUS0_ALIGNED(p) (((unsigned long)p & 3UL) == 0UL) +#define MUR_PLUS1_ALIGNED(p) (((unsigned long)p & 3UL) == 1UL) +#define MUR_PLUS2_ALIGNED(p) (((unsigned long)p & 3UL) == 2UL) +#define MUR_PLUS3_ALIGNED(p) (((unsigned long)p & 3UL) == 3UL) +#define WP(p) ((uint32_t*)((unsigned long)(p) & ~3UL)) +#if (defined(__BIG_ENDIAN__) || defined(SPARC) || defined(__ppc__) || defined(__ppc64__)) +#define MUR_THREE_ONE(p) ((((*WP(p))&0x00ffffff) << 8) | (((*(WP(p)+1))&0xff000000) >> 24)) +#define MUR_TWO_TWO(p) ((((*WP(p))&0x0000ffff) <<16) | (((*(WP(p)+1))&0xffff0000) >> 16)) +#define MUR_ONE_THREE(p) ((((*WP(p))&0x000000ff) <<24) | (((*(WP(p)+1))&0xffffff00) >> 8)) +#else /* assume little endian non-intel */ +#define MUR_THREE_ONE(p) ((((*WP(p))&0xffffff00) >> 8) | (((*(WP(p)+1))&0x000000ff) << 24)) +#define MUR_TWO_TWO(p) ((((*WP(p))&0xffff0000) >>16) | (((*(WP(p)+1))&0x0000ffff) << 16)) +#define MUR_ONE_THREE(p) ((((*WP(p))&0xff000000) >>24) | (((*(WP(p)+1))&0x00ffffff) << 8)) +#endif +#define MUR_GETBLOCK(p,i) (MUR_PLUS0_ALIGNED(p) ? ((p)[i]) : \ + (MUR_PLUS1_ALIGNED(p) ? MUR_THREE_ONE(p) : \ + (MUR_PLUS2_ALIGNED(p) ? MUR_TWO_TWO(p) : \ + MUR_ONE_THREE(p)))) +#endif +#define MUR_ROTL32(x,r) (((x) << (r)) | ((x) >> (32 - (r)))) +#define MUR_FMIX(_h) \ +do { \ + _h ^= _h >> 16; \ + _h *= 0x85ebca6bu; \ + _h ^= _h >> 13; \ + _h *= 0xc2b2ae35u; \ + _h ^= _h >> 16; \ +} while (0) + +#define HASH_MUR(key,keylen,hashv) \ +do { \ + const uint8_t *_mur_data = (const uint8_t*)(key); \ + const int _mur_nblocks = (int)(keylen) / 4; \ + uint32_t _mur_h1 = 0xf88D5353u; \ + uint32_t _mur_c1 = 0xcc9e2d51u; \ + uint32_t _mur_c2 = 0x1b873593u; \ + uint32_t _mur_k1 = 0; \ + const uint8_t *_mur_tail; \ + const uint32_t *_mur_blocks = (const uint32_t*)(_mur_data+(_mur_nblocks*4)); \ + int _mur_i; \ + for (_mur_i = -_mur_nblocks; _mur_i != 0; _mur_i++) { \ + _mur_k1 = MUR_GETBLOCK(_mur_blocks,_mur_i); \ + _mur_k1 *= _mur_c1; \ + _mur_k1 = MUR_ROTL32(_mur_k1,15); \ + _mur_k1 *= _mur_c2; \ + \ + _mur_h1 ^= _mur_k1; \ + _mur_h1 = MUR_ROTL32(_mur_h1,13); \ + _mur_h1 = (_mur_h1*5U) + 0xe6546b64u; \ + } \ + _mur_tail = (const uint8_t*)(_mur_data + (_mur_nblocks*4)); \ + _mur_k1=0; \ + switch ((keylen) & 3U) { \ + case 0: break; \ + case 3: _mur_k1 ^= (uint32_t)_mur_tail[2] << 16; /* FALLTHROUGH */ \ + case 2: _mur_k1 ^= (uint32_t)_mur_tail[1] << 8; /* FALLTHROUGH */ \ + case 1: _mur_k1 ^= (uint32_t)_mur_tail[0]; \ + _mur_k1 *= _mur_c1; \ + _mur_k1 = MUR_ROTL32(_mur_k1,15); \ + _mur_k1 *= _mur_c2; \ + _mur_h1 ^= _mur_k1; \ + } \ + _mur_h1 ^= (uint32_t)(keylen); \ + MUR_FMIX(_mur_h1); \ + hashv = _mur_h1; \ +} while (0) +#endif /* HASH_USING_NO_STRICT_ALIASING */ + +/* iterate over items in a known bucket to find desired item */ +#define HASH_FIND_IN_BKT(tbl,hh,head,keyptr,keylen_in,hashval,out) \ +do { \ + if ((head).hh_head != NULL) { \ + DECLTYPE_ASSIGN(out, ELMT_FROM_HH(tbl, (head).hh_head)); \ + } else { \ + (out) = NULL; \ + } \ + while ((out) != NULL) { \ + if ((out)->hh.hashv == (hashval) && (out)->hh.keylen == (keylen_in)) { \ + if (uthash_memcmp((out)->hh.key, keyptr, keylen_in) == 0) { \ + break; \ + } \ + } \ + if ((out)->hh.hh_next != NULL) { \ + DECLTYPE_ASSIGN(out, ELMT_FROM_HH(tbl, (out)->hh.hh_next)); \ + } else { \ + (out) = NULL; \ + } \ + } \ +} while (0) + +/* add an item to a bucket */ +#define HASH_ADD_TO_BKT(head,hh,addhh,oomed) \ +do { \ + UT_hash_bucket *_ha_head = &(head); \ + _ha_head->count++; \ + (addhh)->hh_next = _ha_head->hh_head; \ + (addhh)->hh_prev = NULL; \ + if (_ha_head->hh_head != NULL) { \ + _ha_head->hh_head->hh_prev = (addhh); \ + } \ + _ha_head->hh_head = (addhh); \ + if ((_ha_head->count >= ((_ha_head->expand_mult + 1U) * HASH_BKT_CAPACITY_THRESH)) \ + && !(addhh)->tbl->noexpand) { \ + HASH_EXPAND_BUCKETS(addhh,(addhh)->tbl, oomed); \ + IF_HASH_NONFATAL_OOM( \ + if (oomed) { \ + HASH_DEL_IN_BKT(head,addhh); \ + } \ + ) \ + } \ +} while (0) + +/* remove an item from a given bucket */ +#define HASH_DEL_IN_BKT(head,delhh) \ +do { \ + UT_hash_bucket *_hd_head = &(head); \ + _hd_head->count--; \ + if (_hd_head->hh_head == (delhh)) { \ + _hd_head->hh_head = (delhh)->hh_next; \ + } \ + if ((delhh)->hh_prev) { \ + (delhh)->hh_prev->hh_next = (delhh)->hh_next; \ + } \ + if ((delhh)->hh_next) { \ + (delhh)->hh_next->hh_prev = (delhh)->hh_prev; \ + } \ +} while (0) + +/* Bucket expansion has the effect of doubling the number of buckets + * and redistributing the items into the new buckets. Ideally the + * items will distribute more or less evenly into the new buckets + * (the extent to which this is true is a measure of the quality of + * the hash function as it applies to the key domain). + * + * With the items distributed into more buckets, the chain length + * (item count) in each bucket is reduced. Thus by expanding buckets + * the hash keeps a bound on the chain length. This bounded chain + * length is the essence of how a hash provides constant time lookup. + * + * The calculation of tbl->ideal_chain_maxlen below deserves some + * explanation. First, keep in mind that we're calculating the ideal + * maximum chain length based on the *new* (doubled) bucket count. + * In fractions this is just n/b (n=number of items,b=new num buckets). + * Since the ideal chain length is an integer, we want to calculate + * ceil(n/b). We don't depend on floating point arithmetic in this + * hash, so to calculate ceil(n/b) with integers we could write + * + * ceil(n/b) = (n/b) + ((n%b)?1:0) + * + * and in fact a previous version of this hash did just that. + * But now we have improved things a bit by recognizing that b is + * always a power of two. We keep its base 2 log handy (call it lb), + * so now we can write this with a bit shift and logical AND: + * + * ceil(n/b) = (n>>lb) + ( (n & (b-1)) ? 1:0) + * + */ +#define HASH_EXPAND_BUCKETS(hh,tbl,oomed) \ +do { \ + unsigned _he_bkt; \ + unsigned _he_bkt_i; \ + struct UT_hash_handle *_he_thh, *_he_hh_nxt; \ + UT_hash_bucket *_he_new_buckets, *_he_newbkt; \ + _he_new_buckets = (UT_hash_bucket*)uthash_malloc( \ + 2UL * (tbl)->num_buckets * sizeof(struct UT_hash_bucket)); \ + if (!_he_new_buckets) { \ + HASH_RECORD_OOM(oomed); \ + } else { \ + uthash_bzero(_he_new_buckets, \ + 2UL * (tbl)->num_buckets * sizeof(struct UT_hash_bucket)); \ + (tbl)->ideal_chain_maxlen = \ + ((tbl)->num_items >> ((tbl)->log2_num_buckets+1U)) + \ + ((((tbl)->num_items & (((tbl)->num_buckets*2U)-1U)) != 0U) ? 1U : 0U); \ + (tbl)->nonideal_items = 0; \ + for (_he_bkt_i = 0; _he_bkt_i < (tbl)->num_buckets; _he_bkt_i++) { \ + _he_thh = (tbl)->buckets[ _he_bkt_i ].hh_head; \ + while (_he_thh != NULL) { \ + _he_hh_nxt = _he_thh->hh_next; \ + HASH_TO_BKT(_he_thh->hashv, (tbl)->num_buckets * 2U, _he_bkt); \ + _he_newbkt = &(_he_new_buckets[_he_bkt]); \ + if (++(_he_newbkt->count) > (tbl)->ideal_chain_maxlen) { \ + (tbl)->nonideal_items++; \ + _he_newbkt->expand_mult = _he_newbkt->count / (tbl)->ideal_chain_maxlen; \ + } \ + _he_thh->hh_prev = NULL; \ + _he_thh->hh_next = _he_newbkt->hh_head; \ + if (_he_newbkt->hh_head != NULL) { \ + _he_newbkt->hh_head->hh_prev = _he_thh; \ + } \ + _he_newbkt->hh_head = _he_thh; \ + _he_thh = _he_hh_nxt; \ + } \ + } \ + uthash_free((tbl)->buckets, (tbl)->num_buckets * sizeof(struct UT_hash_bucket)); \ + (tbl)->num_buckets *= 2U; \ + (tbl)->log2_num_buckets++; \ + (tbl)->buckets = _he_new_buckets; \ + (tbl)->ineff_expands = ((tbl)->nonideal_items > ((tbl)->num_items >> 1)) ? \ + ((tbl)->ineff_expands+1U) : 0U; \ + if ((tbl)->ineff_expands > 1U) { \ + (tbl)->noexpand = 1; \ + uthash_noexpand_fyi(tbl); \ + } \ + uthash_expand_fyi(tbl); \ + } \ +} while (0) + + +/* This is an adaptation of Simon Tatham's O(n log(n)) mergesort */ +/* Note that HASH_SORT assumes the hash handle name to be hh. + * HASH_SRT was added to allow the hash handle name to be passed in. */ +#define HASH_SORT(head,cmpfcn) HASH_SRT(hh,head,cmpfcn) +#define HASH_SRT(hh,head,cmpfcn) \ +do { \ + unsigned _hs_i; \ + unsigned _hs_looping,_hs_nmerges,_hs_insize,_hs_psize,_hs_qsize; \ + struct UT_hash_handle *_hs_p, *_hs_q, *_hs_e, *_hs_list, *_hs_tail; \ + if (head != NULL) { \ + _hs_insize = 1; \ + _hs_looping = 1; \ + _hs_list = &((head)->hh); \ + while (_hs_looping != 0U) { \ + _hs_p = _hs_list; \ + _hs_list = NULL; \ + _hs_tail = NULL; \ + _hs_nmerges = 0; \ + while (_hs_p != NULL) { \ + _hs_nmerges++; \ + _hs_q = _hs_p; \ + _hs_psize = 0; \ + for (_hs_i = 0; _hs_i < _hs_insize; ++_hs_i) { \ + _hs_psize++; \ + _hs_q = ((_hs_q->next != NULL) ? \ + HH_FROM_ELMT((head)->hh.tbl, _hs_q->next) : NULL); \ + if (_hs_q == NULL) { \ + break; \ + } \ + } \ + _hs_qsize = _hs_insize; \ + while ((_hs_psize != 0U) || ((_hs_qsize != 0U) && (_hs_q != NULL))) { \ + if (_hs_psize == 0U) { \ + _hs_e = _hs_q; \ + _hs_q = ((_hs_q->next != NULL) ? \ + HH_FROM_ELMT((head)->hh.tbl, _hs_q->next) : NULL); \ + _hs_qsize--; \ + } else if ((_hs_qsize == 0U) || (_hs_q == NULL)) { \ + _hs_e = _hs_p; \ + if (_hs_p != NULL) { \ + _hs_p = ((_hs_p->next != NULL) ? \ + HH_FROM_ELMT((head)->hh.tbl, _hs_p->next) : NULL); \ + } \ + _hs_psize--; \ + } else if ((cmpfcn( \ + DECLTYPE(head)(ELMT_FROM_HH((head)->hh.tbl, _hs_p)), \ + DECLTYPE(head)(ELMT_FROM_HH((head)->hh.tbl, _hs_q)) \ + )) <= 0) { \ + _hs_e = _hs_p; \ + if (_hs_p != NULL) { \ + _hs_p = ((_hs_p->next != NULL) ? \ + HH_FROM_ELMT((head)->hh.tbl, _hs_p->next) : NULL); \ + } \ + _hs_psize--; \ + } else { \ + _hs_e = _hs_q; \ + _hs_q = ((_hs_q->next != NULL) ? \ + HH_FROM_ELMT((head)->hh.tbl, _hs_q->next) : NULL); \ + _hs_qsize--; \ + } \ + if ( _hs_tail != NULL ) { \ + _hs_tail->next = ((_hs_e != NULL) ? \ + ELMT_FROM_HH((head)->hh.tbl, _hs_e) : NULL); \ + } else { \ + _hs_list = _hs_e; \ + } \ + if (_hs_e != NULL) { \ + _hs_e->prev = ((_hs_tail != NULL) ? \ + ELMT_FROM_HH((head)->hh.tbl, _hs_tail) : NULL); \ + } \ + _hs_tail = _hs_e; \ + } \ + _hs_p = _hs_q; \ + } \ + if (_hs_tail != NULL) { \ + _hs_tail->next = NULL; \ + } \ + if (_hs_nmerges <= 1U) { \ + _hs_looping = 0; \ + (head)->hh.tbl->tail = _hs_tail; \ + DECLTYPE_ASSIGN(head, ELMT_FROM_HH((head)->hh.tbl, _hs_list)); \ + } \ + _hs_insize *= 2U; \ + } \ + HASH_FSCK(hh, head, "HASH_SRT"); \ + } \ +} while (0) + +/* This function selects items from one hash into another hash. + * The end result is that the selected items have dual presence + * in both hashes. There is no copy of the items made; rather + * they are added into the new hash through a secondary hash + * hash handle that must be present in the structure. */ +#define HASH_SELECT(hh_dst, dst, hh_src, src, cond) \ +do { \ + unsigned _src_bkt, _dst_bkt; \ + void *_last_elt = NULL, *_elt; \ + UT_hash_handle *_src_hh, *_dst_hh, *_last_elt_hh=NULL; \ + ptrdiff_t _dst_hho = ((char*)(&(dst)->hh_dst) - (char*)(dst)); \ + if ((src) != NULL) { \ + for (_src_bkt=0; _src_bkt < (src)->hh_src.tbl->num_buckets; _src_bkt++) { \ + for (_src_hh = (src)->hh_src.tbl->buckets[_src_bkt].hh_head; \ + _src_hh != NULL; \ + _src_hh = _src_hh->hh_next) { \ + _elt = ELMT_FROM_HH((src)->hh_src.tbl, _src_hh); \ + if (cond(_elt)) { \ + IF_HASH_NONFATAL_OOM( int _hs_oomed = 0; ) \ + _dst_hh = (UT_hash_handle*)(((char*)_elt) + _dst_hho); \ + _dst_hh->key = _src_hh->key; \ + _dst_hh->keylen = _src_hh->keylen; \ + _dst_hh->hashv = _src_hh->hashv; \ + _dst_hh->prev = _last_elt; \ + _dst_hh->next = NULL; \ + if (_last_elt_hh != NULL) { \ + _last_elt_hh->next = _elt; \ + } \ + if ((dst) == NULL) { \ + DECLTYPE_ASSIGN(dst, _elt); \ + HASH_MAKE_TABLE(hh_dst, dst, _hs_oomed); \ + IF_HASH_NONFATAL_OOM( \ + if (_hs_oomed) { \ + uthash_nonfatal_oom(_elt); \ + (dst) = NULL; \ + continue; \ + } \ + ) \ + } else { \ + _dst_hh->tbl = (dst)->hh_dst.tbl; \ + } \ + HASH_TO_BKT(_dst_hh->hashv, _dst_hh->tbl->num_buckets, _dst_bkt); \ + HASH_ADD_TO_BKT(_dst_hh->tbl->buckets[_dst_bkt], hh_dst, _dst_hh, _hs_oomed); \ + (dst)->hh_dst.tbl->num_items++; \ + IF_HASH_NONFATAL_OOM( \ + if (_hs_oomed) { \ + HASH_ROLLBACK_BKT(hh_dst, dst, _dst_hh); \ + HASH_DELETE_HH(hh_dst, dst, _dst_hh); \ + _dst_hh->tbl = NULL; \ + uthash_nonfatal_oom(_elt); \ + continue; \ + } \ + ) \ + HASH_BLOOM_ADD(_dst_hh->tbl, _dst_hh->hashv); \ + _last_elt = _elt; \ + _last_elt_hh = _dst_hh; \ + } \ + } \ + } \ + } \ + HASH_FSCK(hh_dst, dst, "HASH_SELECT"); \ +} while (0) + +#define HASH_CLEAR(hh,head) \ +do { \ + if ((head) != NULL) { \ + HASH_BLOOM_FREE((head)->hh.tbl); \ + uthash_free((head)->hh.tbl->buckets, \ + (head)->hh.tbl->num_buckets*sizeof(struct UT_hash_bucket)); \ + uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \ + (head) = NULL; \ + } \ +} while (0) + +#define HASH_OVERHEAD(hh,head) \ + (((head) != NULL) ? ( \ + (size_t)(((head)->hh.tbl->num_items * sizeof(UT_hash_handle)) + \ + ((head)->hh.tbl->num_buckets * sizeof(UT_hash_bucket)) + \ + sizeof(UT_hash_table) + \ + (HASH_BLOOM_BYTELEN))) : 0U) + +#ifdef NO_DECLTYPE +#define HASH_ITER(hh,head,el,tmp) \ +for(((el)=(head)), ((*(char**)(&(tmp)))=(char*)((head!=NULL)?(head)->hh.next:NULL)); \ + (el) != NULL; ((el)=(tmp)), ((*(char**)(&(tmp)))=(char*)((tmp!=NULL)?(tmp)->hh.next:NULL))) +#else +#define HASH_ITER(hh,head,el,tmp) \ +for(((el)=(head)), ((tmp)=DECLTYPE(el)((head!=NULL)?(head)->hh.next:NULL)); \ + (el) != NULL; ((el)=(tmp)), ((tmp)=DECLTYPE(el)((tmp!=NULL)?(tmp)->hh.next:NULL))) +#endif + +/* obtain a count of items in the hash */ +#define HASH_COUNT(head) HASH_CNT(hh,head) +#define HASH_CNT(hh,head) ((head != NULL)?((head)->hh.tbl->num_items):0U) + +typedef struct UT_hash_bucket { + struct UT_hash_handle *hh_head; + unsigned count; + + /* expand_mult is normally set to 0. In this situation, the max chain length + * threshold is enforced at its default value, HASH_BKT_CAPACITY_THRESH. (If + * the bucket's chain exceeds this length, bucket expansion is triggered). + * However, setting expand_mult to a non-zero value delays bucket expansion + * (that would be triggered by additions to this particular bucket) + * until its chain length reaches a *multiple* of HASH_BKT_CAPACITY_THRESH. + * (The multiplier is simply expand_mult+1). The whole idea of this + * multiplier is to reduce bucket expansions, since they are expensive, in + * situations where we know that a particular bucket tends to be overused. + * It is better to let its chain length grow to a longer yet-still-bounded + * value, than to do an O(n) bucket expansion too often. + */ + unsigned expand_mult; + +} UT_hash_bucket; + +/* random signature used only to find hash tables in external analysis */ +#define HASH_SIGNATURE 0xa0111fe1u +#define HASH_BLOOM_SIGNATURE 0xb12220f2u + +typedef struct UT_hash_table { + UT_hash_bucket *buckets; + unsigned num_buckets, log2_num_buckets; + unsigned num_items; + struct UT_hash_handle *tail; /* tail hh in app order, for fast append */ + ptrdiff_t hho; /* hash handle offset (byte pos of hash handle in element */ + + /* in an ideal situation (all buckets used equally), no bucket would have + * more than ceil(#items/#buckets) items. that's the ideal chain length. */ + unsigned ideal_chain_maxlen; + + /* nonideal_items is the number of items in the hash whose chain position + * exceeds the ideal chain maxlen. these items pay the penalty for an uneven + * hash distribution; reaching them in a chain traversal takes >ideal steps */ + unsigned nonideal_items; + + /* ineffective expands occur when a bucket doubling was performed, but + * afterward, more than half the items in the hash had nonideal chain + * positions. If this happens on two consecutive expansions we inhibit any + * further expansion, as it's not helping; this happens when the hash + * function isn't a good fit for the key domain. When expansion is inhibited + * the hash will still work, albeit no longer in constant time. */ + unsigned ineff_expands, noexpand; + + uint32_t signature; /* used only to find hash tables in external analysis */ +#ifdef HASH_BLOOM + uint32_t bloom_sig; /* used only to test bloom exists in external analysis */ + uint8_t *bloom_bv; + uint8_t bloom_nbits; +#endif + +} UT_hash_table; + +typedef struct UT_hash_handle { + struct UT_hash_table *tbl; + void *prev; /* prev element in app order */ + void *next; /* next element in app order */ + struct UT_hash_handle *hh_prev; /* previous hh in bucket order */ + struct UT_hash_handle *hh_next; /* next hh in bucket order */ + void *key; /* ptr to enclosing struct's key */ + unsigned keylen; /* enclosing struct's key len */ + unsigned hashv; /* result of hash-fcn(key) */ +} UT_hash_handle; + +#endif /* UTHASH_H */ diff --git a/tpws/Makefile b/tpws/Makefile new file mode 100644 index 0000000..7094d43 --- /dev/null +++ b/tpws/Makefile @@ -0,0 +1,12 @@ +CC ?= gcc +CFLAGS += -std=c99 -s -O3 +LIBS = -lz -lcap +SRC_FILES = *.c + +all: tpws + +tpws: $(SRC_FILES) + $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) $(LIBS) + +clean: + rm -f tpws *.o diff --git a/tpws/gzip.c b/tpws/gzip.c new file mode 100644 index 0000000..d536b23 --- /dev/null +++ b/tpws/gzip.c @@ -0,0 +1,82 @@ +#include +#include +#include +#include "gzip.h" + +#define ZCHUNK 16384 +#define BUFMIN 128 +#define BUFCHUNK (1024*128) + +int z_readfile(FILE *F,char **buf,size_t *size) +{ + z_stream zs; + int r; + unsigned char in[ZCHUNK]; + size_t bufsize; + void *newbuf; + + memset(&zs,0,sizeof(zs)); + + *buf = NULL; + bufsize=*size=0; + + r=inflateInit2(&zs,47); + if (r != Z_OK) return r; + + do + { + zs.avail_in = fread(in, 1, sizeof(in), F); + if (ferror(F)) + { + r = Z_ERRNO; + goto zerr; + } + if (!zs.avail_in) break; + zs.next_in = in; + do + { + if ((bufsize-*size) +#include +#include + +int z_readfile(FILE *F,char **buf,size_t *size); +bool is_gzip(FILE* F); diff --git a/tpws/hostlist.c b/tpws/hostlist.c new file mode 100644 index 0000000..cbed7a8 --- /dev/null +++ b/tpws/hostlist.c @@ -0,0 +1,112 @@ +#include +#include "hostlist.h" +#include "gzip.h" +#include "params.h" + +static bool addpool(strpool **hostlist, char **s, char *end) +{ + char *p; + + // advance until eol lowering all chars + for (p = *s; p +#include "strpool.h" + +bool LoadHostList(strpool **hostlist, char *filename); +bool SearchHostList(strpool *hostlist, const char *host); diff --git a/tpws/params.h b/tpws/params.h new file mode 100644 index 0000000..87f2ec4 --- /dev/null +++ b/tpws/params.h @@ -0,0 +1,43 @@ +#pragma once + +#include +#include +#include +#include "strpool.h" + +enum splithttpreq { split_none = 0, split_method, split_host }; + +struct params_s +{ + char bindaddr[64],bindiface[IF_NAMESIZE]; + bool bind_if6; + bool bindll,bindll_force; + int bind_wait_ifup,bind_wait_ip,bind_wait_ip_ll; + uint8_t proxy_type; + bool no_resolve; + bool skip_nodelay; + uid_t uid; + gid_t gid; + bool daemon; + uint16_t port; + int maxconn,maxfiles,max_orphan_time; + int local_rcvbuf,local_sndbuf,remote_rcvbuf,remote_sndbuf; + + bool tamper; // any tamper option is set + bool hostcase, hostdot, hosttab, hostnospace, methodspace, methodeol, unixeol; + int hostpad; + char hostspell[4]; + enum splithttpreq split_http_req; + int split_pos; + char hostfile[256]; + char pidfile[256]; + strpool *hostlist; + + int debug; +}; + +extern struct params_s params; + +#define _DBGPRINT(format, level, ...) { if (params.debug>=level) printf(format "\n", ##__VA_ARGS__); } +#define VPRINT(format, ...) _DBGPRINT(format,1,##__VA_ARGS__) +#define DBGPRINT(format, ...) _DBGPRINT(format,2,##__VA_ARGS__) diff --git a/tpws/socks.h b/tpws/socks.h new file mode 100644 index 0000000..9026a64 --- /dev/null +++ b/tpws/socks.h @@ -0,0 +1,93 @@ +#pragma once + +#include +#include + +#pragma pack(push,1) + +#define S4_CMD_CONNECT 1 +#define S4_CMD_BIND 2 +typedef struct +{ + uint8_t ver,cmd; + uint16_t port; + uint32_t ip; +} s4_req; +#define S4_REQ_HEADER_VALID(r,l) (l>=sizeof(s4_req) && r->ver==4) +#define S4_REQ_CONNECT_VALID(r,l) (S4_REQ_HEADER_VALID(r,l) && r->cmd==S4_CMD_CONNECT) + +#define S4_REP_OK 90 +#define S4_REP_FAILED 91 +typedef struct +{ + uint8_t zero,rep; + uint16_t port; + uint32_t ip; +} s4_rep; + + + +#define S5_AUTH_NONE 0 +#define S5_AUTH_GSSAPI 1 +#define S5_AUTH_USERPASS 2 +#define S5_AUTH_UNACCEPTABLE 0xFF +typedef struct +{ + uint8_t ver,nmethods,methods[255]; +} s5_handshake; +#define S5_REQ_HANDHSHAKE_VALID(r,l) (l>=3 && r->ver==5 && r->nmethods && l>=(2+r->nmethods)) +typedef struct +{ + uint8_t ver,method; +} s5_handshake_ack; + +#define S5_CMD_CONNECT 1 +#define S5_CMD_BIND 2 +#define S5_CMD_UDP_ASSOC 3 +#define S5_ATYP_IP4 1 +#define S5_ATYP_DOM 3 +#define S5_ATYP_IP6 4 +typedef struct +{ + uint8_t ver,cmd,rsv,atyp; + union { + struct { + struct in_addr addr; + uint16_t port; + } d4; + struct { + struct in6_addr addr; + uint16_t port; + } d6; + struct { + uint8_t len; + char domport[255+2]; // max hostname + binary port + } dd; + }; +} s5_req; +#define S5_REQ_HEADER_VALID(r,l) (l>=4 && r->ver==5) +#define S5_IP46_VALID(r,l) (r->atyp==S5_ATYP_IP4 && l>=(4+sizeof(r->d4)) || r->atyp==S5_ATYP_IP6 && l>=(4+sizeof(r->d6))) +#define S5_REQ_CONNECT_VALID(r,l) (S5_REQ_HEADER_VALID(r,l) && r->cmd==S5_CMD_CONNECT && (S5_IP46_VALID(r,l) || r->atyp==S5_ATYP_DOM && l>=5 && l>=(5+r->dd.len))) +#define S5_PORT_FROM_DD(r,l) (l>=(4+r->dd.len+2) ? ntohs(*(uint16_t*)(r->dd.domport+r->dd.len)) : 0) + +#define S5_REP_OK 0 +#define S5_REP_GENERAL_FAILURE 1 +#define S5_REP_NOT_ALLOWED_BY_RULESET 2 +#define S5_REP_NETWORK_UNREACHABLE 3 +#define S5_REP_HOST_UNREACHABLE 4 +#define S5_REP_CONN_REFUSED 5 +#define S5_REP_TTL_EXPIRED 6 +#define S5_REP_COMMAND_NOT_SUPPORTED 7 +#define S5_REP_ADDR_TYPE_NOT_SUPPORTED 8 +typedef struct +{ + uint8_t ver,rep,rsv,atyp; + union { + struct { + struct in_addr addr; + uint16_t port; + } d4; + }; +} s5_rep; + +#pragma pack(pop) diff --git a/tpws/strpool.c b/tpws/strpool.c new file mode 100644 index 0000000..41e62a9 --- /dev/null +++ b/tpws/strpool.c @@ -0,0 +1,76 @@ +#define _GNU_SOURCE +#include "strpool.h" +#include +#include + +#undef uthash_nonfatal_oom +#define uthash_nonfatal_oom(elt) ut_oom_recover(elt) + +static bool oom=false; +static void ut_oom_recover(strpool *elem) +{ + oom=true; +} + +// for zero terminated strings +bool StrPoolAddStr(strpool **pp,const char *s) +{ + strpool *elem; + if (!(elem = (strpool*)malloc(sizeof(strpool)))) + return false; + if (!(elem->str = strdup(s))) + { + free(elem); + return false; + } + oom = false; + HASH_ADD_KEYPTR( hh, *pp, elem->str, strlen(elem->str), elem ); + if (oom) + { + free(elem->str); + free(elem); + return false; + } + return true; +} +// for not zero terminated strings +bool StrPoolAddStrLen(strpool **pp,const char *s,size_t slen) +{ + strpool *elem; + if (!(elem = (strpool*)malloc(sizeof(strpool)))) + return false; + if (!(elem->str = malloc(slen+1))) + { + free(elem); + return false; + } + memcpy(elem->str,s,slen); + elem->str[slen]=0; + oom = false; + HASH_ADD_KEYPTR( hh, *pp, elem->str, strlen(elem->str), elem ); + if (oom) + { + free(elem->str); + free(elem); + return false; + } + return true; +} + +bool StrPoolCheckStr(strpool *p,const char *s) +{ + strpool *elem; + HASH_FIND_STR( p, s, elem); + return elem!=NULL; +} + +void StrPoolDestroy(strpool **p) +{ + strpool *elem,*tmp; + HASH_ITER(hh, *p, elem, tmp) { + free(elem->str); + HASH_DEL(*p, elem); + free(elem); + } + *p = NULL; +} diff --git a/tpws/strpool.h b/tpws/strpool.h new file mode 100644 index 0000000..5932ba3 --- /dev/null +++ b/tpws/strpool.h @@ -0,0 +1,19 @@ +#pragma once + +#include +#include + +//#define HASH_BLOOM 20 +#define HASH_NONFATAL_OOM 1 +#define HASH_FUNCTION HASH_BER +#include "uthash.h" + +typedef struct strpool { + char *str; /* key */ + UT_hash_handle hh; /* makes this structure hashable */ +} strpool; + +void StrPoolDestroy(strpool **p); +bool StrPoolAddStr(strpool **pp,const char *s); +bool StrPoolAddStrLen(strpool **pp,const char *s,size_t slen); +bool StrPoolCheckStr(strpool *p,const char *s); diff --git a/tpws/tamper.c b/tpws/tamper.c new file mode 100644 index 0000000..9b100e4 --- /dev/null +++ b/tpws/tamper.c @@ -0,0 +1,222 @@ +#include "tamper.h" +#include "params.h" +#include "hostlist.h" +#include +#include + +char *find_bin(void *data, size_t len, const void *blk, size_t blk_len) +{ + while (len >= blk_len) + { + if (!memcmp(data, blk, blk_len)) + return data; + data = (char*)data + 1; + len--; + } + return NULL; +} + +// pHost points to "Host: ..." +bool find_host(char **pHost,char *buf,size_t bs) +{ + if (!*pHost) + { + *pHost = find_bin(buf, bs, "\nHost:", 6); + if (*pHost) + { + (*pHost)++; + VPRINT("Found Host: at pos %zu",*pHost - buf) + } + } + return !!*pHost; +} + +static const char *http_methods[] = { "GET /","POST /","HEAD /","OPTIONS /","PUT /","DELETE /","CONNECT /","TRACE /",NULL }; +void modify_tcp_segment(char *segment,size_t segment_buffer_size,size_t *size,size_t *split_pos) +{ + char *p, *pp, *pHost = NULL; + size_t method_len = 0, pos; + const char **method; + bool bIsHttp = false, bBypass = false; + char bRemovedHostSpace = 0; + char Host[128]; + + *split_pos=0; + + for (method = http_methods; *method; method++) + { + method_len = strlen(*method); + if (method_len <= *size && !memcmp(segment, *method, method_len)) + { + bIsHttp = true; + method_len -= 2; // "GET /" => "GET" + break; + } + } + if (bIsHttp) + { + VPRINT("Data block looks like http request start : %s", *method) + // cpu saving : we search host only if and when required. we do not research host every time we need its position + if (params.hostlist && find_host(&pHost,segment,*size)) + { + p = pHost + 5; + while (p < (segment + *size) && (*p == ' ' || *p == '\t')) p++; + pp = p; + while (pp < (segment + *size) && (pp - p) < (sizeof(Host) - 1) && *pp != '\r' && *pp != '\n') pp++; + memcpy(Host, p, pp - p); + Host[pp - p] = '\0'; + VPRINT("Requested Host is : %s", Host) + for(p = Host; *p; p++) *p=tolower(*p); + bBypass = !SearchHostList(params.hostlist,Host); + } + if (!bBypass) + { + if (params.unixeol) + { + p = pp = segment; + while (p = find_bin(p, segment + *size - p, "\r\n", 2)) + { + *p = '\n'; p++; + memmove(p, p + 1, segment + *size - p - 1); + (*size)--; + if (pp == (p - 1)) + { + // probably end of http headers + VPRINT("Found double EOL at pos %zu. Stop replacing.", pp - segment) + break; + } + pp = p; + } + pHost = NULL; // invalidate + } + if (params.methodeol && (*size+1+!params.unixeol)<=segment_buffer_size) + { + VPRINT("Adding EOL before method") + if (params.unixeol) + { + memmove(segment + 1, segment, *size); + (*size)++;; + segment[0] = '\n'; + if (*split_pos) (*split_pos)++; + } + else + { + memmove(segment + 2, segment, *size); + *size += 2; + segment[0] = '\r'; + segment[1] = '\n'; + if (*split_pos) *split_pos += 2; + } + pHost = NULL; // invalidate + } + if (params.methodspace && *size '%c%c%c%c:' at pos %zu", params.hostspell[0], params.hostspell[1], params.hostspell[2], params.hostspell[3], pHost - segment) + memcpy(pHost, params.hostspell, 4); + } + if (params.hostpad && find_host(&pHost,segment,*size)) + { + // add : XXXXX: segment_buffer_size) + VPRINT("could not add host padding : buffer too small") + else + { + if ((hostpad+*size)>segment_buffer_size) + { + hostpad=segment_buffer_size-*size; + VPRINT("host padding reduced to %zu bytes : buffer too small", hostpad) + } + else + VPRINT("host padding with %zu bytes", hostpad) + + p = pHost; + pos = p - segment; + memmove(p + hostpad, p, *size - pos); + (*size) += hostpad; + while(hostpad) + { + #define MAX_HDR_SIZE 2048 + size_t padsize = hostpad > hsize ? hostpad-hsize : 0; + if (padsize>MAX_HDR_SIZE) padsize=MAX_HDR_SIZE; + // if next header would be too small then add extra padding to the current one + if ((hostpad-padsize-hsize) +#include + +char *find_bin(void *data, size_t len, const void *blk, size_t blk_len); +bool find_host(char **pHost,char *buf,size_t bs); +void modify_tcp_segment(char *segment,size_t segment_buffer_size,size_t *size,size_t *split_pos); diff --git a/tpws/tpws.c b/tpws/tpws.c new file mode 100644 index 0000000..2b671b7 --- /dev/null +++ b/tpws/tpws.c @@ -0,0 +1,850 @@ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "tpws.h" +#include "tpws_conn.h" +#include "hostlist.h" +#include "params.h" + +struct params_s params; + +bool bHup = false; +static void onhup(int sig) +{ + printf("HUP received !\n"); + if (params.hostlist) + printf("Will reload hostlist on next request\n"); + bHup = true; +} +// should be called in normal execution +void dohup() +{ + if (bHup) + { + if (params.hostlist) + { + if (!LoadHostList(¶ms.hostlist, params.hostfile)) + { + // what will we do without hostlist ?? sure, gonna die + exit(1); + } + } + bHup = false; + } +} + + + +static int8_t block_sigpipe() +{ + sigset_t sigset; + memset(&sigset, 0, sizeof(sigset)); + + //Get the old sigset, add SIGPIPE and update sigset + if (sigprocmask(SIG_BLOCK, NULL, &sigset) == -1) { + perror("sigprocmask (get)"); + return -1; + } + + if (sigaddset(&sigset, SIGPIPE) == -1) { + perror("sigaddset"); + return -1; + } + + if (sigprocmask(SIG_BLOCK, &sigset, NULL) == -1) { + perror("sigprocmask (set)"); + return -1; + } + + return 0; +} + + +static bool is_interface_online(const char *ifname) +{ + struct ifreq ifr; + int sock; + + if ((sock=socket(PF_INET, SOCK_DGRAM, IPPROTO_IP))==-1) + return false; + memset(&ifr, 0, sizeof(ifr)); + strncpy(ifr.ifr_name, ifname, IFNAMSIZ); + ifr.ifr_name[IFNAMSIZ] = 0; + ioctl(sock, SIOCGIFFLAGS, &ifr); + close(sock); + return !!(ifr.ifr_flags & IFF_UP); +} + + +static void exithelp() +{ + printf( + " --bind-addr=|\n" + " --bind-iface4=\t; bind to the first ipv4 addr of interface\n" + " --bind-iface6=\t; bind to the first ipv6 addr of interface\n" + " --bind-linklocal=prefer|force\t; prefer or force ipv6 link local\n" + " --bind-wait-ifup=\t\t; wait for interface to appear and up\n" + " --bind-wait-ip=\t\t; after ifup wait for ip address to appear up to N seconds\n" + " --bind-wait-ip-linklocal=\t; accept only link locals first N seconds then any\n" + " --socks\t\t\t; implement socks4/5 proxy instead of transparent proxy\n" + " --no-resolve\t\t\t; disable socks5 remote dns ability (resolves are not async, they block all activity)\n" + " --local-rcvbuf=\n" + " --local-sndbuf=\n" + " --remote-rcvbuf=\n" + " --remote-sndbuf=\n" + " --skip-nodelay\t\t\t; do not set TCP_NODELAY option for outgoing connections (incompatible with split options)\n" + " --port=\n" + " --maxconn=\n" + " --maxfiles=\t; should be at least (X*connections+16), where X=6 in tcp proxy mode, X=4 in tampering mode\n" + " --max-orphan-time=\t; if local leg sends something and closes and remote leg is still connecting then cancel connection attempt after N seconds\n" + " --daemon\t\t\t; daemonize\n" + " --pidfile=\t\t; write pid to file\n" + " --user=\t\t; drop root privs\n" + " --uid=uid[:gid]\t\t; drop root privs\n" + " --debug=0|1|2\t\t\t; 0(default)=silent 1=verbose 2=debug\n" + "\nTAMPERING:\n" + " --hostlist=\t\t; only act on host in the list (one host per line, subdomains auto apply)\n" + " --split-http-req=method|host\n" + " --split-pos=\t; split at specified pos. invalidates split-http-req.\n" + " --hostcase\t\t\t; change Host: => host:\n" + " --hostspell\t\t\t; exact spelling of \"Host\" header. must be 4 chars. default is \"host\"\n" + " --hostdot\t\t\t; add \".\" after Host: name\n" + " --hosttab\t\t\t; add tab after Host: name\n" + " --hostnospace\t\t\t; remove space after Host:\n" + " --hostpad=\t\t; add dummy padding headers before Host:\n" + " --methodspace\t\t\t; add extra space after method\n" + " --methodeol\t\t\t; add end-of-line before method\n" + " --unixeol\t\t\t; replace 0D0A to 0A\n" + ); + exit(1); +} +static void cleanup_params() +{ + if (params.hostlist) + { + StrPoolDestroy(¶ms.hostlist); + params.hostlist = NULL; + } +} +static void exithelp_clean() +{ + cleanup_params(); + exithelp(); +} +static void exit_clean(int code) +{ + cleanup_params(); + exit(code); +} +void parse_params(int argc, char *argv[]) +{ + int option_index = 0; + int v, i; + + memset(¶ms, 0, sizeof(params)); + memcpy(params.hostspell, "host", 4); // default hostspell + params.maxconn = DEFAULT_MAX_CONN; + params.max_orphan_time = DEFAULT_MAX_ORPHAN_TIME; + + const struct option long_options[] = { + { "help",no_argument,0,0 },// optidx=0 + { "h",no_argument,0,0 },// optidx=1 + { "bind-addr",required_argument,0,0 },// optidx=2 + { "bind-iface4",required_argument,0,0 },// optidx=3 + { "bind-iface6",required_argument,0,0 },// optidx=4 + { "bind-linklocal",required_argument,0,0 },// optidx=5 + { "bind-wait-ifup",required_argument,0,0 },// optidx=6 + { "bind-wait-ip",required_argument,0,0 },// optidx=7 + { "bind-wait-ip-linklocal",required_argument,0,0 },// optidx=8 + { "port",required_argument,0,0 },// optidx=9 + { "daemon",no_argument,0,0 },// optidx=10 + { "user",required_argument,0,0 },// optidx=11 + { "uid",required_argument,0,0 },// optidx=12 + { "maxconn",required_argument,0,0 },// optidx=13 + { "maxfiles",required_argument,0,0 },// optidx=14 + { "max-orphan-time",required_argument,0,0 },// optidx=15 + { "hostcase",no_argument,0,0 },// optidx=16 + { "hostspell",required_argument,0,0 },// optidx=17 + { "hostdot",no_argument,0,0 },// optidx=18 + { "hostnospace",no_argument,0,0 },// optidx=19 + { "hostpad",required_argument,0,0 },// optidx=20 + { "split-http-req",required_argument,0,0 },// optidx=21 + { "split-pos",required_argument,0,0 },// optidx=22 + { "methodspace",no_argument,0,0 },// optidx=23 + { "methodeol",no_argument,0,0 },// optidx=24 + { "hosttab",no_argument,0,0 },// optidx=25 + { "unixeol",no_argument,0,0 },// optidx=26 + { "hostlist",required_argument,0,0 },// optidx=27 + { "pidfile",required_argument,0,0 },// optidx=28 + { "debug",optional_argument,0,0 },// optidx=29 + { "local-rcvbuf",required_argument,0,0 },// optidx=30 + { "local-sndbuf",required_argument,0,0 },// optidx=31 + { "remote-rcvbuf",required_argument,0,0 },// optidx=32 + { "remote-sndbuf",required_argument,0,0 },// optidx=33 + { "socks",no_argument,0,0 },// optidx=34 + { "no-resolve",no_argument,0,0 },// optidx=35 + { "skip-nodelay",no_argument,0,0 },// optidx=36 + { NULL,0,NULL,0 } + }; + while ((v = getopt_long_only(argc, argv, "", long_options, &option_index)) != -1) + { + if (v) exithelp_clean(); + switch (option_index) + { + case 0: + case 1: + exithelp_clean(); + break; + case 2: /* bind-addr */ + strncpy(params.bindaddr, optarg, sizeof(params.bindaddr)); + params.bindaddr[sizeof(params.bindaddr) - 1] = 0; + break; + case 3: /* bind-iface4 */ + params.bind_if6=false; + strncpy(params.bindiface, optarg, sizeof(params.bindiface)); + params.bindiface[sizeof(params.bindiface) - 1] = 0; + break; + case 4: /* bind-iface6 */ + params.bind_if6=true; + strncpy(params.bindiface, optarg, sizeof(params.bindiface)); + params.bindiface[sizeof(params.bindiface) - 1] = 0; + break; + case 5: /* bind-linklocal */ + params.bindll = true; + if (!strcmp(optarg, "force")) + params.bindll_force=true; + else if (strcmp(optarg, "prefer")) + { + fprintf(stderr, "invalid parameter in bind-linklocal : %s\n",optarg); + exit_clean(1); + } + break; + case 6: /* bind-wait-ifup */ + params.bind_wait_ifup = atoi(optarg); + break; + case 7: /* bind-wait-ip */ + params.bind_wait_ip = atoi(optarg); + break; + case 8: /* bind-wait-ip-linklocal */ + params.bind_wait_ip_ll = atoi(optarg); + break; + case 9: /* port */ + i = atoi(optarg); + if (i <= 0 || i > 65535) + { + fprintf(stderr, "bad port number\n"); + exit_clean(1); + } + params.port = (uint16_t)i; + break; + case 10: /* daemon */ + params.daemon = true; + break; + case 11: /* user */ + { + struct passwd *pwd = getpwnam(optarg); + if (!pwd) + { + fprintf(stderr, "non-existent username supplied\n"); + exit_clean(1); + } + params.uid = pwd->pw_uid; + params.gid = pwd->pw_gid; + break; + } + case 12: /* uid */ + params.gid=0x7FFFFFFF; // default git. drop gid=0 + if (!sscanf(optarg,"%u:%u",¶ms.uid,¶ms.gid)) + { + fprintf(stderr, "--uid should be : uid[:gid]\n"); + exit_clean(1); + } + break; + case 13: /* maxconn */ + params.maxconn = atoi(optarg); + if (params.maxconn <= 0 || params.maxconn > 10000) + { + fprintf(stderr, "bad maxconn\n"); + exit_clean(1); + } + break; + case 14: /* maxfiles */ + params.maxfiles = atoi(optarg); + if (params.maxfiles < 0) + { + fprintf(stderr, "bad maxfiles\n"); + exit_clean(1); + } + break; + case 15: /* max-orphan-time */ + params.max_orphan_time = atoi(optarg); + if (params.max_orphan_time < 0) + { + fprintf(stderr, "bad max_orphan_time\n"); + exit_clean(1); + } + break; + case 16: /* hostcase */ + params.hostcase = true; + params.tamper = true; + break; + case 17: /* hostspell */ + if (strlen(optarg) != 4) + { + fprintf(stderr, "hostspell must be exactly 4 chars long\n"); + exit_clean(1); + } + params.hostcase = true; + memcpy(params.hostspell, optarg, 4); + params.tamper = true; + break; + case 18: /* hostdot */ + params.hostdot = true; + params.tamper = true; + break; + case 19: /* hostnospace */ + params.hostnospace = true; + params.tamper = true; + break; + case 20: /* hostpad */ + params.hostpad = atoi(optarg); + params.tamper = true; + break; + case 21: /* split-http-req */ + if (!strcmp(optarg, "method")) + params.split_http_req = split_method; + else if (!strcmp(optarg, "host")) + params.split_http_req = split_host; + else + { + fprintf(stderr, "Invalid argument for split-http-req\n"); + exit_clean(1); + } + params.tamper = true; + break; + case 22: /* split-pos */ + i = atoi(optarg); + if (i) + params.split_pos = i; + else + { + fprintf(stderr, "Invalid argument for split-pos\n"); + exit_clean(1); + } + params.tamper = true; + break; + case 23: /* methodspace */ + params.methodspace = true; + params.tamper = true; + break; + case 24: /* methodeol */ + params.methodeol = true; + params.tamper = true; + break; + case 25: /* hosttab */ + params.hosttab = true; + params.tamper = true; + break; + case 26: /* unixeol */ + params.unixeol = true; + params.tamper = true; + break; + case 27: /* hostlist */ + if (!LoadHostList(¶ms.hostlist, optarg)) + exit_clean(1); + strncpy(params.hostfile,optarg,sizeof(params.hostfile)); + params.hostfile[sizeof(params.hostfile)-1]='\0'; + params.tamper = true; + break; + case 28: /* pidfile */ + strncpy(params.pidfile,optarg,sizeof(params.pidfile)); + params.pidfile[sizeof(params.pidfile)-1]='\0'; + break; + case 29: + params.debug = optarg ? atoi(optarg) : 1; + break; + case 30: /* local-rcvbuf */ + params.local_rcvbuf = atoi(optarg)/2; + break; + case 31: /* local-sndbuf */ + params.local_sndbuf = atoi(optarg)/2; + break; + case 32: /* remote-rcvbuf */ + params.remote_rcvbuf = atoi(optarg)/2; + break; + case 33: /* remote-sndbuf */ + params.remote_sndbuf = atoi(optarg)/2; + break; + case 34: /* socks */ + params.proxy_type = CONN_TYPE_SOCKS; + break; + case 35: /* no-resolve */ + params.no_resolve = true; + break; + case 36: /* skip-nodelay */ + params.skip_nodelay = true; + break; + } + } + if (!params.port) + { + fprintf(stderr, "Need port number\n"); + exit_clean(1); + } + if (params.skip_nodelay && (params.split_http_req || params.split_pos)) + { + fprintf(stderr, "Cannot split with --skip-nodelay\n"); + exit_clean(1); + } +} + +static void daemonize() +{ + int pid,fd; + + pid = fork(); + if (pid == -1) + { + perror("fork: "); + exit(2); + } + else if (pid != 0) + exit(0); + + if (setsid() == -1) + exit(2); + if (chdir("/") == -1) + exit(2); + close(STDIN_FILENO); + close(STDOUT_FILENO); + close(STDERR_FILENO); + /* redirect fd's 0,1,2 to /dev/null */ + open("/dev/null", O_RDWR); + /* stdin */ + fd=dup(0); + /* stdout */ + fd=dup(0); + /* stderror */ +} + +static bool setpcap(cap_value_t *caps,int ncaps) +{ + cap_t capabilities; + + if (!(capabilities = cap_init())) + return false; + + if (ncaps && (cap_set_flag(capabilities, CAP_PERMITTED, ncaps, caps, CAP_SET) || + cap_set_flag(capabilities, CAP_EFFECTIVE, ncaps, caps, CAP_SET))) + { + cap_free(capabilities); + return false; + } + if (cap_set_proc(capabilities)) + { + cap_free(capabilities); + return false; + } + cap_free(capabilities); + return true; +} +static int getmaxcap() +{ + int maxcap = CAP_LAST_CAP; + FILE *F = fopen("/proc/sys/kernel/cap_last_cap","r"); + if (F) + { + int n=fscanf(F,"%d",&maxcap); + fclose(F); + } + return maxcap; + +} +static bool dropcaps() +{ + // must have CAP_SETPCAP at the end. its required to clear bounding set + cap_value_t cap_values[] = {CAP_SETPCAP}; + int capct=sizeof(cap_values)/sizeof(*cap_values); + int maxcap = getmaxcap(); + + if (setpcap(cap_values, capct)) + { + for(int cap=0;cap<=maxcap;cap++) + { + if (cap_drop_bound(cap)) + { + fprintf(stderr,"could not drop cap %d\n",cap); + perror("cap_drop_bound"); + } + } + } + // now without CAP_SETPCAP + if (!setpcap(cap_values, capct - 1)) + { + perror("setpcap"); + return false; + } + return true; +} +static bool droproot() +{ + if (params.uid || params.gid) + { + if (prctl(PR_SET_KEEPCAPS, 1L)) + { + perror("prctl(PR_SET_KEEPCAPS): "); + return false; + } + if (setgid(params.gid)) + { + perror("setgid: "); + return false; + } + if (setuid(params.uid)) + { + perror("setuid: "); + return false; + } + } + return dropcaps(); +} + + +static bool writepid(const char *filename) +{ + FILE *F; + if (!(F=fopen(filename,"w"))) + return false; + fprintf(F,"%d",getpid()); + fclose(F); + return true; +} + + + +static bool find_listen_addr(struct sockaddr_storage *salisten, bool bindll, int *if_index) +{ + struct ifaddrs *addrs,*a; + bool found=false; + + if (getifaddrs(&addrs)<0) + return false; + + a = addrs; + while (a) + { + if (a->ifa_addr) + { + if (a->ifa_addr->sa_family==AF_INET && + *params.bindiface && !params.bind_if6 && !strcmp(a->ifa_name, params.bindiface)) + { + salisten->ss_family = AF_INET; + memcpy(&((struct sockaddr_in*)salisten)->sin_addr, &((struct sockaddr_in*)a->ifa_addr)->sin_addr, sizeof(struct in_addr)); + found=true; + break; + } + // ipv6 links locals are fe80::/10 + else if (a->ifa_addr->sa_family==AF_INET6 + && + (!*params.bindiface && bindll || + *params.bindiface && params.bind_if6 && !strcmp(a->ifa_name, params.bindiface)) + && + (!bindll || + ((struct sockaddr_in6*)a->ifa_addr)->sin6_addr.s6_addr[0]==0xFE && + (((struct sockaddr_in6*)a->ifa_addr)->sin6_addr.s6_addr[1] & 0xC0)==0x80)) + { + salisten->ss_family = AF_INET6; + memcpy(&((struct sockaddr_in6*)salisten)->sin6_addr, &((struct sockaddr_in6*)a->ifa_addr)->sin6_addr, sizeof(struct in6_addr)); + if (if_index) *if_index = if_nametoindex(a->ifa_name); + found=true; + break; + } + } + a = a->ifa_next; + } + freeifaddrs(addrs); + return found; +} + +static bool set_ulimit() +{ + FILE *F; + int fdmax,fdmin_system,n,cur_lim=0; + + if (!params.maxfiles) + { + // 4 fds per tamper connection (2 pipe + 2 socket), 6 fds for tcp proxy connection (4 pipe + 2 socket) + // additional 1/2 for unpaired remote legs sending buffers + // 16 for listen_fd, epoll, hostlist, ... + fdmax = (params.tamper ? 4 : 6) * params.maxconn; + fdmax += fdmax/2 + 16; + } + else + fdmax = params.maxfiles; + fdmin_system = fdmax + 4096; + DBGPRINT("set_ulimit : fdmax=%d fdmin_system=%d",fdmax,fdmin_system) + + if (!(F=fopen("/proc/sys/fs/file-max","r"))) + return false; + n=fscanf(F,"%d",&cur_lim); + fclose(F); + if (!n) return false; + DBGPRINT("set_ulimit : current system file-max=%d",cur_lim) + if (cur_lim 0) + { + int sec=0; + if (!is_interface_online(params.bindiface)) + { + fprintf(stderr,"waiting ifup of %s for up to %d seconds...\n",params.bindiface,params.bind_wait_ifup); + do + { + sleep(1); + sec++; + } + while (!is_interface_online(params.bindiface) && sec=params.bind_wait_ifup) + { + printf("wait timed out\n"); + goto exiterr; + } + } + } + if (!(if_index = if_nametoindex(params.bindiface)) && params.bind_wait_ip<=0) + { + printf("bad iface %s\n",params.bindiface); + goto exiterr; + } + } + if (*params.bindaddr) + { + if (inet_pton(AF_INET, params.bindaddr, &((struct sockaddr_in*)&salisten)->sin_addr)) + { + salisten.ss_family = AF_INET; + } + else if (inet_pton(AF_INET6, params.bindaddr, &((struct sockaddr_in6*)&salisten)->sin6_addr)) + { + salisten.ss_family = AF_INET6; + ipv6_only = 1; + } + else + { + printf("bad bind addr : %s\n", params.bindaddr); + goto exiterr; + } + } + else + { + if (*params.bindiface || params.bindll) + { + bool found; + int sec=0; + + if (params.bind_wait_ip > 0) + { + fprintf(stderr,"waiting for ip for %d seconds...\n", params.bind_wait_ip); + if (params.bindll && !params.bindll_force && params.bind_wait_ip_ll>0) + fprintf(stderr,"during the first %d seconds accepting only link locals...\n", params.bind_wait_ip_ll); + } + + for(;;) + { + found = find_listen_addr(&salisten,params.bindll,&if_index); + if (found) break; + + if (params.bindll && !params.bindll_force && sec>=params.bind_wait_ip_ll) + if (found = find_listen_addr(&salisten,false,&if_index)) break; + + if (sec>=params.bind_wait_ip) + break; + + sleep(1); + sec++; + } + + if (!found) + { + printf("suitable ip address not found\n"); + goto exiterr; + } + ipv6_only=1; + } + else + { + salisten.ss_family = AF_INET6; + // leave sin6_addr zero + } + } + if (salisten.ss_family == AF_INET6) + { + salisten_len = sizeof(struct sockaddr_in6); + ((struct sockaddr_in6*)&salisten)->sin6_port = htons(params.port); + ((struct sockaddr_in6*)&salisten)->sin6_scope_id = if_index; + } + else + { + salisten_len = sizeof(struct sockaddr_in); + ((struct sockaddr_in*)&salisten)->sin_port = htons(params.port); + } + + if (params.daemon) daemonize(); + + if (*params.pidfile && !writepid(params.pidfile)) + { + fprintf(stderr,"could not write pidfile\n"); + goto exiterr; + } + + if ((listen_fd = socket(salisten.ss_family, SOCK_STREAM, 0)) == -1) { + perror("socket: "); + goto exiterr; + } + + if ((salisten.ss_family == AF_INET6) && setsockopt(listen_fd, IPPROTO_IPV6, IPV6_V6ONLY, &ipv6_only, sizeof(ipv6_only)) == -1) + { + perror("setsockopt (IPV6_ONLY): "); + goto exiterr; + } + + if (setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)) == -1) + { + perror("setsockopt (SO_REUSEADDR): "); + goto exiterr; + } + + //Mark that this socket can be used for transparent proxying + //This allows the socket to accept connections for non-local IPs + if (params.proxy_type==CONN_TYPE_TRANSPARENT) + { + if (setsockopt(listen_fd, SOL_IP, IP_TRANSPARENT, &yes, sizeof(yes)) == -1) + { + perror("setsockopt (IP_TRANSPARENT): "); + goto exiterr; + } + } + + if (!set_socket_buffers(listen_fd, params.local_rcvbuf, params.local_sndbuf)) + goto exiterr; + if (!params.local_rcvbuf) + { + // HACK : dont know why but if dont set RCVBUF explicitly RCVBUF of accept()-ed socket can be very large. may be linux bug ? + int v,sz; + sz=sizeof(int); + if (!getsockopt(listen_fd,SOL_SOCKET,SO_RCVBUF,&v,&sz)) + { + v/=2; + setsockopt(listen_fd,SOL_SOCKET,SO_RCVBUF,&v,sizeof(int)); + } + } + + if (bind(listen_fd, (struct sockaddr *)&salisten, salisten_len) == -1) { + perror("bind: "); + goto exiterr; + } + + set_ulimit(); + + if (!droproot()) + { + goto exiterr; + } + + fprintf(stderr,"Running as UID=%u GID=%u\n",getuid(),getgid()); + + if (listen(listen_fd, BACKLOG) == -1) { + perror("listen: "); + goto exiterr; + } + + + + //splice() causes the process to receive the SIGPIPE-signal if one part (for + //example a socket) is closed during splice(). I would rather have splice() + //fail and return -1, so blocking SIGPIPE. + if (block_sigpipe() == -1) { + fprintf(stderr, "Could not block SIGPIPE signal\n"); + goto exiterr; + } + + fprintf(stderr, "Will listen to port %d\n", params.port); + fprintf(stderr, params.proxy_type==CONN_TYPE_SOCKS ? "socks mode\n" : "transparent proxy mode\n"); + if (!params.tamper) fprintf(stderr, "TCP proxy mode (no tampering)\n"); + + signal(SIGHUP, onhup); + + retval = event_loop(listen_fd); + + close(listen_fd); + cleanup_params(); + + fprintf(stderr, "Will exit\n"); + + return retval < 0 ? EXIT_FAILURE : EXIT_SUCCESS; + +exiterr: + if (listen_fd!=-1) close(listen_fd); + cleanup_params(); + return EXIT_FAILURE; +} diff --git a/tpws/tpws.h b/tpws/tpws.h new file mode 100644 index 0000000..1e32c87 --- /dev/null +++ b/tpws/tpws.h @@ -0,0 +1,3 @@ +#pragma once + +void dohup(); diff --git a/tpws/tpws_conn.c b/tpws/tpws_conn.c new file mode 100644 index 0000000..7c9b780 --- /dev/null +++ b/tpws/tpws_conn.c @@ -0,0 +1,1381 @@ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "tpws.h" +#include "tpws_conn.h" +#include "tamper.h" +#include "params.h" +#include "socks.h" + +#ifndef IP6T_SO_ORIGINAL_DST + #define IP6T_SO_ORIGINAL_DST 80 +#endif + +// keep separate legs counter. counting every time thousands of legs can consume cpu +static int legs_local, legs_remote; +static void count_legs(struct tailhead *conn_list) +{ + tproxy_conn_t *conn = NULL; + + legs_local = legs_remote = 0; + TAILQ_FOREACH(conn, conn_list, conn_ptrs) + conn->remote ? legs_remote++ : legs_local++; + +} +static void print_legs() +{ + VPRINT("Legs : local:%d remote:%d", legs_local, legs_remote) +} + + +static bool socks5_send_rep(int fd,uint8_t rep) +{ + s5_rep s5rep; + memset(&s5rep,0,sizeof(s5rep)); + s5rep.ver = 5; + s5rep.rep = rep; + s5rep.atyp = S5_ATYP_IP4; + return send(fd,&s5rep,sizeof(s5rep),MSG_DONTWAIT)==sizeof(s5rep); +} +static bool socks5_send_rep_errno(int fd,int errn) +{ + uint8_t rep; + switch(errn) + { + case 0: + rep=S5_REP_OK; break; + case ECONNREFUSED: + rep=S5_REP_CONN_REFUSED; break; + case ENETUNREACH: + rep=S5_REP_NETWORK_UNREACHABLE; break; + case ETIMEDOUT: + case EHOSTUNREACH: + rep=S5_REP_HOST_UNREACHABLE; break; + default: + rep=S5_REP_GENERAL_FAILURE; + } + return socks5_send_rep(fd,rep); +} +static bool socks4_send_rep(int fd, uint8_t rep) +{ + s4_rep s4rep; + memset(&s4rep, 0, sizeof(s4rep)); + s4rep.rep = rep; + return send(fd, &s4rep, sizeof(s4rep), MSG_DONTWAIT) == sizeof(s4rep); +} +static bool socks4_send_rep_errno(int fd, int errn) +{ + return socks4_send_rep(fd, errn ? S4_REP_FAILED : S4_REP_OK); +} +static bool socks_send_rep(uint8_t ver, int fd, uint8_t rep5) +{ + return ver==5 ? socks5_send_rep(fd, rep5) : socks4_send_rep(fd, rep5 ? S4_REP_FAILED : S4_REP_OK); +} +static bool socks_send_rep_errno(uint8_t ver, int fd, int errn) +{ + return ver==5 ? socks5_send_rep_errno(fd,errn) : socks4_send_rep_errno(fd, errn); +} +static bool proxy_remote_conn_ack(tproxy_conn_t *conn) +{ + // if proxy mode acknowledge connection request + // conn = remote. conn->partner = local + if (!conn->remote || !conn->partner) return false; + bool bres = true; + switch(conn->partner->conn_type) + { + case CONN_TYPE_SOCKS: + if (conn->partner->socks_state==S_WAIT_CONNECTION) + { + int errn; + socklen_t optlen = sizeof(errn); + if(getsockopt(conn->fd, SOL_SOCKET, SO_ERROR, &errn, &optlen) == -1) + errn=errno; + conn->partner->socks_state=S_TCP; + bres = socks_send_rep_errno(conn->partner->socks_ver,conn->partner->fd, errn); + DBGPRINT("socks connection acknowledgement. bres=%d remote_errn=%d remote_fd=%d local_fd=%d",bres,errn,conn->fd,conn->partner->fd) + } + break; + } + return bres; +} + + + +static bool send_buffer_create(send_buffer_t *sb, char *data, size_t len) +{ + if (sb->data) + { + fprintf(stderr,"FATAL : send_buffer_create but buffer is not empty\n"); + exit(1); + } + sb->data = malloc(len); + if (!sb->data) + { + DBGPRINT("send_buffer_create failed. errno=%d",errno) + return false; + } + if (data) memcpy(sb->data,data,len); + sb->len = len; + sb->pos = 0; + return true; +} +static bool send_buffer_free(send_buffer_t *sb) +{ + if (sb->data) + { + free(sb->data); + sb->data = NULL; + } +} +static void send_buffers_free(send_buffer_t *sb_array, int count) +{ + for (int i=0;iwr_buf,sizeof(conn->wr_buf)/sizeof(conn->wr_buf[0])); +} +static bool send_buffer_present(send_buffer_t *sb) +{ + return !!sb->data; +} +static bool send_buffers_present(send_buffer_t *sb_array, int count) +{ + for(int i=0;idata + sb->pos, sb->len - sb->pos, 0); + DBGPRINT("send_buffer_send len=%zu pos=%zu wr=%zd err=%d",sb->len,sb->pos,wr,errno) + if (wr>0) + { + sb->pos += wr; + if (sb->pos >= sb->len) + { + send_buffer_free(sb); + } + } + else if (wr<0 && errno==EAGAIN) wr=0; + + return wr; +} +static ssize_t send_buffers_send(send_buffer_t *sb_array, int count, int fd, size_t *real_wr) +{ + ssize_t wr=0,twr=0; + + for (int i=0;iconn_type==CONN_TYPE_SOCKS && conn->socks_state!=S_TCP); +} + +static bool conn_partner_alive(tproxy_conn_t *conn) +{ + return conn->partner && conn->partner->state!=CONN_CLOSED; +} +static bool conn_buffers_present(tproxy_conn_t *conn) +{ + return send_buffers_present(conn->wr_buf,sizeof(conn->wr_buf)/sizeof(conn->wr_buf[0])); +} +static ssize_t conn_buffers_send(tproxy_conn_t *conn) +{ + size_t wr,real_twr; + wr = send_buffers_send(conn->wr_buf,sizeof(conn->wr_buf)/sizeof(conn->wr_buf[0]), conn->fd, &real_twr); + conn->twr += real_twr; + return wr; +} +static bool conn_has_unsent(tproxy_conn_t *conn) +{ + return conn->wr_unsent || conn_buffers_present(conn); +} +static int conn_bytes_unread(tproxy_conn_t *conn) +{ + int numbytes=-1; + ioctl(conn->fd, FIONREAD, &numbytes)!=-1; + return numbytes; +} +static bool conn_has_unsent_pair(tproxy_conn_t *conn) +{ + return conn_has_unsent(conn) || (conn_partner_alive(conn) && conn_has_unsent(conn->partner)); +} + + +static ssize_t send_or_buffer(send_buffer_t *sb, int fd, char *buf, size_t len) +{ + ssize_t wr=0; + if (len) + { + wr = send(fd, buf, len, 0); + if (wr<0 && errno==EAGAIN) wr=0; + if (wr>=0 && wrsin6_addr.s6_addr,"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff",12); +} +static bool mappedcmp(const struct sockaddr_in *sa1,const struct sockaddr_in6 *sa2) +{ + return ismapped(sa2) && !memcmp(sa2->sin6_addr.s6_addr+12,&sa1->sin_addr.s_addr,4); +} +static bool sacmp(const struct sockaddr *sa1,const struct sockaddr *sa2) +{ + return sa1->sa_family==AF_INET && sa2->sa_family==AF_INET && !memcmp(&((struct sockaddr_in*)sa1)->sin_addr,&((struct sockaddr_in*)sa2)->sin_addr,sizeof(struct in_addr)) || + sa1->sa_family==AF_INET6 && sa2->sa_family==AF_INET6 && !memcmp(&((struct sockaddr_in6*)sa1)->sin6_addr,&((struct sockaddr_in6*)sa2)->sin6_addr,sizeof(struct in6_addr)) || + sa1->sa_family==AF_INET && sa2->sa_family==AF_INET6 && mappedcmp((struct sockaddr_in*)sa1,(struct sockaddr_in6*)sa2) || + sa1->sa_family==AF_INET6 && sa2->sa_family==AF_INET && mappedcmp((struct sockaddr_in*)sa2,(struct sockaddr_in6*)sa1); +} +static uint16_t saport(const struct sockaddr *sa) +{ + return htons(sa->sa_family==AF_INET ? ((struct sockaddr_in*)sa)->sin_port : + sa->sa_family==AF_INET6 ? ((struct sockaddr_in6*)sa)->sin6_port : 0); +} +// -1 = error, 0 = not local, 1 = local +static bool check_local_ip(const struct sockaddr *saddr) +{ + struct ifaddrs *addrs,*a; + + if (getifaddrs(&addrs)<0) return false; + a = addrs; + + bool bres=false; + while (a) + { + if (a->ifa_addr && sacmp(a->ifa_addr,saddr)) + { + bres=true; + break; + } + a = a->ifa_next; + } + + freeifaddrs(addrs); + return bres; +} +static void print_addrinfo(const struct addrinfo *ai) +{ + char str[64]; + while (ai) + { + switch (ai->ai_family) + { + case AF_INET: + if (inet_ntop(ai->ai_family, &((struct sockaddr_in*)ai->ai_addr)->sin_addr, str, sizeof(str))) + printf("%s\n", str); + break; + case AF_INET6: + if (inet_ntop(ai->ai_family, &((struct sockaddr_in6*)ai->ai_addr)->sin6_addr, str, sizeof(str))) + printf( "%s\n", str); + break; + } + ai = ai->ai_next; + } +} +static void print_sockaddr(const struct sockaddr *sa) +{ + char str[64]; + switch (sa->sa_family) + { + case AF_INET: + if (inet_ntop(sa->sa_family, &((struct sockaddr_in*)sa)->sin_addr, str, sizeof(str))) + printf("%s:%d", str, ntohs(((struct sockaddr_in*)sa)->sin_port)); + break; + case AF_INET6: + if (inet_ntop(sa->sa_family, &((struct sockaddr_in6*)sa)->sin6_addr, str, sizeof(str))) + printf( "%s:%d", str, ntohs(((struct sockaddr_in6*)sa)->sin6_port)); + break; + default: + printf("UNKNOWN_FAMILY_%d",sa->sa_family); + } +} + + +static void dbgprint_socket_buffers(int fd) +{ + if (params.debug>=2) + { + int v,sz; + sz=sizeof(int); + if (!getsockopt(fd,SOL_SOCKET,SO_RCVBUF,&v,&sz)) + DBGPRINT("fd=%d SO_RCVBUF=%d",fd,v) + sz=sizeof(int); + if (!getsockopt(fd,SOL_SOCKET,SO_SNDBUF,&v,&sz)) + DBGPRINT("fd=%d SO_SNDBUF=%d",fd,v) + } +} + +bool set_socket_buffers(int fd, int rcvbuf, int sndbuf) +{ + DBGPRINT("set_socket_buffers fd=%d rcvbuf=%d sndbuf=%d",fd,rcvbuf,sndbuf) + if (rcvbuf && setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &rcvbuf, sizeof(int)) <0) + { + perror("setsockopt (SO_RCVBUF): "); + close(fd); + return false; + } + if (sndbuf && setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &sndbuf, sizeof(int)) <0) + { + perror("setsockopt (SO_SNDBUF): "); + close(fd); + return false; + } + dbgprint_socket_buffers(fd); + return true; +} + +//Createas a socket and initiates the connection to the host specified by +//remote_addr. +//Returns 0 if something fails, >0 on success (socket fd). +static int connect_remote(const struct sockaddr *remote_addr) +{ + int remote_fd = 0, yes = 1, no = 0; + + //Use NONBLOCK to avoid slow connects affecting the performance of other connections + if((remote_fd = socket(remote_addr->sa_family, SOCK_STREAM | SOCK_NONBLOCK, 0)) < 0){ + perror("socket (connect_remote): "); + return 0; + } + + if(setsockopt(remote_fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)) < 0) + { + perror("setsockopt (SO_REUSEADDR, connect_remote): "); + close(remote_fd); + return 0; + } + if (!set_socket_buffers(remote_fd, params.remote_rcvbuf, params.remote_sndbuf)) + return 0; + if(!set_keepalive(remote_fd)) + { + perror("set_keepalive: "); + close(remote_fd); + return 0; + } + if (setsockopt(remote_fd, IPPROTO_TCP, TCP_NODELAY, params.skip_nodelay ? &no : &yes, sizeof(int)) <0) + { + perror("setsockopt (SO_NODELAY, connect_remote): "); + close(remote_fd); + return 0; + } + if(connect(remote_fd, remote_addr, remote_addr->sa_family == AF_INET ? sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6)) < 0) + { + if(errno != EINPROGRESS) + { + perror("connect (connect_remote): "); + close(remote_fd); + return 0; + } + } + DBGPRINT("Connecting remote fd=%d",remote_fd) + + return remote_fd; +} + +//Store the original destination address in remote_addr +//Return 0 on success, <0 on failure +static bool get_dest_addr(int sockfd, struct sockaddr_storage *orig_dst) +{ + char orig_dst_str[INET6_ADDRSTRLEN]; + socklen_t addrlen = sizeof(*orig_dst); + int r; + + memset(orig_dst, 0, addrlen); + + //For UDP transparent proxying: + //Set IP_RECVORIGDSTADDR socket option for getting the original + //destination of a datagram + + // DNAT + r=getsockopt(sockfd, SOL_IP, SO_ORIGINAL_DST, (struct sockaddr*) orig_dst, &addrlen); + if (r<0) + r = getsockopt(sockfd, SOL_IPV6, IP6T_SO_ORIGINAL_DST, (struct sockaddr*) orig_dst, &addrlen); + if (r<0) + { + fprintf(stderr,"both SO_ORIGINAL_DST and IP6T_SO_ORIGINAL_DST failed !\n"); + // TPROXY : socket is bound to original destination + r=getsockname(sockfd, (struct sockaddr*) orig_dst, &addrlen); + if (r<0) + { + perror("getsockname: "); + return false; + } + } + if (orig_dst->ss_family == AF_INET) + { + inet_ntop(AF_INET, &(((struct sockaddr_in*) orig_dst)->sin_addr), orig_dst_str, INET_ADDRSTRLEN); + VPRINT("Original destination for socket fd=%d : %s:%d", sockfd,orig_dst_str, htons(((struct sockaddr_in*) orig_dst)->sin_port)) + } + else if (orig_dst->ss_family == AF_INET6) + { + inet_ntop(AF_INET6,&(((struct sockaddr_in6*) orig_dst)->sin6_addr), orig_dst_str, INET6_ADDRSTRLEN); + VPRINT("Original destination for socket fd=%d : [%s]:%d", sockfd,orig_dst_str, htons(((struct sockaddr_in6*) orig_dst)->sin6_port)) + } + return true; +} + +//Free resources occupied by this connection +static void free_conn(tproxy_conn_t *conn) +{ + if (!conn) return; + if (conn->fd) close(conn->fd); + if (conn->splice_pipe[0]) + { + close(conn->splice_pipe[0]); + close(conn->splice_pipe[1]); + } + conn_free_buffers(conn); + if (conn->partner) conn->partner->partner=NULL; + free(conn); +} +static tproxy_conn_t *new_conn(int fd, bool remote) +{ + tproxy_conn_t *conn; + + //Create connection object and fill in information + if((conn = (tproxy_conn_t*) malloc(sizeof(tproxy_conn_t))) == NULL) + { + fprintf(stderr, "Could not allocate memory for connection\n"); + return NULL; + } + + memset(conn, 0, sizeof(tproxy_conn_t)); + conn->state = CONN_UNAVAILABLE; + conn->fd = fd; + conn->remote = remote; + + // if dont tamper - both legs are spliced, create 2 pipes + // otherwise create pipe only in local leg + if((!params.tamper || !remote) && pipe2(conn->splice_pipe, O_NONBLOCK) != 0) + { + fprintf(stderr, "Could not create the splice pipe\n"); + free_conn(conn); + return NULL; + } + + return conn; +} + +static bool epoll_set(tproxy_conn_t *conn, uint32_t events) +{ + struct epoll_event ev; + + memset(&ev, 0, sizeof(ev)); + ev.events = events; + ev.data.ptr = (void*) conn; + DBGPRINT("epoll_set fd=%d events=%08X",conn->fd,events); + if(epoll_ctl(conn->efd, EPOLL_CTL_MOD, conn->fd, &ev)==-1 && + epoll_ctl(conn->efd, EPOLL_CTL_ADD, conn->fd, &ev)==-1) + { + perror("epoll_ctl (add/mod)"); + return false; + } + return true; +} +static bool epoll_del(tproxy_conn_t *conn) +{ + struct epoll_event ev; + + memset(&ev, 0, sizeof(ev)); + + DBGPRINT("epoll_del fd=%d",conn->fd); + if(epoll_ctl(conn->efd, EPOLL_CTL_DEL, conn->fd, &ev)==-1) + { + perror("epoll_ctl (del)"); + return false; + } + return true; +} + +static bool epoll_update_flow(tproxy_conn_t *conn) +{ + if (conn->bFlowInPrev==conn->bFlowIn && conn->bFlowOutPrev==conn->bFlowOut && conn->bPrevRdhup==(conn->state==CONN_RDHUP)) + return true; // unchanged, no need to syscall + uint32_t evtmask = (conn->state==CONN_RDHUP ? 0 : EPOLLRDHUP)|(conn->bFlowIn?EPOLLIN:0)|(conn->bFlowOut?EPOLLOUT:0); + if (!epoll_set(conn, evtmask)) + return false; + DBGPRINT("SET FLOW fd=%d to in=%d out=%d state_rdhup=%d",conn->fd,conn->bFlowIn,conn->bFlowOut,conn->state==CONN_RDHUP) + conn->bFlowInPrev = conn->bFlowIn; + conn->bFlowOutPrev = conn->bFlowOut; + conn->bPrevRdhup = (conn->state==CONN_RDHUP); + return true; +} +static bool epoll_set_flow(tproxy_conn_t *conn, bool bFlowIn, bool bFlowOut) +{ + conn->bFlowIn = bFlowIn; + conn->bFlowOut = bFlowOut; + return epoll_update_flow(conn); +} + +//Acquires information, initiates a connect and initialises a new connection +//object. Return NULL if anything fails, pointer to object otherwise +static tproxy_conn_t* add_tcp_connection(int efd, struct tailhead *conn_list, + int local_fd, uint16_t listen_port, conn_type_t proxy_type) +{ + struct sockaddr_storage orig_dst; + tproxy_conn_t *conn; + int remote_fd=0; + int yes=1; + + if (proxy_type==CONN_TYPE_TRANSPARENT) + { + if(!get_dest_addr(local_fd, &orig_dst)) + { + fprintf(stderr, "Could not get destination address\n"); + close(local_fd); + return NULL; + } + + if (check_local_ip((struct sockaddr*)&orig_dst) && saport((struct sockaddr*)&orig_dst)==listen_port) + { + VPRINT("Dropping connection to local address to the same port to avoid loop") + close(local_fd); + return NULL; + } + } + + // socket buffers inherited from listen_fd + dbgprint_socket_buffers(local_fd); + + if(!set_keepalive(local_fd)) + { + perror("set_keepalive: "); + close(local_fd); + return 0; + } + + if (proxy_type==CONN_TYPE_TRANSPARENT) + { + if(!(remote_fd = connect_remote((struct sockaddr *)&orig_dst))) + { + fprintf(stderr, "Failed to connect\n"); + close(local_fd); + return NULL; + } + } + + if(!(conn = new_conn(local_fd, false))) + { + if (remote_fd) close(remote_fd); + close(local_fd); + return NULL; + } + conn->conn_type = proxy_type; // only local connection has proxy_type. remote is always in tcp mode + conn->state = CONN_AVAILABLE; // accepted connection is immediately available + conn->efd = efd; + + if (proxy_type==CONN_TYPE_TRANSPARENT) + { + if(!(conn->partner = new_conn(remote_fd, true))) + { + free_conn(conn); + close(remote_fd); + return NULL; + } + conn->partner->partner = conn; + conn->partner->efd = efd; + + //remote_fd is connecting. Non-blocking connects are signaled as done by + //socket being marked as ready for writing + if (!epoll_set(conn->partner, EPOLLOUT)) + { + free_conn(conn->partner); + free_conn(conn); + return NULL; + } + } + + //Transparent proxy mode : + // Local socket can be closed while waiting for connection attempt. I need + // to detect this when waiting for connect() to complete. However, I dont + // want to get EPOLLIN-events, as I dont want to receive any data before + // remote connection is established + //Proxy mode : I need to service proxy protocol + // remote connection not started until proxy handshake is complete + + if (!epoll_set(conn, proxy_type==CONN_TYPE_TRANSPARENT ? EPOLLRDHUP : (EPOLLIN|EPOLLRDHUP))) + { + free_conn(conn->partner); + free_conn(conn); + return NULL; + } + + TAILQ_INSERT_HEAD(conn_list, conn, conn_ptrs); + legs_local++; + if (conn->partner) + { + TAILQ_INSERT_HEAD(conn_list, conn->partner, conn_ptrs); + legs_remote++; + } + return conn; +} + +//Checks if a connection attempt was successful or not +//Returns true if successfull, false if not +static bool check_connection_attempt(tproxy_conn_t *conn, int efd) +{ + int fd_flags = 0; + int errn = 0; + socklen_t optlen = sizeof(errn); + + if (conn->state!=CONN_UNAVAILABLE || !conn->remote) + { + // locals are connected since accept + // remote need to be checked only once + return true; + } + + if (!conn_partner_alive(conn)) + { + // local leg died ? + VPRINT("check_connection_attempt : fd=%d (remote) : local leg died. failing this connection attempt.", conn->fd) + return false; + } + + // check the connection was sucessfull. it means its not in in SO_ERROR state + if(getsockopt(conn->fd, SOL_SOCKET, SO_ERROR, &errn, &optlen) == -1) + { + perror("getsockopt (SO_ERROR)"); + return false; + } + if (!errn) + { + VPRINT("Socket fd=%d (remote) connected", conn->fd) + if (!epoll_set_flow(conn, true, false) || !epoll_set_flow(conn->partner, true, false)) + return false; + conn->state = CONN_AVAILABLE; + } + return proxy_remote_conn_ack(conn) && !errn; +} + + + + +static bool epoll_set_flow_pair(tproxy_conn_t *conn) +{ + bool bHasUnsent = conn_has_unsent(conn); + bool bHasUnsentPartner = conn_partner_alive(conn) ? conn_has_unsent(conn->partner) : false; + + DBGPRINT("epoll_set_flow_pair fd=%d remote=%d partner_fd=%d bHasUnsent=%d bHasUnsentPartner=%d state_rdhup=%d", + conn->fd , conn->remote, conn_partner_alive(conn) ? conn->partner->fd : 0, bHasUnsent, bHasUnsentPartner, conn->state==CONN_RDHUP) + if (!epoll_set_flow(conn, !bHasUnsentPartner && (conn->state!=CONN_RDHUP), bHasUnsent || conn->state==CONN_RDHUP)) + return false; + if (conn_partner_alive(conn)) + { + if (!epoll_set_flow(conn->partner, !bHasUnsent && (conn->partner->state!=CONN_RDHUP), bHasUnsentPartner || conn->partner->state==CONN_RDHUP)) + return false; + } + return true; +} + +static bool handle_unsent(tproxy_conn_t *conn) +{ + ssize_t wr=0,twr=0; + + DBGPRINT("+handle_unsent, fd=%d has_unsent=%d has_unsent_partner=%d",conn->fd,conn_has_unsent(conn),conn_partner_alive(conn) ? conn_has_unsent(conn->partner) : false) + + if (conn->wr_unsent) + { + wr = splice(conn->splice_pipe[0], NULL, conn->fd, NULL, conn->wr_unsent, SPLICE_F_MOVE | SPLICE_F_NONBLOCK); + DBGPRINT("splice unsent=%zd wr=%zd err=%d",conn->wr_unsent,wr,errno) + if (wr<0) + { + if (errno==EAGAIN) wr=0; + else return false; + } + twr += wr; + conn->twr += wr; + conn->wr_unsent -= wr; + } + if (!conn->wr_unsent && conn_buffers_present(conn)) + { + wr=conn_buffers_send(conn); + DBGPRINT("conn_buffers_send wr=%zd",wr) + if (wr<0) return false; + twr += wr; + } + return epoll_set_flow_pair(conn); +} + + +bool proxy_mode_connect_remote(const struct sockaddr *sa, tproxy_conn_t *conn, struct tailhead *conn_list) +{ + int remote_fd; + + if (params.debug>=1) + { + printf("socks target for fd=%d is : ", conn->fd); + print_sockaddr(sa); + printf("\n"); + } + if (check_local_ip((struct sockaddr *)sa)) + { + VPRINT("Dropping connection to local address for security reasons") + socks_send_rep(conn->socks_ver, conn->fd, S5_REP_NOT_ALLOWED_BY_RULESET); + return false; + } + + if (!(remote_fd = connect_remote(sa))) + { + fprintf(stderr, "socks failed to connect (1) errno=%d\n", errno); + socks_send_rep_errno(conn->socks_ver, conn->fd, errno); + return false; + } + if (!(conn->partner = new_conn(remote_fd, true))) + { + close(remote_fd); + fprintf(stderr, "socks out-of-memory (1)\n"); + socks_send_rep(conn->socks_ver, conn->fd, S5_REP_GENERAL_FAILURE); + return false; + } + conn->partner->partner = conn; + conn->partner->efd = conn->efd; + if (!epoll_set(conn->partner, EPOLLOUT)) + { + fprintf(stderr, "socks epoll_set error %d\n", errno); + free_conn(conn->partner); + conn->partner = NULL; + socks_send_rep(conn->socks_ver, conn->fd, S5_REP_GENERAL_FAILURE); + return false; + } + TAILQ_INSERT_HEAD(conn_list, conn->partner, conn_ptrs); + legs_remote++; + print_legs(); + DBGPRINT("socks connecting") + conn->socks_state = S_WAIT_CONNECTION; + return true; +} + +static bool handle_proxy_mode(tproxy_conn_t *conn, struct tailhead *conn_list) +{ + // To simplify things I dont care about buffering. If message splits, I just hang up + // in proxy mode messages are short. they can be split only intentionally. all normal programs send them in one packet + + ssize_t rd,wr; + char buf[sizeof(s5_req)]; // s5_req - the largest possible req + struct sockaddr_storage ss; + + // receive proxy control message + rd=recv(conn->fd, buf, sizeof(buf), MSG_DONTWAIT); + DBGPRINT("handle_proxy_mode rd=%zd",rd) + if (rd<1) return false; // hangup + switch(conn->conn_type) + { + case CONN_TYPE_SOCKS: + switch(conn->socks_state) + { + case S_WAIT_HANDSHAKE: + DBGPRINT("S_WAIT_HANDSHAKE") + if (buf[0] != 5 && buf[0] != 4) return false; // unknown socks version + conn->socks_ver = buf[0]; + DBGPRINT("socks version %u", conn->socks_ver) + if (conn->socks_ver==5) + { + s5_handshake *m = (s5_handshake*)buf; + s5_handshake_ack ack; + uint8_t k; + + ack.ver=5; + if (!S5_REQ_HANDHSHAKE_VALID(m,rd)) + { + DBGPRINT("socks5 proxy handshake invalid") + return false; + } + for (k=0;knmethods;k++) if (m->methods[k]==S5_AUTH_NONE) break; + if (k>=m->nmethods) + { + DBGPRINT("socks5 client wants authentication but we dont support") + ack.method=S5_AUTH_UNACCEPTABLE; + wr=send(conn->fd,&ack,sizeof(ack),MSG_DONTWAIT); + return false; + } + DBGPRINT("socks5 recv valid handshake") + ack.method=S5_AUTH_NONE; + wr=send(conn->fd,&ack,sizeof(ack),MSG_DONTWAIT); + if (wr!=sizeof(ack)) + { + DBGPRINT("socks5 handshake ack send error. wr=%zd errno=%d",wr,errno) + return false; + } + DBGPRINT("socks5 send handshake ack OK") + conn->socks_state=S_WAIT_REQUEST; + return true; + } + else + { + // socks4 does not have separate handshake phase. it starts with connect request + // ipv6 and domain resolving are not supported + s4_req *m = (s4_req*)buf; + if (!S4_REQ_HEADER_VALID(m, rd)) + { + DBGPRINT("socks4 request invalid") + return false; + } + if (m->cmd!=S4_CMD_CONNECT) + { + // BIND is not supported + DBGPRINT("socks4 unsupported command %02X", m->cmd) + socks4_send_rep(conn->fd, S4_REP_FAILED); + return false; + } + if (!S4_REQ_CONNECT_VALID(m, rd)) + { + DBGPRINT("socks4 connect request invalid") + socks4_send_rep(conn->fd, S4_REP_FAILED); + return false; + } + if (!m->port) + { + DBGPRINT("socks4 zero port") + socks4_send_rep(conn->fd, S4_REP_FAILED); + return false; + } + ss.ss_family = AF_INET; + ((struct sockaddr_in*)&ss)->sin_port = m->port; + ((struct sockaddr_in*)&ss)->sin_addr.s_addr = m->ip; + return proxy_mode_connect_remote((struct sockaddr *)&ss, conn, conn_list); + } + break; + case S_WAIT_REQUEST: + DBGPRINT("S_WAIT_REQUEST") + { + s5_req *m = (s5_req*)buf; + char str[64]; + + if (!S5_REQ_HEADER_VALID(m,rd)) + { + DBGPRINT("socks5 request invalid") + return false; + } + if (m->cmd!=S5_CMD_CONNECT) + { + // BIND and UDP are not supported + DBGPRINT("socks5 unsupported command %02X", m->cmd) + socks5_send_rep(conn->fd,S5_REP_COMMAND_NOT_SUPPORTED); + return false; + } + if (!S5_REQ_CONNECT_VALID(m,rd)) + { + DBGPRINT("socks5 connect request invalid") + return false; + } + DBGPRINT("socks5 recv valid connect request") + switch(m->atyp) + { + case S5_ATYP_IP4: + ss.ss_family = AF_INET; + ((struct sockaddr_in*)&ss)->sin_port = m->d4.port; + ((struct sockaddr_in*)&ss)->sin_addr = m->d4.addr; + break; + case S5_ATYP_IP6: + ss.ss_family = AF_INET6; + ((struct sockaddr_in6*)&ss)->sin6_port = m->d6.port; + ((struct sockaddr_in6*)&ss)->sin6_addr = m->d6.addr; + ((struct sockaddr_in6*)&ss)->sin6_flowinfo = 0; + ((struct sockaddr_in6*)&ss)->sin6_scope_id = 0; + break; + case S5_ATYP_DOM: + // NOTE : resolving is blocking. do you want it really ? + { + struct addrinfo *ai,hints; + char sdom[256]; + int r; + uint16_t port; + char sport[6]; + + if (params.no_resolve) + { + DBGPRINT("socks5 hostname resolving disabled") + socks5_send_rep(conn->fd,S5_REP_NOT_ALLOWED_BY_RULESET); + return false; + } + port=S5_PORT_FROM_DD(m,rd); + if (!port) + { + DBGPRINT("socks5 no port is given") + socks5_send_rep(conn->fd,S5_REP_HOST_UNREACHABLE); + return false; + } + snprintf(sport,sizeof(sport),"%u",port); + memcpy(sdom,m->dd.domport,m->dd.len); + sdom[m->dd.len] = '\0'; + DBGPRINT("socks5 resolving hostname '%s' port '%s'",sdom,sport) + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_socktype = SOCK_STREAM; + r=getaddrinfo(sdom,sport,&hints,&ai); + if (r) + { + DBGPRINT("socks5 getaddrinfo error %d",r) + socks5_send_rep(conn->fd,S5_REP_HOST_UNREACHABLE); + return false; + } + if (params.debug>=2) + { + printf("socks5 hostname resolved to :\n"); + print_addrinfo(ai); + } + memcpy(&ss,ai->ai_addr,ai->ai_addrlen); + freeaddrinfo(ai); + } + break; + default: + return false; // should not be here. S5_REQ_CONNECT_VALID checks for valid atyp + + } + return proxy_mode_connect_remote((struct sockaddr *)&ss,conn,conn_list); + } + break; + case S_WAIT_CONNECTION: + DBGPRINT("socks received message while in S_WAIT_CONNECTION. hanging up") + break; + } + break; + } + return false; +} + +#define RD_BLOCK_SIZE 65536 +#define MAX_WASTE (1024*1024) +static bool handle_epoll(tproxy_conn_t *conn, struct tailhead *conn_list, uint32_t evt) +{ + int numbytes; + ssize_t rd = 0, wr = 0; + size_t bs; + + + DBGPRINT("+handle_epoll") + + if (!conn_in_tcp_mode(conn)) + { + if (!(evt & EPOLLIN)) + return true; // nothing to read + return handle_proxy_mode(conn,conn_list); + } + + if (!handle_unsent(conn)) + return false; // error + if (!conn_partner_alive(conn) && !conn_has_unsent(conn)) + return false; // when no partner, we only waste read and send unsent + + if (!(evt & EPOLLIN)) + return true; // nothing to read + + if (!conn_partner_alive(conn)) + { + // throw it to a black hole + char waste[65070]; + ssize_t trd=0; + + while((rd=recv(conn->fd, waste, sizeof(waste), MSG_DONTWAIT))>0 && trdtrd+=rd; + } + DBGPRINT("wasted recv=%zd all_rd=%zd err=%d",rd,trd,errno) + return true; + } + + // do not receive new until old is sent + if (conn_has_unsent(conn->partner)) + return true; + + bool oom=false; + + numbytes=conn_bytes_unread(conn); + DBGPRINT("numbytes=%d",numbytes) + if (numbytes>0) + { + if (!params.tamper || conn->remote) + { + // incoming data from remote leg we splice without touching + // pipe is in the local leg, so its in conn->partner->splice_pipe + // if we dont tamper - splice both legs + + rd = splice(conn->fd, NULL, conn->partner->splice_pipe[1], NULL, SPLICE_LEN, SPLICE_F_MOVE | SPLICE_F_NONBLOCK); + DBGPRINT("splice fd=%d remote=%d len=%d rd=%zd err=%d",conn->fd,conn->remote,SPLICE_LEN,rd,errno) + if (rd<0 && errno==EAGAIN) rd=0; + if (rd>0) + { + conn->trd += rd; + conn->partner->wr_unsent += rd; + wr = splice(conn->partner->splice_pipe[0], NULL, conn->partner->fd, NULL, conn->partner->wr_unsent, SPLICE_F_MOVE | SPLICE_F_NONBLOCK); + DBGPRINT("splice fd=%d remote=%d wr=%zd err=%d",conn->partner->fd,conn->partner->remote,wr,errno) + if (wr<0 && errno==EAGAIN) wr=0; + if (wr>0) + { + conn->partner->wr_unsent -= wr; + conn->partner->twr += wr; + } + } + } + else + { + // incoming data from local leg + char buf[RD_BLOCK_SIZE + 4]; + + rd = recv(conn->fd, buf, RD_BLOCK_SIZE, MSG_DONTWAIT); + DBGPRINT("recv fd=%d rd=%zd err=%d",conn->fd, rd,errno) + if (rd<0 && errno==EAGAIN) rd=0; + if (rd>0) + { + conn->trd+=rd; + + size_t split_pos=0; + + bs = rd; + modify_tcp_segment(buf,sizeof(buf),&bs,&split_pos); + + if (split_pos) + { + VPRINT("Splitting at pos %zu", split_pos) + wr = send_or_buffer(conn->partner->wr_buf, conn->partner->fd, buf, split_pos); + DBGPRINT("send_or_buffer(1) fd=%d wr=%zd err=%d",conn->partner->fd,wr,errno) + if (wr >= 0) + { + conn->partner->twr += wr; + wr = send_or_buffer(conn->partner->wr_buf + 1, conn->partner->fd, buf + split_pos, bs - split_pos); + DBGPRINT("send_or_buffer(2) fd=%d wr=%zd err=%d",conn->partner->fd,wr,errno) + if (wr>0) conn->partner->twr += wr; + } + } + else + { + wr = send_or_buffer(conn->partner->wr_buf, conn->partner->fd, buf, bs); + DBGPRINT("send_or_buffer(3) fd=%d wr=%zd err=%d",conn->partner->fd,wr,errno) + if (wr>0) conn->partner->twr += wr; + } + if (wr<0 && errno==ENOMEM) oom=true; + } + } + + if (!epoll_set_flow_pair(conn)) + return false; + } + + DBGPRINT("-handle_epoll rd=%zd wr=%zd",rd,wr) + if (oom) DBGPRINT("handle_epoll: OUT_OF_MEMORY") + + // do not fail if partner fails. + // if partner fails there will be another epoll event with EPOLLHUP or EPOLLERR + return rd>=0 && !oom; +} + +static bool remove_closed_connections(int efd, struct tailhead *close_list) +{ + tproxy_conn_t *conn = NULL; + bool bRemoved = false; + + while (conn = TAILQ_FIRST(close_list)) + { + TAILQ_REMOVE(close_list, conn, conn_ptrs); + + shutdown(conn->fd,SHUT_RDWR); + epoll_del(conn); + VPRINT("Socket fd=%d (partner_fd=%d, remote=%d) closed, connection removed. total_read=%zu total_write=%zu event_count=%d", + conn->fd, conn->partner ? conn->partner->fd : 0, conn->remote, conn->trd, conn->twr, conn->event_count) + if (conn->remote) legs_remote--; else legs_local--; + free_conn(conn); + bRemoved = true; + } + return bRemoved; +} + +// move to close list connection and its partner +static void close_tcp_conn(struct tailhead *conn_list, struct tailhead *close_list, tproxy_conn_t *conn) +{ + if (conn->state != CONN_CLOSED) + { + conn->state = CONN_CLOSED; + TAILQ_REMOVE(conn_list, conn, conn_ptrs); + TAILQ_INSERT_TAIL(close_list, conn, conn_ptrs); + } +} + + +static bool read_all_and_buffer(tproxy_conn_t *conn, int buffer_number) +{ + if (conn_partner_alive(conn)) + { + int numbytes=conn_bytes_unread(conn); + DBGPRINT("read_all_and_buffer(%d) numbytes=%d",buffer_number,numbytes) + if (numbytes>0) + { + if (send_buffer_create(conn->partner->wr_buf+buffer_number, NULL, numbytes)) + { + ssize_t rd = recv(conn->fd, conn->partner->wr_buf[buffer_number].data, numbytes, MSG_DONTWAIT); + if (rd>0) + { + conn->trd+=rd; + conn->partner->wr_buf[buffer_number].len = rd; + + conn->partner->bFlowOut = true; + if (epoll_update_flow(conn->partner)) + return true; + } + send_buffer_free(conn->partner->wr_buf+buffer_number); + } + } + } + return false; +} + + +static bool conn_timed_out(tproxy_conn_t *conn) +{ + if (conn->orphan_since && conn->state==CONN_UNAVAILABLE) + { + time_t timediff = time(NULL) - conn->orphan_since; + return timediff>=params.max_orphan_time; + } + else + return false; +} +static void conn_close_timed_out(struct tailhead *conn_list, struct tailhead *close_list) +{ + tproxy_conn_t *c,*cnext = NULL; + + DBGPRINT("conn_close_timed_out") + + c = TAILQ_FIRST(conn_list); + while(c) + { + cnext = TAILQ_NEXT(c,conn_ptrs); + if (conn_timed_out(c)) + { + DBGPRINT("closing timed out connection: fd=%d remote=%d",c->fd,c->remote) + close_tcp_conn(conn_list,close_list,c); + } + c = cnext; + } +} + +static void conn_close_both(struct tailhead *conn_list, struct tailhead *close_list, tproxy_conn_t *conn) +{ + if (conn_partner_alive(conn)) close_tcp_conn(conn_list,close_list,conn->partner); + close_tcp_conn(conn_list,close_list,conn); +} +static void conn_close_with_partner_check(struct tailhead *conn_list, struct tailhead *close_list, tproxy_conn_t *conn) +{ + close_tcp_conn(conn_list,close_list,conn); + if (conn_partner_alive(conn)) + { + if (!conn_has_unsent(conn->partner)) + close_tcp_conn(conn_list,close_list,conn->partner); + else if (conn->partner->remote && conn->partner->state==CONN_UNAVAILABLE && params.max_orphan_time) + // time out only remote legs that are not connected yet + conn->partner->orphan_since = time(NULL); + } +} + +int event_loop(int listen_fd) +{ + int retval = 0, num_events = 0; + int tmp_fd = 0; //Used to temporarily hold the accepted file descriptor + tproxy_conn_t *conn = NULL; + int efd, i; + struct epoll_event ev, events[MAX_EPOLL_EVENTS]; + struct tailhead conn_list, close_list; + time_t tm,last_timeout_check=0; + + legs_local = legs_remote = 0; + //Initialize queue (remember that TAILQ_HEAD just defines the struct) + TAILQ_INIT(&conn_list); + TAILQ_INIT(&close_list); + + if ((efd = epoll_create(1)) == -1) { + perror("epoll_create"); + return -1; + } + + //Start monitoring listen socket + memset(&ev, 0, sizeof(ev)); + ev.events = EPOLLIN; + //There is only one listen socket, and I want to use ptr in order to have + //easy access to the connections. So if ptr is NULL that means an event on + //listen socket. + ev.data.ptr = NULL; + if (epoll_ctl(efd, EPOLL_CTL_ADD, listen_fd, &ev) == -1) { + perror("epoll_ctl (listen socket)"); + close(efd); + return -1; + } + + while (1) + { + DBGPRINT("epoll_wait") + + if ((num_events = epoll_wait(efd, events, MAX_EPOLL_EVENTS, -1)) == -1) + { + if (errno == EINTR) continue; // system call was interrupted + perror("epoll_wait"); + retval = -1; + break; + } + + dohup(); + + for (i = 0; i < num_events; i++) + { + if (events[i].data.ptr == NULL) + { + DBGPRINT("\nEVENT mask %08X conn=NULL (accept)",events[i].events) + + //Accept new connection + tmp_fd = accept4(listen_fd, NULL, 0, SOCK_NONBLOCK); + if (tmp_fd < 0) + { + fprintf(stderr, "Failed to accept connection\n"); + } + else if (legs_local >= params.maxconn) // each connection has 2 legs - local and remote + { + close(tmp_fd); + VPRINT("Too many local legs : %d", legs_local) + } + else if (!(conn=add_tcp_connection(efd, &conn_list, tmp_fd, params.port, params.proxy_type))) + { + // add_tcp_connection closes fd in case of failure + fprintf(stderr, "Failed to add connection\n"); + } + else + { + print_legs(); + VPRINT("Socket fd=%d (local) connected", conn->fd) + } + } + else + { + conn = (tproxy_conn_t*)events[i].data.ptr; + conn->event_count++; + + DBGPRINT("\nEVENT mask %08X fd=%d remote=%d fd_partner=%d",events[i].events,conn->fd,conn->remote,conn_partner_alive(conn) ? conn->partner->fd : 0) + + if (conn->state != CONN_CLOSED) + { + if (events[i].events & (EPOLLHUP|EPOLLERR)) + { + if (events[i].events & EPOLLERR) DBGPRINT("EPOLLERR") + if (events[i].events & EPOLLHUP) DBGPRINT("EPOLLHUP") + proxy_remote_conn_ack(conn); + read_all_and_buffer(conn,3); + conn_close_with_partner_check(&conn_list,&close_list,conn); + continue; + } + if (events[i].events & EPOLLOUT) + { + if (!check_connection_attempt(conn, efd)) + { + VPRINT("Connection attempt failed for fd=%d", conn->fd) + conn_close_both(&conn_list,&close_list,conn); + continue; + } + } + if (events[i].events & EPOLLRDHUP) + { + DBGPRINT("EPOLLRDHUP") + read_all_and_buffer(conn,2); + + if (conn_has_unsent(conn)) + { + DBGPRINT("conn fd=%d has unsent, not closing", conn->fd) + conn->state = CONN_RDHUP; // only writes + epoll_set_flow(conn,false,true); + } + else + { + DBGPRINT("conn fd=%d has no unsent, closing", conn->fd) + conn_close_with_partner_check(&conn_list,&close_list,conn); + } + continue; + } + + if (events[i].events & (EPOLLIN|EPOLLOUT)) + { + // will not receive this until successful check_connection_attempt() + if (!handle_epoll(conn, &conn_list, events[i].events)) + { + DBGPRINT("handle_epoll false") + conn_close_with_partner_check(&conn_list,&close_list,conn); + continue; + } + } + } + + } + } + tm = time(NULL); + if (last_timeout_check!=tm) + { + // limit whole list lookups to once per second + last_timeout_check=tm; + conn_close_timed_out(&conn_list,&close_list); + } + if (remove_closed_connections(efd, &close_list)) + { + // at least one leg was removed. recount legs + print_legs(); + } + + fflush(stderr); fflush(stdout); // for console messages + } + + close(efd); + + return retval; +} diff --git a/tpws/tpws_conn.h b/tpws/tpws_conn.h new file mode 100644 index 0000000..06a2bbb --- /dev/null +++ b/tpws/tpws_conn.h @@ -0,0 +1,95 @@ +#pragma once + +#include +#include +#include + +#define BACKLOG 10 +#define MAX_EPOLL_EVENTS 64 +#define IP_TRANSPARENT 19 //So that application compiles on OpenWRT +#define SPLICE_LEN 65536 +#define DEFAULT_MAX_CONN 512 +#define DEFAULT_MAX_ORPHAN_TIME 5 + +int event_loop(int listen_fd); + +//Three different states of a connection +enum{ + CONN_UNAVAILABLE=0, // connecting + CONN_AVAILABLE, // operational + CONN_RDHUP, // received RDHUP, only sending unsent buffers. more RDHUPs are blocked + CONN_CLOSED // will be deleted soon +}; +typedef uint8_t conn_state_t; + +// data in a send_buffer can be sent in several stages +// pos indicates size of already sent data +// when pos==len its time to free buffer +struct send_buffer +{ + char *data; + size_t len,pos; +}; +typedef struct send_buffer send_buffer_t; + +enum{ + CONN_TYPE_TRANSPARENT=0, + CONN_TYPE_SOCKS +}; +typedef uint8_t conn_type_t; + +struct tproxy_conn +{ + bool remote; // false - accepted, true - connected + int efd; // epoll fd + int fd; + int splice_pipe[2]; + conn_state_t state; + conn_type_t conn_type; + + struct tproxy_conn *partner; // other leg + time_t orphan_since; + + // socks5 state machine + enum { + S_WAIT_HANDSHAKE=0, + S_WAIT_REQUEST, + S_WAIT_CONNECTION, + S_TCP + } socks_state; + uint8_t socks_ver; + + // these value are used in flow control. we do not use ET (edge triggered) polling + // if we dont disable notifications they will come endlessly until condition becomes false and will eat all cpu time + bool bFlowIn,bFlowOut, bFlowInPrev,bFlowOutPrev, bPrevRdhup; + + // total read,write + size_t trd,twr; + // number of epoll_wait events + unsigned int event_count; + + // connection is either spliced or send/recv + // spliced connection have pipe buffering but also can have send_buffer's + // pipe buffer comes first, then send_buffer's from 0 to countof(wr_buf)-1 + // send/recv connection do not have pipe and wr_unsent is meaningless, always 0 + ssize_t wr_unsent; // unsent bytes in the pipe + // buffer 0 : send before split_pos + // buffer 1 : send after split_pos + // buffer 2 : after RDHUP read all and buffer to the partner + // buffer 3 : after HUP read all and buffer to the partner + // (2 and 3 should not be filled simultaneously, but who knows what can happen. if we have to refill non-empty buffer its FATAL) + // all buffers are sent strictly from 0 to countof(wr_buf)-1 + // buffer cannot be sent if there is unsent data in a lower buffer + struct send_buffer wr_buf[4]; + + //Create the struct which contains ptrs to next/prev element + TAILQ_ENTRY(tproxy_conn) conn_ptrs; +}; +typedef struct tproxy_conn tproxy_conn_t; + +//Define the struct tailhead (code in sys/queue.h is quite intuitive) +//Use tail queue for efficient delete +TAILQ_HEAD(tailhead, tproxy_conn); + + +bool set_socket_buffers(int fd, int rcvbuf, int sndbuf); diff --git a/tpws/uthash.h b/tpws/uthash.h new file mode 100644 index 0000000..f34c1f9 --- /dev/null +++ b/tpws/uthash.h @@ -0,0 +1,1217 @@ +/* +Copyright (c) 2003-2018, Troy D. Hanson http://troydhanson.github.com/uthash/ +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER +OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef UTHASH_H +#define UTHASH_H + +#define UTHASH_VERSION 2.0.2 + +#include /* memcmp, memset, strlen */ +#include /* ptrdiff_t */ +#include /* exit */ + +/* These macros use decltype or the earlier __typeof GNU extension. + As decltype is only available in newer compilers (VS2010 or gcc 4.3+ + when compiling c++ source) this code uses whatever method is needed + or, for VS2008 where neither is available, uses casting workarounds. */ +#if !defined(DECLTYPE) && !defined(NO_DECLTYPE) +#if defined(_MSC_VER) /* MS compiler */ +#if _MSC_VER >= 1600 && defined(__cplusplus) /* VS2010 or newer in C++ mode */ +#define DECLTYPE(x) (decltype(x)) +#else /* VS2008 or older (or VS2010 in C mode) */ +#define NO_DECLTYPE +#endif +#elif defined(__BORLANDC__) || defined(__ICCARM__) || defined(__LCC__) || defined(__WATCOMC__) +#define NO_DECLTYPE +#else /* GNU, Sun and other compilers */ +#define DECLTYPE(x) (__typeof(x)) +#endif +#endif + +#ifdef NO_DECLTYPE +#define DECLTYPE(x) +#define DECLTYPE_ASSIGN(dst,src) \ +do { \ + char **_da_dst = (char**)(&(dst)); \ + *_da_dst = (char*)(src); \ +} while (0) +#else +#define DECLTYPE_ASSIGN(dst,src) \ +do { \ + (dst) = DECLTYPE(dst)(src); \ +} while (0) +#endif + +/* a number of the hash function use uint32_t which isn't defined on Pre VS2010 */ +#if defined(_WIN32) +#if defined(_MSC_VER) && _MSC_VER >= 1600 +#include +#elif defined(__WATCOMC__) || defined(__MINGW32__) || defined(__CYGWIN__) +#include +#else +typedef unsigned int uint32_t; +typedef unsigned char uint8_t; +#endif +#elif defined(__GNUC__) && !defined(__VXWORKS__) +#include +#else +typedef unsigned int uint32_t; +typedef unsigned char uint8_t; +#endif + +#ifndef uthash_malloc +#define uthash_malloc(sz) malloc(sz) /* malloc fcn */ +#endif +#ifndef uthash_free +#define uthash_free(ptr,sz) free(ptr) /* free fcn */ +#endif +#ifndef uthash_bzero +#define uthash_bzero(a,n) memset(a,'\0',n) +#endif +#ifndef uthash_memcmp +#define uthash_memcmp(a,b,n) memcmp(a,b,n) +#endif +#ifndef uthash_strlen +#define uthash_strlen(s) strlen(s) +#endif + +#ifndef uthash_noexpand_fyi +#define uthash_noexpand_fyi(tbl) /* can be defined to log noexpand */ +#endif +#ifndef uthash_expand_fyi +#define uthash_expand_fyi(tbl) /* can be defined to log expands */ +#endif + +#ifndef HASH_NONFATAL_OOM +#define HASH_NONFATAL_OOM 0 +#endif + +#if HASH_NONFATAL_OOM +/* malloc failures can be recovered from */ + +#ifndef uthash_nonfatal_oom +#define uthash_nonfatal_oom(obj) do {} while (0) /* non-fatal OOM error */ +#endif + +#define HASH_RECORD_OOM(oomed) do { (oomed) = 1; } while (0) +#define IF_HASH_NONFATAL_OOM(x) x + +#else +/* malloc failures result in lost memory, hash tables are unusable */ + +#ifndef uthash_fatal +#define uthash_fatal(msg) exit(-1) /* fatal OOM error */ +#endif + +#define HASH_RECORD_OOM(oomed) uthash_fatal("out of memory") +#define IF_HASH_NONFATAL_OOM(x) + +#endif + +/* initial number of buckets */ +#define HASH_INITIAL_NUM_BUCKETS 32U /* initial number of buckets */ +#define HASH_INITIAL_NUM_BUCKETS_LOG2 5U /* lg2 of initial number of buckets */ +#define HASH_BKT_CAPACITY_THRESH 10U /* expand when bucket count reaches */ + +/* calculate the element whose hash handle address is hhp */ +#define ELMT_FROM_HH(tbl,hhp) ((void*)(((char*)(hhp)) - ((tbl)->hho))) +/* calculate the hash handle from element address elp */ +#define HH_FROM_ELMT(tbl,elp) ((UT_hash_handle *)(((char*)(elp)) + ((tbl)->hho))) + +#define HASH_ROLLBACK_BKT(hh, head, itemptrhh) \ +do { \ + struct UT_hash_handle *_hd_hh_item = (itemptrhh); \ + unsigned _hd_bkt; \ + HASH_TO_BKT(_hd_hh_item->hashv, (head)->hh.tbl->num_buckets, _hd_bkt); \ + (head)->hh.tbl->buckets[_hd_bkt].count++; \ + _hd_hh_item->hh_next = NULL; \ + _hd_hh_item->hh_prev = NULL; \ +} while (0) + +#define HASH_VALUE(keyptr,keylen,hashv) \ +do { \ + HASH_FCN(keyptr, keylen, hashv); \ +} while (0) + +#define HASH_FIND_BYHASHVALUE(hh,head,keyptr,keylen,hashval,out) \ +do { \ + (out) = NULL; \ + if (head) { \ + unsigned _hf_bkt; \ + HASH_TO_BKT(hashval, (head)->hh.tbl->num_buckets, _hf_bkt); \ + if (HASH_BLOOM_TEST((head)->hh.tbl, hashval) != 0) { \ + HASH_FIND_IN_BKT((head)->hh.tbl, hh, (head)->hh.tbl->buckets[ _hf_bkt ], keyptr, keylen, hashval, out); \ + } \ + } \ +} while (0) + +#define HASH_FIND(hh,head,keyptr,keylen,out) \ +do { \ + unsigned _hf_hashv; \ + HASH_VALUE(keyptr, keylen, _hf_hashv); \ + HASH_FIND_BYHASHVALUE(hh, head, keyptr, keylen, _hf_hashv, out); \ +} while (0) + +#ifdef HASH_BLOOM +#define HASH_BLOOM_BITLEN (1UL << HASH_BLOOM) +#define HASH_BLOOM_BYTELEN (HASH_BLOOM_BITLEN/8UL) + (((HASH_BLOOM_BITLEN%8UL)!=0UL) ? 1UL : 0UL) +#define HASH_BLOOM_MAKE(tbl,oomed) \ +do { \ + (tbl)->bloom_nbits = HASH_BLOOM; \ + (tbl)->bloom_bv = (uint8_t*)uthash_malloc(HASH_BLOOM_BYTELEN); \ + if (!(tbl)->bloom_bv) { \ + HASH_RECORD_OOM(oomed); \ + } else { \ + uthash_bzero((tbl)->bloom_bv, HASH_BLOOM_BYTELEN); \ + (tbl)->bloom_sig = HASH_BLOOM_SIGNATURE; \ + } \ +} while (0) + +#define HASH_BLOOM_FREE(tbl) \ +do { \ + uthash_free((tbl)->bloom_bv, HASH_BLOOM_BYTELEN); \ +} while (0) + +#define HASH_BLOOM_BITSET(bv,idx) (bv[(idx)/8U] |= (1U << ((idx)%8U))) +#define HASH_BLOOM_BITTEST(bv,idx) (bv[(idx)/8U] & (1U << ((idx)%8U))) + +#define HASH_BLOOM_ADD(tbl,hashv) \ + HASH_BLOOM_BITSET((tbl)->bloom_bv, ((hashv) & (uint32_t)((1UL << (tbl)->bloom_nbits) - 1U))) + +#define HASH_BLOOM_TEST(tbl,hashv) \ + HASH_BLOOM_BITTEST((tbl)->bloom_bv, ((hashv) & (uint32_t)((1UL << (tbl)->bloom_nbits) - 1U))) + +#else +#define HASH_BLOOM_MAKE(tbl,oomed) +#define HASH_BLOOM_FREE(tbl) +#define HASH_BLOOM_ADD(tbl,hashv) +#define HASH_BLOOM_TEST(tbl,hashv) (1) +#define HASH_BLOOM_BYTELEN 0U +#endif + +#define HASH_MAKE_TABLE(hh,head,oomed) \ +do { \ + (head)->hh.tbl = (UT_hash_table*)uthash_malloc(sizeof(UT_hash_table)); \ + if (!(head)->hh.tbl) { \ + HASH_RECORD_OOM(oomed); \ + } else { \ + uthash_bzero((head)->hh.tbl, sizeof(UT_hash_table)); \ + (head)->hh.tbl->tail = &((head)->hh); \ + (head)->hh.tbl->num_buckets = HASH_INITIAL_NUM_BUCKETS; \ + (head)->hh.tbl->log2_num_buckets = HASH_INITIAL_NUM_BUCKETS_LOG2; \ + (head)->hh.tbl->hho = (char*)(&(head)->hh) - (char*)(head); \ + (head)->hh.tbl->buckets = (UT_hash_bucket*)uthash_malloc( \ + HASH_INITIAL_NUM_BUCKETS * sizeof(struct UT_hash_bucket)); \ + (head)->hh.tbl->signature = HASH_SIGNATURE; \ + if (!(head)->hh.tbl->buckets) { \ + HASH_RECORD_OOM(oomed); \ + uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \ + } else { \ + uthash_bzero((head)->hh.tbl->buckets, \ + HASH_INITIAL_NUM_BUCKETS * sizeof(struct UT_hash_bucket)); \ + HASH_BLOOM_MAKE((head)->hh.tbl, oomed); \ + IF_HASH_NONFATAL_OOM( \ + if (oomed) { \ + uthash_free((head)->hh.tbl->buckets, \ + HASH_INITIAL_NUM_BUCKETS*sizeof(struct UT_hash_bucket)); \ + uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \ + } \ + ) \ + } \ + } \ +} while (0) + +#define HASH_REPLACE_BYHASHVALUE_INORDER(hh,head,fieldname,keylen_in,hashval,add,replaced,cmpfcn) \ +do { \ + (replaced) = NULL; \ + HASH_FIND_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, replaced); \ + if (replaced) { \ + HASH_DELETE(hh, head, replaced); \ + } \ + HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh, head, &((add)->fieldname), keylen_in, hashval, add, cmpfcn); \ +} while (0) + +#define HASH_REPLACE_BYHASHVALUE(hh,head,fieldname,keylen_in,hashval,add,replaced) \ +do { \ + (replaced) = NULL; \ + HASH_FIND_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, replaced); \ + if (replaced) { \ + HASH_DELETE(hh, head, replaced); \ + } \ + HASH_ADD_KEYPTR_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, add); \ +} while (0) + +#define HASH_REPLACE(hh,head,fieldname,keylen_in,add,replaced) \ +do { \ + unsigned _hr_hashv; \ + HASH_VALUE(&((add)->fieldname), keylen_in, _hr_hashv); \ + HASH_REPLACE_BYHASHVALUE(hh, head, fieldname, keylen_in, _hr_hashv, add, replaced); \ +} while (0) + +#define HASH_REPLACE_INORDER(hh,head,fieldname,keylen_in,add,replaced,cmpfcn) \ +do { \ + unsigned _hr_hashv; \ + HASH_VALUE(&((add)->fieldname), keylen_in, _hr_hashv); \ + HASH_REPLACE_BYHASHVALUE_INORDER(hh, head, fieldname, keylen_in, _hr_hashv, add, replaced, cmpfcn); \ +} while (0) + +#define HASH_APPEND_LIST(hh, head, add) \ +do { \ + (add)->hh.next = NULL; \ + (add)->hh.prev = ELMT_FROM_HH((head)->hh.tbl, (head)->hh.tbl->tail); \ + (head)->hh.tbl->tail->next = (add); \ + (head)->hh.tbl->tail = &((add)->hh); \ +} while (0) + +#define HASH_AKBI_INNER_LOOP(hh,head,add,cmpfcn) \ +do { \ + do { \ + if (cmpfcn(DECLTYPE(head)(_hs_iter), add) > 0) { \ + break; \ + } \ + } while ((_hs_iter = HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->next)); \ +} while (0) + +#ifdef NO_DECLTYPE +#undef HASH_AKBI_INNER_LOOP +#define HASH_AKBI_INNER_LOOP(hh,head,add,cmpfcn) \ +do { \ + char *_hs_saved_head = (char*)(head); \ + do { \ + DECLTYPE_ASSIGN(head, _hs_iter); \ + if (cmpfcn(head, add) > 0) { \ + DECLTYPE_ASSIGN(head, _hs_saved_head); \ + break; \ + } \ + DECLTYPE_ASSIGN(head, _hs_saved_head); \ + } while ((_hs_iter = HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->next)); \ +} while (0) +#endif + +#if HASH_NONFATAL_OOM + +#define HASH_ADD_TO_TABLE(hh,head,keyptr,keylen_in,hashval,add,oomed) \ +do { \ + if (!(oomed)) { \ + unsigned _ha_bkt; \ + (head)->hh.tbl->num_items++; \ + HASH_TO_BKT(hashval, (head)->hh.tbl->num_buckets, _ha_bkt); \ + HASH_ADD_TO_BKT((head)->hh.tbl->buckets[_ha_bkt], hh, &(add)->hh, oomed); \ + if (oomed) { \ + HASH_ROLLBACK_BKT(hh, head, &(add)->hh); \ + HASH_DELETE_HH(hh, head, &(add)->hh); \ + (add)->hh.tbl = NULL; \ + uthash_nonfatal_oom(add); \ + } else { \ + HASH_BLOOM_ADD((head)->hh.tbl, hashval); \ + HASH_EMIT_KEY(hh, head, keyptr, keylen_in); \ + } \ + } else { \ + (add)->hh.tbl = NULL; \ + uthash_nonfatal_oom(add); \ + } \ +} while (0) + +#else + +#define HASH_ADD_TO_TABLE(hh,head,keyptr,keylen_in,hashval,add,oomed) \ +do { \ + unsigned _ha_bkt; \ + (head)->hh.tbl->num_items++; \ + HASH_TO_BKT(hashval, (head)->hh.tbl->num_buckets, _ha_bkt); \ + HASH_ADD_TO_BKT((head)->hh.tbl->buckets[_ha_bkt], hh, &(add)->hh, oomed); \ + HASH_BLOOM_ADD((head)->hh.tbl, hashval); \ + HASH_EMIT_KEY(hh, head, keyptr, keylen_in); \ +} while (0) + +#endif + + +#define HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh,head,keyptr,keylen_in,hashval,add,cmpfcn) \ +do { \ + IF_HASH_NONFATAL_OOM( int _ha_oomed = 0; ) \ + (add)->hh.hashv = (hashval); \ + (add)->hh.key = (char*) (keyptr); \ + (add)->hh.keylen = (unsigned) (keylen_in); \ + if (!(head)) { \ + (add)->hh.next = NULL; \ + (add)->hh.prev = NULL; \ + HASH_MAKE_TABLE(hh, add, _ha_oomed); \ + IF_HASH_NONFATAL_OOM( if (!_ha_oomed) { ) \ + (head) = (add); \ + IF_HASH_NONFATAL_OOM( } ) \ + } else { \ + void *_hs_iter = (head); \ + (add)->hh.tbl = (head)->hh.tbl; \ + HASH_AKBI_INNER_LOOP(hh, head, add, cmpfcn); \ + if (_hs_iter) { \ + (add)->hh.next = _hs_iter; \ + if (((add)->hh.prev = HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->prev)) { \ + HH_FROM_ELMT((head)->hh.tbl, (add)->hh.prev)->next = (add); \ + } else { \ + (head) = (add); \ + } \ + HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->prev = (add); \ + } else { \ + HASH_APPEND_LIST(hh, head, add); \ + } \ + } \ + HASH_ADD_TO_TABLE(hh, head, keyptr, keylen_in, hashval, add, _ha_oomed); \ + HASH_FSCK(hh, head, "HASH_ADD_KEYPTR_BYHASHVALUE_INORDER"); \ +} while (0) + +#define HASH_ADD_KEYPTR_INORDER(hh,head,keyptr,keylen_in,add,cmpfcn) \ +do { \ + unsigned _hs_hashv; \ + HASH_VALUE(keyptr, keylen_in, _hs_hashv); \ + HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh, head, keyptr, keylen_in, _hs_hashv, add, cmpfcn); \ +} while (0) + +#define HASH_ADD_BYHASHVALUE_INORDER(hh,head,fieldname,keylen_in,hashval,add,cmpfcn) \ + HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh, head, &((add)->fieldname), keylen_in, hashval, add, cmpfcn) + +#define HASH_ADD_INORDER(hh,head,fieldname,keylen_in,add,cmpfcn) \ + HASH_ADD_KEYPTR_INORDER(hh, head, &((add)->fieldname), keylen_in, add, cmpfcn) + +#define HASH_ADD_KEYPTR_BYHASHVALUE(hh,head,keyptr,keylen_in,hashval,add) \ +do { \ + IF_HASH_NONFATAL_OOM( int _ha_oomed = 0; ) \ + (add)->hh.hashv = (hashval); \ + (add)->hh.key = (char*) (keyptr); \ + (add)->hh.keylen = (unsigned) (keylen_in); \ + if (!(head)) { \ + (add)->hh.next = NULL; \ + (add)->hh.prev = NULL; \ + HASH_MAKE_TABLE(hh, add, _ha_oomed); \ + IF_HASH_NONFATAL_OOM( if (!_ha_oomed) { ) \ + (head) = (add); \ + IF_HASH_NONFATAL_OOM( } ) \ + } else { \ + (add)->hh.tbl = (head)->hh.tbl; \ + HASH_APPEND_LIST(hh, head, add); \ + } \ + HASH_ADD_TO_TABLE(hh, head, keyptr, keylen_in, hashval, add, _ha_oomed); \ + HASH_FSCK(hh, head, "HASH_ADD_KEYPTR_BYHASHVALUE"); \ +} while (0) + +#define HASH_ADD_KEYPTR(hh,head,keyptr,keylen_in,add) \ +do { \ + unsigned _ha_hashv; \ + HASH_VALUE(keyptr, keylen_in, _ha_hashv); \ + HASH_ADD_KEYPTR_BYHASHVALUE(hh, head, keyptr, keylen_in, _ha_hashv, add); \ +} while (0) + +#define HASH_ADD_BYHASHVALUE(hh,head,fieldname,keylen_in,hashval,add) \ + HASH_ADD_KEYPTR_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, add) + +#define HASH_ADD(hh,head,fieldname,keylen_in,add) \ + HASH_ADD_KEYPTR(hh, head, &((add)->fieldname), keylen_in, add) + +#define HASH_TO_BKT(hashv,num_bkts,bkt) \ +do { \ + bkt = ((hashv) & ((num_bkts) - 1U)); \ +} while (0) + +/* delete "delptr" from the hash table. + * "the usual" patch-up process for the app-order doubly-linked-list. + * The use of _hd_hh_del below deserves special explanation. + * These used to be expressed using (delptr) but that led to a bug + * if someone used the same symbol for the head and deletee, like + * HASH_DELETE(hh,users,users); + * We want that to work, but by changing the head (users) below + * we were forfeiting our ability to further refer to the deletee (users) + * in the patch-up process. Solution: use scratch space to + * copy the deletee pointer, then the latter references are via that + * scratch pointer rather than through the repointed (users) symbol. + */ +#define HASH_DELETE(hh,head,delptr) \ + HASH_DELETE_HH(hh, head, &(delptr)->hh) + +#define HASH_DELETE_HH(hh,head,delptrhh) \ +do { \ + struct UT_hash_handle *_hd_hh_del = (delptrhh); \ + if ((_hd_hh_del->prev == NULL) && (_hd_hh_del->next == NULL)) { \ + HASH_BLOOM_FREE((head)->hh.tbl); \ + uthash_free((head)->hh.tbl->buckets, \ + (head)->hh.tbl->num_buckets * sizeof(struct UT_hash_bucket)); \ + uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \ + (head) = NULL; \ + } else { \ + unsigned _hd_bkt; \ + if (_hd_hh_del == (head)->hh.tbl->tail) { \ + (head)->hh.tbl->tail = HH_FROM_ELMT((head)->hh.tbl, _hd_hh_del->prev); \ + } \ + if (_hd_hh_del->prev != NULL) { \ + HH_FROM_ELMT((head)->hh.tbl, _hd_hh_del->prev)->next = _hd_hh_del->next; \ + } else { \ + DECLTYPE_ASSIGN(head, _hd_hh_del->next); \ + } \ + if (_hd_hh_del->next != NULL) { \ + HH_FROM_ELMT((head)->hh.tbl, _hd_hh_del->next)->prev = _hd_hh_del->prev; \ + } \ + HASH_TO_BKT(_hd_hh_del->hashv, (head)->hh.tbl->num_buckets, _hd_bkt); \ + HASH_DEL_IN_BKT((head)->hh.tbl->buckets[_hd_bkt], _hd_hh_del); \ + (head)->hh.tbl->num_items--; \ + } \ + HASH_FSCK(hh, head, "HASH_DELETE_HH"); \ +} while (0) + +/* convenience forms of HASH_FIND/HASH_ADD/HASH_DEL */ +#define HASH_FIND_STR(head,findstr,out) \ +do { \ + unsigned _uthash_hfstr_keylen = (unsigned)uthash_strlen(findstr); \ + HASH_FIND(hh, head, findstr, _uthash_hfstr_keylen, out); \ +} while (0) +#define HASH_ADD_STR(head,strfield,add) \ +do { \ + unsigned _uthash_hastr_keylen = (unsigned)uthash_strlen((add)->strfield); \ + HASH_ADD(hh, head, strfield[0], _uthash_hastr_keylen, add); \ +} while (0) +#define HASH_REPLACE_STR(head,strfield,add,replaced) \ +do { \ + unsigned _uthash_hrstr_keylen = (unsigned)uthash_strlen((add)->strfield); \ + HASH_REPLACE(hh, head, strfield[0], _uthash_hrstr_keylen, add, replaced); \ +} while (0) +#define HASH_FIND_INT(head,findint,out) \ + HASH_FIND(hh,head,findint,sizeof(int),out) +#define HASH_ADD_INT(head,intfield,add) \ + HASH_ADD(hh,head,intfield,sizeof(int),add) +#define HASH_REPLACE_INT(head,intfield,add,replaced) \ + HASH_REPLACE(hh,head,intfield,sizeof(int),add,replaced) +#define HASH_FIND_PTR(head,findptr,out) \ + HASH_FIND(hh,head,findptr,sizeof(void *),out) +#define HASH_ADD_PTR(head,ptrfield,add) \ + HASH_ADD(hh,head,ptrfield,sizeof(void *),add) +#define HASH_REPLACE_PTR(head,ptrfield,add,replaced) \ + HASH_REPLACE(hh,head,ptrfield,sizeof(void *),add,replaced) +#define HASH_DEL(head,delptr) \ + HASH_DELETE(hh,head,delptr) + +/* HASH_FSCK checks hash integrity on every add/delete when HASH_DEBUG is defined. + * This is for uthash developer only; it compiles away if HASH_DEBUG isn't defined. + */ +#ifdef HASH_DEBUG +#define HASH_OOPS(...) do { fprintf(stderr,__VA_ARGS__); exit(-1); } while (0) +#define HASH_FSCK(hh,head,where) \ +do { \ + struct UT_hash_handle *_thh; \ + if (head) { \ + unsigned _bkt_i; \ + unsigned _count = 0; \ + char *_prev; \ + for (_bkt_i = 0; _bkt_i < (head)->hh.tbl->num_buckets; ++_bkt_i) { \ + unsigned _bkt_count = 0; \ + _thh = (head)->hh.tbl->buckets[_bkt_i].hh_head; \ + _prev = NULL; \ + while (_thh) { \ + if (_prev != (char*)(_thh->hh_prev)) { \ + HASH_OOPS("%s: invalid hh_prev %p, actual %p\n", \ + (where), (void*)_thh->hh_prev, (void*)_prev); \ + } \ + _bkt_count++; \ + _prev = (char*)(_thh); \ + _thh = _thh->hh_next; \ + } \ + _count += _bkt_count; \ + if ((head)->hh.tbl->buckets[_bkt_i].count != _bkt_count) { \ + HASH_OOPS("%s: invalid bucket count %u, actual %u\n", \ + (where), (head)->hh.tbl->buckets[_bkt_i].count, _bkt_count); \ + } \ + } \ + if (_count != (head)->hh.tbl->num_items) { \ + HASH_OOPS("%s: invalid hh item count %u, actual %u\n", \ + (where), (head)->hh.tbl->num_items, _count); \ + } \ + _count = 0; \ + _prev = NULL; \ + _thh = &(head)->hh; \ + while (_thh) { \ + _count++; \ + if (_prev != (char*)_thh->prev) { \ + HASH_OOPS("%s: invalid prev %p, actual %p\n", \ + (where), (void*)_thh->prev, (void*)_prev); \ + } \ + _prev = (char*)ELMT_FROM_HH((head)->hh.tbl, _thh); \ + _thh = (_thh->next ? HH_FROM_ELMT((head)->hh.tbl, _thh->next) : NULL); \ + } \ + if (_count != (head)->hh.tbl->num_items) { \ + HASH_OOPS("%s: invalid app item count %u, actual %u\n", \ + (where), (head)->hh.tbl->num_items, _count); \ + } \ + } \ +} while (0) +#else +#define HASH_FSCK(hh,head,where) +#endif + +/* When compiled with -DHASH_EMIT_KEYS, length-prefixed keys are emitted to + * the descriptor to which this macro is defined for tuning the hash function. + * The app can #include to get the prototype for write(2). */ +#ifdef HASH_EMIT_KEYS +#define HASH_EMIT_KEY(hh,head,keyptr,fieldlen) \ +do { \ + unsigned _klen = fieldlen; \ + write(HASH_EMIT_KEYS, &_klen, sizeof(_klen)); \ + write(HASH_EMIT_KEYS, keyptr, (unsigned long)fieldlen); \ +} while (0) +#else +#define HASH_EMIT_KEY(hh,head,keyptr,fieldlen) +#endif + +/* default to Jenkin's hash unless overridden e.g. DHASH_FUNCTION=HASH_SAX */ +#ifdef HASH_FUNCTION +#define HASH_FCN HASH_FUNCTION +#else +#define HASH_FCN HASH_JEN +#endif + +/* The Bernstein hash function, used in Perl prior to v5.6. Note (x<<5+x)=x*33. */ +#define HASH_BER(key,keylen,hashv) \ +do { \ + unsigned _hb_keylen = (unsigned)keylen; \ + const unsigned char *_hb_key = (const unsigned char*)(key); \ + (hashv) = 0; \ + while (_hb_keylen-- != 0U) { \ + (hashv) = (((hashv) << 5) + (hashv)) + *_hb_key++; \ + } \ +} while (0) + + +/* SAX/FNV/OAT/JEN hash functions are macro variants of those listed at + * http://eternallyconfuzzled.com/tuts/algorithms/jsw_tut_hashing.aspx */ +#define HASH_SAX(key,keylen,hashv) \ +do { \ + unsigned _sx_i; \ + const unsigned char *_hs_key = (const unsigned char*)(key); \ + hashv = 0; \ + for (_sx_i=0; _sx_i < keylen; _sx_i++) { \ + hashv ^= (hashv << 5) + (hashv >> 2) + _hs_key[_sx_i]; \ + } \ +} while (0) +/* FNV-1a variation */ +#define HASH_FNV(key,keylen,hashv) \ +do { \ + unsigned _fn_i; \ + const unsigned char *_hf_key = (const unsigned char*)(key); \ + (hashv) = 2166136261U; \ + for (_fn_i=0; _fn_i < keylen; _fn_i++) { \ + hashv = hashv ^ _hf_key[_fn_i]; \ + hashv = hashv * 16777619U; \ + } \ +} while (0) + +#define HASH_OAT(key,keylen,hashv) \ +do { \ + unsigned _ho_i; \ + const unsigned char *_ho_key=(const unsigned char*)(key); \ + hashv = 0; \ + for(_ho_i=0; _ho_i < keylen; _ho_i++) { \ + hashv += _ho_key[_ho_i]; \ + hashv += (hashv << 10); \ + hashv ^= (hashv >> 6); \ + } \ + hashv += (hashv << 3); \ + hashv ^= (hashv >> 11); \ + hashv += (hashv << 15); \ +} while (0) + +#define HASH_JEN_MIX(a,b,c) \ +do { \ + a -= b; a -= c; a ^= ( c >> 13 ); \ + b -= c; b -= a; b ^= ( a << 8 ); \ + c -= a; c -= b; c ^= ( b >> 13 ); \ + a -= b; a -= c; a ^= ( c >> 12 ); \ + b -= c; b -= a; b ^= ( a << 16 ); \ + c -= a; c -= b; c ^= ( b >> 5 ); \ + a -= b; a -= c; a ^= ( c >> 3 ); \ + b -= c; b -= a; b ^= ( a << 10 ); \ + c -= a; c -= b; c ^= ( b >> 15 ); \ +} while (0) + +#define HASH_JEN(key,keylen,hashv) \ +do { \ + unsigned _hj_i,_hj_j,_hj_k; \ + unsigned const char *_hj_key=(unsigned const char*)(key); \ + hashv = 0xfeedbeefu; \ + _hj_i = _hj_j = 0x9e3779b9u; \ + _hj_k = (unsigned)(keylen); \ + while (_hj_k >= 12U) { \ + _hj_i += (_hj_key[0] + ( (unsigned)_hj_key[1] << 8 ) \ + + ( (unsigned)_hj_key[2] << 16 ) \ + + ( (unsigned)_hj_key[3] << 24 ) ); \ + _hj_j += (_hj_key[4] + ( (unsigned)_hj_key[5] << 8 ) \ + + ( (unsigned)_hj_key[6] << 16 ) \ + + ( (unsigned)_hj_key[7] << 24 ) ); \ + hashv += (_hj_key[8] + ( (unsigned)_hj_key[9] << 8 ) \ + + ( (unsigned)_hj_key[10] << 16 ) \ + + ( (unsigned)_hj_key[11] << 24 ) ); \ + \ + HASH_JEN_MIX(_hj_i, _hj_j, hashv); \ + \ + _hj_key += 12; \ + _hj_k -= 12U; \ + } \ + hashv += (unsigned)(keylen); \ + switch ( _hj_k ) { \ + case 11: hashv += ( (unsigned)_hj_key[10] << 24 ); /* FALLTHROUGH */ \ + case 10: hashv += ( (unsigned)_hj_key[9] << 16 ); /* FALLTHROUGH */ \ + case 9: hashv += ( (unsigned)_hj_key[8] << 8 ); /* FALLTHROUGH */ \ + case 8: _hj_j += ( (unsigned)_hj_key[7] << 24 ); /* FALLTHROUGH */ \ + case 7: _hj_j += ( (unsigned)_hj_key[6] << 16 ); /* FALLTHROUGH */ \ + case 6: _hj_j += ( (unsigned)_hj_key[5] << 8 ); /* FALLTHROUGH */ \ + case 5: _hj_j += _hj_key[4]; /* FALLTHROUGH */ \ + case 4: _hj_i += ( (unsigned)_hj_key[3] << 24 ); /* FALLTHROUGH */ \ + case 3: _hj_i += ( (unsigned)_hj_key[2] << 16 ); /* FALLTHROUGH */ \ + case 2: _hj_i += ( (unsigned)_hj_key[1] << 8 ); /* FALLTHROUGH */ \ + case 1: _hj_i += _hj_key[0]; \ + } \ + HASH_JEN_MIX(_hj_i, _hj_j, hashv); \ +} while (0) + +/* The Paul Hsieh hash function */ +#undef get16bits +#if (defined(__GNUC__) && defined(__i386__)) || defined(__WATCOMC__) \ + || defined(_MSC_VER) || defined (__BORLANDC__) || defined (__TURBOC__) +#define get16bits(d) (*((const uint16_t *) (d))) +#endif + +#if !defined (get16bits) +#define get16bits(d) ((((uint32_t)(((const uint8_t *)(d))[1])) << 8) \ + +(uint32_t)(((const uint8_t *)(d))[0]) ) +#endif +#define HASH_SFH(key,keylen,hashv) \ +do { \ + unsigned const char *_sfh_key=(unsigned const char*)(key); \ + uint32_t _sfh_tmp, _sfh_len = (uint32_t)keylen; \ + \ + unsigned _sfh_rem = _sfh_len & 3U; \ + _sfh_len >>= 2; \ + hashv = 0xcafebabeu; \ + \ + /* Main loop */ \ + for (;_sfh_len > 0U; _sfh_len--) { \ + hashv += get16bits (_sfh_key); \ + _sfh_tmp = ((uint32_t)(get16bits (_sfh_key+2)) << 11) ^ hashv; \ + hashv = (hashv << 16) ^ _sfh_tmp; \ + _sfh_key += 2U*sizeof (uint16_t); \ + hashv += hashv >> 11; \ + } \ + \ + /* Handle end cases */ \ + switch (_sfh_rem) { \ + case 3: hashv += get16bits (_sfh_key); \ + hashv ^= hashv << 16; \ + hashv ^= (uint32_t)(_sfh_key[sizeof (uint16_t)]) << 18; \ + hashv += hashv >> 11; \ + break; \ + case 2: hashv += get16bits (_sfh_key); \ + hashv ^= hashv << 11; \ + hashv += hashv >> 17; \ + break; \ + case 1: hashv += *_sfh_key; \ + hashv ^= hashv << 10; \ + hashv += hashv >> 1; \ + } \ + \ + /* Force "avalanching" of final 127 bits */ \ + hashv ^= hashv << 3; \ + hashv += hashv >> 5; \ + hashv ^= hashv << 4; \ + hashv += hashv >> 17; \ + hashv ^= hashv << 25; \ + hashv += hashv >> 6; \ +} while (0) + +#ifdef HASH_USING_NO_STRICT_ALIASING +/* The MurmurHash exploits some CPU's (x86,x86_64) tolerance for unaligned reads. + * For other types of CPU's (e.g. Sparc) an unaligned read causes a bus error. + * MurmurHash uses the faster approach only on CPU's where we know it's safe. + * + * Note the preprocessor built-in defines can be emitted using: + * + * gcc -m64 -dM -E - < /dev/null (on gcc) + * cc -## a.c (where a.c is a simple test file) (Sun Studio) + */ +#if (defined(__i386__) || defined(__x86_64__) || defined(_M_IX86)) +#define MUR_GETBLOCK(p,i) p[i] +#else /* non intel */ +#define MUR_PLUS0_ALIGNED(p) (((unsigned long)p & 3UL) == 0UL) +#define MUR_PLUS1_ALIGNED(p) (((unsigned long)p & 3UL) == 1UL) +#define MUR_PLUS2_ALIGNED(p) (((unsigned long)p & 3UL) == 2UL) +#define MUR_PLUS3_ALIGNED(p) (((unsigned long)p & 3UL) == 3UL) +#define WP(p) ((uint32_t*)((unsigned long)(p) & ~3UL)) +#if (defined(__BIG_ENDIAN__) || defined(SPARC) || defined(__ppc__) || defined(__ppc64__)) +#define MUR_THREE_ONE(p) ((((*WP(p))&0x00ffffff) << 8) | (((*(WP(p)+1))&0xff000000) >> 24)) +#define MUR_TWO_TWO(p) ((((*WP(p))&0x0000ffff) <<16) | (((*(WP(p)+1))&0xffff0000) >> 16)) +#define MUR_ONE_THREE(p) ((((*WP(p))&0x000000ff) <<24) | (((*(WP(p)+1))&0xffffff00) >> 8)) +#else /* assume little endian non-intel */ +#define MUR_THREE_ONE(p) ((((*WP(p))&0xffffff00) >> 8) | (((*(WP(p)+1))&0x000000ff) << 24)) +#define MUR_TWO_TWO(p) ((((*WP(p))&0xffff0000) >>16) | (((*(WP(p)+1))&0x0000ffff) << 16)) +#define MUR_ONE_THREE(p) ((((*WP(p))&0xff000000) >>24) | (((*(WP(p)+1))&0x00ffffff) << 8)) +#endif +#define MUR_GETBLOCK(p,i) (MUR_PLUS0_ALIGNED(p) ? ((p)[i]) : \ + (MUR_PLUS1_ALIGNED(p) ? MUR_THREE_ONE(p) : \ + (MUR_PLUS2_ALIGNED(p) ? MUR_TWO_TWO(p) : \ + MUR_ONE_THREE(p)))) +#endif +#define MUR_ROTL32(x,r) (((x) << (r)) | ((x) >> (32 - (r)))) +#define MUR_FMIX(_h) \ +do { \ + _h ^= _h >> 16; \ + _h *= 0x85ebca6bu; \ + _h ^= _h >> 13; \ + _h *= 0xc2b2ae35u; \ + _h ^= _h >> 16; \ +} while (0) + +#define HASH_MUR(key,keylen,hashv) \ +do { \ + const uint8_t *_mur_data = (const uint8_t*)(key); \ + const int _mur_nblocks = (int)(keylen) / 4; \ + uint32_t _mur_h1 = 0xf88D5353u; \ + uint32_t _mur_c1 = 0xcc9e2d51u; \ + uint32_t _mur_c2 = 0x1b873593u; \ + uint32_t _mur_k1 = 0; \ + const uint8_t *_mur_tail; \ + const uint32_t *_mur_blocks = (const uint32_t*)(_mur_data+(_mur_nblocks*4)); \ + int _mur_i; \ + for (_mur_i = -_mur_nblocks; _mur_i != 0; _mur_i++) { \ + _mur_k1 = MUR_GETBLOCK(_mur_blocks,_mur_i); \ + _mur_k1 *= _mur_c1; \ + _mur_k1 = MUR_ROTL32(_mur_k1,15); \ + _mur_k1 *= _mur_c2; \ + \ + _mur_h1 ^= _mur_k1; \ + _mur_h1 = MUR_ROTL32(_mur_h1,13); \ + _mur_h1 = (_mur_h1*5U) + 0xe6546b64u; \ + } \ + _mur_tail = (const uint8_t*)(_mur_data + (_mur_nblocks*4)); \ + _mur_k1=0; \ + switch ((keylen) & 3U) { \ + case 0: break; \ + case 3: _mur_k1 ^= (uint32_t)_mur_tail[2] << 16; /* FALLTHROUGH */ \ + case 2: _mur_k1 ^= (uint32_t)_mur_tail[1] << 8; /* FALLTHROUGH */ \ + case 1: _mur_k1 ^= (uint32_t)_mur_tail[0]; \ + _mur_k1 *= _mur_c1; \ + _mur_k1 = MUR_ROTL32(_mur_k1,15); \ + _mur_k1 *= _mur_c2; \ + _mur_h1 ^= _mur_k1; \ + } \ + _mur_h1 ^= (uint32_t)(keylen); \ + MUR_FMIX(_mur_h1); \ + hashv = _mur_h1; \ +} while (0) +#endif /* HASH_USING_NO_STRICT_ALIASING */ + +/* iterate over items in a known bucket to find desired item */ +#define HASH_FIND_IN_BKT(tbl,hh,head,keyptr,keylen_in,hashval,out) \ +do { \ + if ((head).hh_head != NULL) { \ + DECLTYPE_ASSIGN(out, ELMT_FROM_HH(tbl, (head).hh_head)); \ + } else { \ + (out) = NULL; \ + } \ + while ((out) != NULL) { \ + if ((out)->hh.hashv == (hashval) && (out)->hh.keylen == (keylen_in)) { \ + if (uthash_memcmp((out)->hh.key, keyptr, keylen_in) == 0) { \ + break; \ + } \ + } \ + if ((out)->hh.hh_next != NULL) { \ + DECLTYPE_ASSIGN(out, ELMT_FROM_HH(tbl, (out)->hh.hh_next)); \ + } else { \ + (out) = NULL; \ + } \ + } \ +} while (0) + +/* add an item to a bucket */ +#define HASH_ADD_TO_BKT(head,hh,addhh,oomed) \ +do { \ + UT_hash_bucket *_ha_head = &(head); \ + _ha_head->count++; \ + (addhh)->hh_next = _ha_head->hh_head; \ + (addhh)->hh_prev = NULL; \ + if (_ha_head->hh_head != NULL) { \ + _ha_head->hh_head->hh_prev = (addhh); \ + } \ + _ha_head->hh_head = (addhh); \ + if ((_ha_head->count >= ((_ha_head->expand_mult + 1U) * HASH_BKT_CAPACITY_THRESH)) \ + && !(addhh)->tbl->noexpand) { \ + HASH_EXPAND_BUCKETS(addhh,(addhh)->tbl, oomed); \ + IF_HASH_NONFATAL_OOM( \ + if (oomed) { \ + HASH_DEL_IN_BKT(head,addhh); \ + } \ + ) \ + } \ +} while (0) + +/* remove an item from a given bucket */ +#define HASH_DEL_IN_BKT(head,delhh) \ +do { \ + UT_hash_bucket *_hd_head = &(head); \ + _hd_head->count--; \ + if (_hd_head->hh_head == (delhh)) { \ + _hd_head->hh_head = (delhh)->hh_next; \ + } \ + if ((delhh)->hh_prev) { \ + (delhh)->hh_prev->hh_next = (delhh)->hh_next; \ + } \ + if ((delhh)->hh_next) { \ + (delhh)->hh_next->hh_prev = (delhh)->hh_prev; \ + } \ +} while (0) + +/* Bucket expansion has the effect of doubling the number of buckets + * and redistributing the items into the new buckets. Ideally the + * items will distribute more or less evenly into the new buckets + * (the extent to which this is true is a measure of the quality of + * the hash function as it applies to the key domain). + * + * With the items distributed into more buckets, the chain length + * (item count) in each bucket is reduced. Thus by expanding buckets + * the hash keeps a bound on the chain length. This bounded chain + * length is the essence of how a hash provides constant time lookup. + * + * The calculation of tbl->ideal_chain_maxlen below deserves some + * explanation. First, keep in mind that we're calculating the ideal + * maximum chain length based on the *new* (doubled) bucket count. + * In fractions this is just n/b (n=number of items,b=new num buckets). + * Since the ideal chain length is an integer, we want to calculate + * ceil(n/b). We don't depend on floating point arithmetic in this + * hash, so to calculate ceil(n/b) with integers we could write + * + * ceil(n/b) = (n/b) + ((n%b)?1:0) + * + * and in fact a previous version of this hash did just that. + * But now we have improved things a bit by recognizing that b is + * always a power of two. We keep its base 2 log handy (call it lb), + * so now we can write this with a bit shift and logical AND: + * + * ceil(n/b) = (n>>lb) + ( (n & (b-1)) ? 1:0) + * + */ +#define HASH_EXPAND_BUCKETS(hh,tbl,oomed) \ +do { \ + unsigned _he_bkt; \ + unsigned _he_bkt_i; \ + struct UT_hash_handle *_he_thh, *_he_hh_nxt; \ + UT_hash_bucket *_he_new_buckets, *_he_newbkt; \ + _he_new_buckets = (UT_hash_bucket*)uthash_malloc( \ + 2UL * (tbl)->num_buckets * sizeof(struct UT_hash_bucket)); \ + if (!_he_new_buckets) { \ + HASH_RECORD_OOM(oomed); \ + } else { \ + uthash_bzero(_he_new_buckets, \ + 2UL * (tbl)->num_buckets * sizeof(struct UT_hash_bucket)); \ + (tbl)->ideal_chain_maxlen = \ + ((tbl)->num_items >> ((tbl)->log2_num_buckets+1U)) + \ + ((((tbl)->num_items & (((tbl)->num_buckets*2U)-1U)) != 0U) ? 1U : 0U); \ + (tbl)->nonideal_items = 0; \ + for (_he_bkt_i = 0; _he_bkt_i < (tbl)->num_buckets; _he_bkt_i++) { \ + _he_thh = (tbl)->buckets[ _he_bkt_i ].hh_head; \ + while (_he_thh != NULL) { \ + _he_hh_nxt = _he_thh->hh_next; \ + HASH_TO_BKT(_he_thh->hashv, (tbl)->num_buckets * 2U, _he_bkt); \ + _he_newbkt = &(_he_new_buckets[_he_bkt]); \ + if (++(_he_newbkt->count) > (tbl)->ideal_chain_maxlen) { \ + (tbl)->nonideal_items++; \ + _he_newbkt->expand_mult = _he_newbkt->count / (tbl)->ideal_chain_maxlen; \ + } \ + _he_thh->hh_prev = NULL; \ + _he_thh->hh_next = _he_newbkt->hh_head; \ + if (_he_newbkt->hh_head != NULL) { \ + _he_newbkt->hh_head->hh_prev = _he_thh; \ + } \ + _he_newbkt->hh_head = _he_thh; \ + _he_thh = _he_hh_nxt; \ + } \ + } \ + uthash_free((tbl)->buckets, (tbl)->num_buckets * sizeof(struct UT_hash_bucket)); \ + (tbl)->num_buckets *= 2U; \ + (tbl)->log2_num_buckets++; \ + (tbl)->buckets = _he_new_buckets; \ + (tbl)->ineff_expands = ((tbl)->nonideal_items > ((tbl)->num_items >> 1)) ? \ + ((tbl)->ineff_expands+1U) : 0U; \ + if ((tbl)->ineff_expands > 1U) { \ + (tbl)->noexpand = 1; \ + uthash_noexpand_fyi(tbl); \ + } \ + uthash_expand_fyi(tbl); \ + } \ +} while (0) + + +/* This is an adaptation of Simon Tatham's O(n log(n)) mergesort */ +/* Note that HASH_SORT assumes the hash handle name to be hh. + * HASH_SRT was added to allow the hash handle name to be passed in. */ +#define HASH_SORT(head,cmpfcn) HASH_SRT(hh,head,cmpfcn) +#define HASH_SRT(hh,head,cmpfcn) \ +do { \ + unsigned _hs_i; \ + unsigned _hs_looping,_hs_nmerges,_hs_insize,_hs_psize,_hs_qsize; \ + struct UT_hash_handle *_hs_p, *_hs_q, *_hs_e, *_hs_list, *_hs_tail; \ + if (head != NULL) { \ + _hs_insize = 1; \ + _hs_looping = 1; \ + _hs_list = &((head)->hh); \ + while (_hs_looping != 0U) { \ + _hs_p = _hs_list; \ + _hs_list = NULL; \ + _hs_tail = NULL; \ + _hs_nmerges = 0; \ + while (_hs_p != NULL) { \ + _hs_nmerges++; \ + _hs_q = _hs_p; \ + _hs_psize = 0; \ + for (_hs_i = 0; _hs_i < _hs_insize; ++_hs_i) { \ + _hs_psize++; \ + _hs_q = ((_hs_q->next != NULL) ? \ + HH_FROM_ELMT((head)->hh.tbl, _hs_q->next) : NULL); \ + if (_hs_q == NULL) { \ + break; \ + } \ + } \ + _hs_qsize = _hs_insize; \ + while ((_hs_psize != 0U) || ((_hs_qsize != 0U) && (_hs_q != NULL))) { \ + if (_hs_psize == 0U) { \ + _hs_e = _hs_q; \ + _hs_q = ((_hs_q->next != NULL) ? \ + HH_FROM_ELMT((head)->hh.tbl, _hs_q->next) : NULL); \ + _hs_qsize--; \ + } else if ((_hs_qsize == 0U) || (_hs_q == NULL)) { \ + _hs_e = _hs_p; \ + if (_hs_p != NULL) { \ + _hs_p = ((_hs_p->next != NULL) ? \ + HH_FROM_ELMT((head)->hh.tbl, _hs_p->next) : NULL); \ + } \ + _hs_psize--; \ + } else if ((cmpfcn( \ + DECLTYPE(head)(ELMT_FROM_HH((head)->hh.tbl, _hs_p)), \ + DECLTYPE(head)(ELMT_FROM_HH((head)->hh.tbl, _hs_q)) \ + )) <= 0) { \ + _hs_e = _hs_p; \ + if (_hs_p != NULL) { \ + _hs_p = ((_hs_p->next != NULL) ? \ + HH_FROM_ELMT((head)->hh.tbl, _hs_p->next) : NULL); \ + } \ + _hs_psize--; \ + } else { \ + _hs_e = _hs_q; \ + _hs_q = ((_hs_q->next != NULL) ? \ + HH_FROM_ELMT((head)->hh.tbl, _hs_q->next) : NULL); \ + _hs_qsize--; \ + } \ + if ( _hs_tail != NULL ) { \ + _hs_tail->next = ((_hs_e != NULL) ? \ + ELMT_FROM_HH((head)->hh.tbl, _hs_e) : NULL); \ + } else { \ + _hs_list = _hs_e; \ + } \ + if (_hs_e != NULL) { \ + _hs_e->prev = ((_hs_tail != NULL) ? \ + ELMT_FROM_HH((head)->hh.tbl, _hs_tail) : NULL); \ + } \ + _hs_tail = _hs_e; \ + } \ + _hs_p = _hs_q; \ + } \ + if (_hs_tail != NULL) { \ + _hs_tail->next = NULL; \ + } \ + if (_hs_nmerges <= 1U) { \ + _hs_looping = 0; \ + (head)->hh.tbl->tail = _hs_tail; \ + DECLTYPE_ASSIGN(head, ELMT_FROM_HH((head)->hh.tbl, _hs_list)); \ + } \ + _hs_insize *= 2U; \ + } \ + HASH_FSCK(hh, head, "HASH_SRT"); \ + } \ +} while (0) + +/* This function selects items from one hash into another hash. + * The end result is that the selected items have dual presence + * in both hashes. There is no copy of the items made; rather + * they are added into the new hash through a secondary hash + * hash handle that must be present in the structure. */ +#define HASH_SELECT(hh_dst, dst, hh_src, src, cond) \ +do { \ + unsigned _src_bkt, _dst_bkt; \ + void *_last_elt = NULL, *_elt; \ + UT_hash_handle *_src_hh, *_dst_hh, *_last_elt_hh=NULL; \ + ptrdiff_t _dst_hho = ((char*)(&(dst)->hh_dst) - (char*)(dst)); \ + if ((src) != NULL) { \ + for (_src_bkt=0; _src_bkt < (src)->hh_src.tbl->num_buckets; _src_bkt++) { \ + for (_src_hh = (src)->hh_src.tbl->buckets[_src_bkt].hh_head; \ + _src_hh != NULL; \ + _src_hh = _src_hh->hh_next) { \ + _elt = ELMT_FROM_HH((src)->hh_src.tbl, _src_hh); \ + if (cond(_elt)) { \ + IF_HASH_NONFATAL_OOM( int _hs_oomed = 0; ) \ + _dst_hh = (UT_hash_handle*)(((char*)_elt) + _dst_hho); \ + _dst_hh->key = _src_hh->key; \ + _dst_hh->keylen = _src_hh->keylen; \ + _dst_hh->hashv = _src_hh->hashv; \ + _dst_hh->prev = _last_elt; \ + _dst_hh->next = NULL; \ + if (_last_elt_hh != NULL) { \ + _last_elt_hh->next = _elt; \ + } \ + if ((dst) == NULL) { \ + DECLTYPE_ASSIGN(dst, _elt); \ + HASH_MAKE_TABLE(hh_dst, dst, _hs_oomed); \ + IF_HASH_NONFATAL_OOM( \ + if (_hs_oomed) { \ + uthash_nonfatal_oom(_elt); \ + (dst) = NULL; \ + continue; \ + } \ + ) \ + } else { \ + _dst_hh->tbl = (dst)->hh_dst.tbl; \ + } \ + HASH_TO_BKT(_dst_hh->hashv, _dst_hh->tbl->num_buckets, _dst_bkt); \ + HASH_ADD_TO_BKT(_dst_hh->tbl->buckets[_dst_bkt], hh_dst, _dst_hh, _hs_oomed); \ + (dst)->hh_dst.tbl->num_items++; \ + IF_HASH_NONFATAL_OOM( \ + if (_hs_oomed) { \ + HASH_ROLLBACK_BKT(hh_dst, dst, _dst_hh); \ + HASH_DELETE_HH(hh_dst, dst, _dst_hh); \ + _dst_hh->tbl = NULL; \ + uthash_nonfatal_oom(_elt); \ + continue; \ + } \ + ) \ + HASH_BLOOM_ADD(_dst_hh->tbl, _dst_hh->hashv); \ + _last_elt = _elt; \ + _last_elt_hh = _dst_hh; \ + } \ + } \ + } \ + } \ + HASH_FSCK(hh_dst, dst, "HASH_SELECT"); \ +} while (0) + +#define HASH_CLEAR(hh,head) \ +do { \ + if ((head) != NULL) { \ + HASH_BLOOM_FREE((head)->hh.tbl); \ + uthash_free((head)->hh.tbl->buckets, \ + (head)->hh.tbl->num_buckets*sizeof(struct UT_hash_bucket)); \ + uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \ + (head) = NULL; \ + } \ +} while (0) + +#define HASH_OVERHEAD(hh,head) \ + (((head) != NULL) ? ( \ + (size_t)(((head)->hh.tbl->num_items * sizeof(UT_hash_handle)) + \ + ((head)->hh.tbl->num_buckets * sizeof(UT_hash_bucket)) + \ + sizeof(UT_hash_table) + \ + (HASH_BLOOM_BYTELEN))) : 0U) + +#ifdef NO_DECLTYPE +#define HASH_ITER(hh,head,el,tmp) \ +for(((el)=(head)), ((*(char**)(&(tmp)))=(char*)((head!=NULL)?(head)->hh.next:NULL)); \ + (el) != NULL; ((el)=(tmp)), ((*(char**)(&(tmp)))=(char*)((tmp!=NULL)?(tmp)->hh.next:NULL))) +#else +#define HASH_ITER(hh,head,el,tmp) \ +for(((el)=(head)), ((tmp)=DECLTYPE(el)((head!=NULL)?(head)->hh.next:NULL)); \ + (el) != NULL; ((el)=(tmp)), ((tmp)=DECLTYPE(el)((tmp!=NULL)?(tmp)->hh.next:NULL))) +#endif + +/* obtain a count of items in the hash */ +#define HASH_COUNT(head) HASH_CNT(hh,head) +#define HASH_CNT(hh,head) ((head != NULL)?((head)->hh.tbl->num_items):0U) + +typedef struct UT_hash_bucket { + struct UT_hash_handle *hh_head; + unsigned count; + + /* expand_mult is normally set to 0. In this situation, the max chain length + * threshold is enforced at its default value, HASH_BKT_CAPACITY_THRESH. (If + * the bucket's chain exceeds this length, bucket expansion is triggered). + * However, setting expand_mult to a non-zero value delays bucket expansion + * (that would be triggered by additions to this particular bucket) + * until its chain length reaches a *multiple* of HASH_BKT_CAPACITY_THRESH. + * (The multiplier is simply expand_mult+1). The whole idea of this + * multiplier is to reduce bucket expansions, since they are expensive, in + * situations where we know that a particular bucket tends to be overused. + * It is better to let its chain length grow to a longer yet-still-bounded + * value, than to do an O(n) bucket expansion too often. + */ + unsigned expand_mult; + +} UT_hash_bucket; + +/* random signature used only to find hash tables in external analysis */ +#define HASH_SIGNATURE 0xa0111fe1u +#define HASH_BLOOM_SIGNATURE 0xb12220f2u + +typedef struct UT_hash_table { + UT_hash_bucket *buckets; + unsigned num_buckets, log2_num_buckets; + unsigned num_items; + struct UT_hash_handle *tail; /* tail hh in app order, for fast append */ + ptrdiff_t hho; /* hash handle offset (byte pos of hash handle in element */ + + /* in an ideal situation (all buckets used equally), no bucket would have + * more than ceil(#items/#buckets) items. that's the ideal chain length. */ + unsigned ideal_chain_maxlen; + + /* nonideal_items is the number of items in the hash whose chain position + * exceeds the ideal chain maxlen. these items pay the penalty for an uneven + * hash distribution; reaching them in a chain traversal takes >ideal steps */ + unsigned nonideal_items; + + /* ineffective expands occur when a bucket doubling was performed, but + * afterward, more than half the items in the hash had nonideal chain + * positions. If this happens on two consecutive expansions we inhibit any + * further expansion, as it's not helping; this happens when the hash + * function isn't a good fit for the key domain. When expansion is inhibited + * the hash will still work, albeit no longer in constant time. */ + unsigned ineff_expands, noexpand; + + uint32_t signature; /* used only to find hash tables in external analysis */ +#ifdef HASH_BLOOM + uint32_t bloom_sig; /* used only to test bloom exists in external analysis */ + uint8_t *bloom_bv; + uint8_t bloom_nbits; +#endif + +} UT_hash_table; + +typedef struct UT_hash_handle { + struct UT_hash_table *tbl; + void *prev; /* prev element in app order */ + void *next; /* next element in app order */ + struct UT_hash_handle *hh_prev; /* previous hh in bucket order */ + struct UT_hash_handle *hh_next; /* next hh in bucket order */ + void *key; /* ptr to enclosing struct's key */ + unsigned keylen; /* enclosing struct's key len */ + unsigned hashv; /* result of hash-fcn(key) */ +} UT_hash_handle; + +#endif /* UTHASH_H */ diff --git a/uninstall_easy.sh b/uninstall_easy.sh new file mode 100755 index 0000000..6103800 --- /dev/null +++ b/uninstall_easy.sh @@ -0,0 +1,218 @@ +#!/bin/sh + +# automated script for easy uninstalling zapret + +SCRIPT=$(readlink -f "$0") +EXEDIR=$(dirname "$SCRIPT") +GET_IPLIST_PREFIX=/ipset/get_ +SYSTEMD_SYSTEM_DIR=/lib/systemd/system +[ -d "$SYSTEMD_SYSTEM_DIR" ] || SYSTEMD_SYSTEM_DIR=/usr/lib/systemd/system + +exists() +{ + which $1 >/dev/null 2>/dev/null +} +whichq() +{ + which $1 2>/dev/null +} + +exitp() +{ + echo + echo press enter to continue + read A + exit $1 +} + +[ $(id -u) -ne "0" ] && { + echo root is required + exists sudo && exec sudo "$0" + exists su && exec su -c "$0" + echo su or sudo not found + exitp 2 +} + +md5file() +{ + md5sum "$1" | cut -f1 -d ' ' +} + + +check_system() +{ + echo \* checking system + + SYSTEM="" + SYSTEMCTL=$(whichq systemctl) + + if [ -x "$SYSTEMCTL" ] ; then + SYSTEM=systemd + elif [ -f "/etc/openwrt_release" ] && exists opkg && exists uci ; then + SYSTEM=openwrt + else + echo system is not either systemd based or openwrt + exitp 5 + fi + echo system is based on $SYSTEM +} + + +crontab_del() +{ + exists crontab || return + + echo \* removing crontab entry + + CRONTMP=/tmp/cron.tmp + crontab -l >$CRONTMP + if grep -q "$GET_IPLIST_PREFIX" $CRONTMP; then + echo removing following entries from crontab : + grep "$GET_IPLIST_PREFIX" $CRONTMP + grep -v "$GET_IPLIST_PREFIX" $CRONTMP >$CRONTMP.2 + crontab $CRONTMP.2 + rm -f $CRONTMP.2 + fi + rm -f $CRONTMP +} + + +service_stop_systemd() +{ + echo \* stopping zapret service + + "$SYSTEMCTL" daemon-reload + "$SYSTEMCTL" disable zapret + "$SYSTEMCTL" stop zapret +} + +service_remove_systemd() +{ + echo \* removing zapret service + + rm -f "$SYSTEMD_SYSTEM_DIR/zapret.service" + "$SYSTEMCTL" daemon-reload +} + +timer_remove_systemd() +{ + echo \* removing zapret-list-update timer + + "$SYSTEMCTL" daemon-reload + "$SYSTEMCTL" disable zapret-list-update.timer + "$SYSTEMCTL" stop zapret-list-update.timer + rm -f "$SYSTEMD_SYSTEM_DIR/zapret-list-update.service" "$SYSTEMD_SYSTEM_DIR/zapret-list-update.timer" + "$SYSTEMCTL" daemon-reload +} + + + +remove_systemd() +{ + INIT_SCRIPT=/etc/init.d/zapret + + service_stop_systemd + service_remove_systemd + timer_remove_systemd + crontab_del +} + + + + + +openwrt_fw_section_find() +{ + # $1 - fw include postfix + # echoes section number + + i=0 + while true + do + path=$(uci -q get firewall.@include[$i].path) + [ -n "$path" ] || break + [ "$path" = "$OPENWRT_FW_INCLUDE$1" ] && { + echo $i + return + } + i=$(($i+1)) + done + false + return +} +openwrt_fw_section_del() +{ + # $1 - fw include postfix + + local id=$(openwrt_fw_section_find $1) + [ -n "$id" ] && { + uci delete firewall.@include[$id] && uci commit firewall + rm -f "$OPENWRT_FW_INCLUDE$1" + } +} + +remove_openwrt_firewall() +{ + echo \* removing firewall script + + openwrt_fw_section_del + # from old zapret versions. now we use single include + openwrt_fw_section_del 6 +} + +restart_openwrt_firewall() +{ + echo \* restarting firewall + + fw3 -q restart || { + echo could not restart firewall + exitp 30 + } +} + +remove_openwrt_iface_hook() +{ + echo \* removing ifup hook + + rm -f /etc/hotplug.d/iface/??-zapret +} + + +service_remove_sysv() +{ + echo \* removing zapret service + + [ -x "$INIT_SCRIPT" ] && { + "$INIT_SCRIPT" disable + "$INIT_SCRIPT" stop + } + rm -f "$INIT_SCRIPT" +} + +remove_openwrt() +{ + INIT_SCRIPT=/etc/init.d/zapret + OPENWRT_FW_INCLUDE=/etc/firewall.zapret + + remove_openwrt_firewall + restart_openwrt_firewall + service_remove_sysv + remove_openwrt_iface_hook + crontab_del +} + + + +check_system + +case $SYSTEM in + systemd) + remove_systemd + ;; + openwrt) + remove_openwrt + ;; +esac + + +exitp 0