diff --git a/Makefile b/Makefile deleted file mode 100644 index 5f6cfc06..00000000 --- a/Makefile +++ /dev/null @@ -1,74 +0,0 @@ -DIRS := nfq tpws ip2net mdig -DIRS_MAC := tpws ip2net mdig -TGT := binaries/my - -all: clean - @mkdir -p "$(TGT)"; \ - for dir in $(DIRS); do \ - find "$$dir" -type f \( -name "*.c" -o -name "*.h" -o -name "*akefile" \) -exec chmod -x {} \; ; \ - $(MAKE) -C "$$dir" || exit; \ - for exe in "$$dir/"*; do \ - if [ -f "$$exe" ] && [ -x "$$exe" ]; then \ - mv -f "$$exe" "${TGT}" ; \ - ln -fs "../${TGT}/$$(basename "$$exe")" "$$exe" ; \ - fi \ - done \ - done - -systemd: clean - @mkdir -p "$(TGT)"; \ - for dir in $(DIRS); do \ - find "$$dir" -type f \( -name "*.c" -o -name "*.h" -o -name "*akefile" \) -exec chmod -x {} \; ; \ - $(MAKE) -C "$$dir" systemd || 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 - -android: clean - @mkdir -p "$(TGT)"; \ - for dir in $(DIRS); do \ - find "$$dir" -type f \( -name "*.c" -o -name "*.h" -o -name "*akefile" \) -exec chmod -x {} \; ; \ - $(MAKE) -C "$$dir" android || exit; \ - for exe in "$$dir/"*; do \ - if [ -f "$$exe" ] && [ -x "$$exe" ]; then \ - mv -f "$$exe" "${TGT}" ; \ - ln -fs "../${TGT}/$$(basename "$$exe")" "$$exe" ; \ - fi \ - done \ - done - -bsd: clean - @mkdir -p "$(TGT)"; \ - for dir in $(DIRS); do \ - find "$$dir" -type f \( -name "*.c" -o -name "*.h" -o -name "*akefile" \) -exec chmod -x {} \; ; \ - $(MAKE) -C "$$dir" bsd || exit; \ - for exe in "$$dir/"*; do \ - if [ -f "$$exe" ] && [ -x "$$exe" ]; then \ - mv -f "$$exe" "${TGT}" ; \ - ln -fs "../${TGT}/$$(basename "$$exe")" "$$exe" ; \ - fi \ - done \ - done - -mac: clean - @mkdir -p "$(TGT)"; \ - for dir in $(DIRS_MAC); do \ - find "$$dir" -type f \( -name "*.c" -o -name "*.h" -o -name "*akefile" \) -exec chmod -x {} \; ; \ - $(MAKE) -C "$$dir" mac || exit; \ - for exe in "$$dir/"*; do \ - if [ -f "$$exe" ] && [ -x "$$exe" ]; then \ - mv -f "$$exe" "${TGT}" ; \ - ln -fs "../${TGT}/$$(basename "$$exe")" "$$exe" ; \ - fi \ - done \ - done - -clean: - @[ -d "$(TGT)" ] && rm -rf "$(TGT)" ; \ - for dir in $(DIRS); do \ - $(MAKE) -C "$$dir" clean; \ - done diff --git a/docs/LICENSE.txt b/docs/LICENSE.txt deleted file mode 100644 index 6f8f4cc5..00000000 --- a/docs/LICENSE.txt +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2016-2024 bol-van - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/docs/bsd.en.md b/docs/bsd.en.md deleted file mode 100644 index c561985a..00000000 --- a/docs/bsd.en.md +++ /dev/null @@ -1,627 +0,0 @@ -## Table of contents - -- [Table of contents](#table-of-contents) -- [Supported versions](#supported-versions) -- [BSD features](#bsd-features) -- [FreeBSD](#freebsd) - - [`dvtws` quick start](#dvtws-quick-start) - - [PF in FreeBSD](#pf-in-freebsd) - - [`pfsense`](#pfsense) -- [OpenBSD](#openbsd) -- [MacOS](#macos) - - [MacOS easy install](#macos-easy-install) - -## Supported versions - -FreeBSD 11.x+ , OpenBSD 6.x+, partially MacOS Sierra+ - -Older versions may work or not. - -## BSD features - -BSD does not have NFQUEUE. Similar mechanism - divert sockets. In BSD compiling -the source from nfq directory result in `dvtws` binary instead of `nfqws`. -`dvtws` shares most of the code with `nfqws` and offers almost identical -parameters. - -FreeBSD has 3 firewalls: IPFilter, ipfw and Packet Filter (PF). OpenBSD has -only PF. - -To compile sources: - -- FreeBSD: `make` -- OpenBSD: `make bsd` -- MacOS: `make mac` - -Compile all programs: -``` -make -C /opt/zapret -``` - -Divert sockets are internal type sockets in the BSD kernel. They have no -relation to network addresses or network packet exchange. They are identified -by a port number `1..65535`. Its like queue number in NFQUEUE. Traffic can be -diverted to a divert socket using firewall rule. If nobody listens on the -specified divert port packets are dropped. Its similar to NFQUEUE without -`--queue-bypass`. - -`ipset/*.sh` scripts work with ipfw lookup tables if ipfw is present. - -ipfw table is analog to linux `ipset`. Unlike ipsets ipfw tables share v4 an v6 -addresses and subnets. - -- If ipfw is absent scripts check LISTS_RELOAD config variable. -- If its present then scripts execute a command from LISTS_RELOAD. -- If LISTS_RELOAD=- scripts do not load tables even if ipfw exists. - -PF can load ip tables from a file. To use this feature with `ipset/*.sh` scripts disable gzip file creation -using `GZIP_LISTS=0` directive in the `/opt/zapret/config` file. - -BSD kernel doesn't implement splice syscall. tpws uses regular recv/send -operations with data copying to user space. Its slower but not critical. - -`tpws` uses nonblocking sockets with linux specific epoll feature. In BSD systems -epoll is emulated by epoll-shim library on top of kqueue. - -`dvtws` uses some programming HACKs, assumptions and knowledge of discovered -bugs and limitations. BSD systems have many limitations, version specific -features and bugs in low level networking, especially for ipv6. Many years have -passed but BSD code still has 15-20 year artificial limiters in the code. `dvtws` -uses additinal divert socket(s) for layer 3 packet injection if raw sockets do -not allow it. It works for the moment but who knows. Such a usage is not very -documented. - -`mdig` and `ip2net` are fully compatible with BSD. - - -## FreeBSD - -Divert sockets require special kernel module `ipdivert`. -Write the following to config files: - -`/boot/loader.conf` (create if absent): -``` -ipdivert_load="YES" -net.inet.ip.fw.default_to_accept=1 -``` - -`/etc/rc.conf`: -``` -firewall_enable="YES" -firewall_script="/etc/rc.firewall.my" -``` - -`/etc/rc.firewall.my`: -``` -ipfw -q -f flush -``` - -Later you will add ipfw commands to `/etc/rc.firewall.my` to be reapplied after reboot. -You can also run zapret daemons from there. Start them with `--daemon` options, for example -``` -pkill ^dvtws$ -/opt/zapret/nfq/dvtws --port=989 --daemon --dpi-desync=multisplit --dpi-desync-split-pos=2 -``` - -To restart firewall and daemons run : `/etc/rc.d/ipfw restart` - -Assume `LAN="em1"`, `WAN="em0"`. - -`tpws` transparent mode quick start. - -For all traffic: -``` -ipfw delete 100 -ipfw add 100 fwd 127.0.0.1,988 tcp from me to any 80,443 proto ip4 xmit em0 not uid daemon -ipfw add 100 fwd ::1,988 tcp from me to any 80,443 proto ip6 xmit em0 not uid daemon -ipfw add 100 fwd 127.0.0.1,988 tcp from any to any 80,443 proto ip4 recv em1 -ipfw add 100 fwd ::1,988 tcp from any to any 80,443 proto ip6 recv em1 -/opt/zapret/tpws/tpws --port=988 --user=daemon --bind-addr=::1 --bind-addr=127.0.0.1 -``` - -Process only table zapret with the exception of table nozapret: -``` -ipfw delete 100 -ipfw add 100 allow tcp from me to table\(nozapret\) 80,443 -ipfw add 100 fwd 127.0.0.1,988 tcp from me to table\(zapret\) 80,443 proto ip4 xmit em0 not uid daemon -ipfw add 100 fwd ::1,988 tcp from me to table\(zapret\) 80,443 proto ip6 xmit em0 not uid daemon -ipfw add 100 allow tcp from any to table\(nozapret\) 80,443 recv em1 -ipfw add 100 fwd 127.0.0.1,988 tcp from any to any 80,443 proto ip4 recv em1 -ipfw add 100 fwd ::1,988 tcp from any to any 80,443 proto ip6 recv em1 -/opt/zapret/tpws/tpws --port=988 --user=daemon --bind-addr=::1 --bind-addr=127.0.0.1 -``` - -Tables zapret, nozapret, ipban are created by `ipset/*.sh` scripts the same way as in Linux. -Its a good idea to update tables periodically: -``` - crontab -e -``` - -Write the line: -``` -0 12 */2 * * /opt/zapret/ipset/get_config.sh -``` - -When using `ipfw`, `tpws` does not require special permissions for transparent -mode. However without root its not possible to bind to ports less than 1024 and -change UID/GID. Without changing UID tpws will run into recursive loop, and -that's why its necessary to write ipfw rules with the right UID. Redirecting to -ports greater than or equal to 1024 is dangerous. If tpws is not running any -unprivileged process can listen to that port and intercept traffic. - -### `dvtws` quick start - -For all traffic: -``` -ipfw delete 100 -ipfw add 100 divert 989 tcp from any to any 80,443 out not diverted not sockarg xmit em0 -# required for autottl mode only -ipfw add 100 divert 989 tcp from any 80,443 to any tcpflags syn,ack in not diverted not sockarg recv em0 -/opt/zapret/nfq/dvtws --port=989 --dpi-desync=multisplit --dpi-desync-split-pos=2 -``` - -Process only table zapret with the exception of table nozapret: -``` -ipfw delete 100 -ipfw add 100 allow tcp from me to table\(nozapret\) 80,443 -ipfw add 100 divert 989 tcp from any to table\(zapret\) 80,443 out not diverted not sockarg xmit em0 -# required for autottl mode only -ipfw add 100 divert 989 tcp from table\(zapret\) 80,443 to any tcpflags syn,ack in not diverted not sockarg recv em0 -/opt/zapret/nfq/dvtws --port=989 --dpi-desync=multisplit --dpi-desync-split-pos=2 -``` - -Reinjection loop avoidance. FreeBSD artificially ignores sockarg for ipv6 in -the kernel. This limitation is coming from the ipv6 early age. Code is still in -"testing" state. 10-20 years. Everybody forgot about it. `dvtws` sends ipv6 -forged frames using another divert socket (HACK). they can be filtered out -using 'diverted'. ipv4 frames are filtered using 'sockarg'. - -### PF in FreeBSD - -The setup is similar to OpenBSD, but there are important nuances. -1. PF support is disabled by default in FreeBSD. Use parameter `--enable-pf`. -2. It's not possible to redirect to `::1`. Need to redirect to the link-local - address of the incoming interface. Look for fe80:... address in ifconfig and - use it for redirection target. -3. pf.conf syntax is a bit different from OpenBSD. -4. How to set maximum table size : sysctl net.pf.request_maxcount=2000000 -5. `divert-to` is broken. Loop avoidance scheme does not work. - This makes `dvtws` unusable with pf. - Someone posted kernel patch but 14-RELEASE is still broken. - -`/etc/pf.conf`: -``` -rdr pass on em1 inet6 proto tcp to port {80,443} -> fe80::31c:29ff:dee2:1c4d port 988 -rdr pass on em1 inet proto tcp to port {80,443} -> 127.0.0.1 port 988 -``` - -Then: -``` -/opt/zapret/tpws/tpws --port=988 --enable-pf --bind-addr=127.0.0.1 --bind-iface6=em1 --bind-linklocal=force -``` - -Its not clear how to do rdr-to outgoing traffic. I could not make route-to -scheme work. - - -### `pfsense` - -`pfsense` is based on FreeBSD. Binaries from `binaries/freebsd-x64` are -compiled in FreeBSD 11 and should work. Use `install_bin.sh`. pfsense uses pf -firewall which does not support divert. Fortunately ipfw and ipdivert modules -are present and can be kldload-ed. In older versions it's also necessary to -change firewall order using sysctl commands. In newer versions those sysctl -parameters are absent but the system behaves as required without them. -Sometimes pf may limit `dvtws` abilities. It scrubs ip fragments disabling `dvtws` -ipfrag2 desync mode. - -There's autostart script example in `init.d/pfsense`. It should be placed to -`/usr/local/etc/rc.d` and edited. Write your ipfw rules and daemon start -commands. -curl is present by default. You can use it to download `tar.gz` release directly from github. -Or you can copy files using sftp. - -Copy zip with zapret files to `/opt` and unpack there as it's done in other -systems. In this case run `dvtws` as `/opt/zapret/nfq/dvtws`. Or just copy -`dvtws` to `/usr/local/sbin`. As you wish. `ipset` scripts are working, cron is -present. It's possible to renew lists. - -If you dont like poverty of default repos its possible to enable FreeBSD repo. -Change `no` to `yes` in `/usr/local/etc/pkg/repos/FreeBSD.conf` and `/usr/local/etc/pkg/repos/pfSense.conf`. - -`/usr/local/etc/rc.d/zapret.sh` (chmod 755) -``` -#!/bin/sh - -kldload ipfw -kldload ipdivert - -# for older pfsense versions. newer do not have these sysctls -sysctl net.inet.ip.pfil.outbound=ipfw,pf -sysctl net.inet.ip.pfil.inbound=ipfw,pf -sysctl net.inet6.ip6.pfil.outbound=ipfw,pf -sysctl net.inet6.ip6.pfil.inbound=ipfw,pf - -ipfw delete 100 -ipfw add 100 divert 989 tcp from any to any 80,443 out not diverted not sockarg xmit em0 -pkill ^dvtws$ -dvtws --daemon --port 989 --dpi-desync=multisplit --dpi-desync-split-pos=2 - -# required for newer pfsense versions (2.6.0 tested) to return ipfw to functional state -pfctl -d ; pfctl -e -``` - -I could not make tpws work from ipfw. Looks like there's some conflict between -two firewalls. Only PF redirection works. PF does not allow to freely add and -delete rules. Only anchors can be reloaded. To make an anchor work it must be -referred from the main ruleset. But its managed by pfsense scripts. - -One possible solution would be to modify `/etc/inc/filter.inc` as follows: -``` - ................. - /* MOD */ - $natrules .= "# ZAPRET redirection\n"; - $natrules .= "rdr-anchor \"zapret\"\n"; - - $natrules .= "# TFTP proxy\n"; - $natrules .= "rdr-anchor \"tftp-proxy/*\"\n"; - ................. -``` - -Write the anchor code to `/etc/zapret.anchor`: -``` -rdr pass on em1 inet proto tcp to port {80,443} -> 127.0.0.1 port 988 -rdr pass on em1 inet6 proto tcp to port {80,443} -> fe80::20c:29ff:5ae3:4821 port 988 -``` -Replace `fe80::20c:29ff:5ae3:4821` with your link local address of the LAN -interface or remove the line if ipv6 is not needed. - -Autostart `/usr/local/etc/rc.d/zapret.sh`: -``` -pfctl -a zapret -f /etc/zapret.anchor -pkill ^tpws$ -tpws --daemon --port=988 --enable-pf --bind-addr=127.0.0.1 --bind-iface6=em1 --bind-linklocal=force --split-pos=2 -``` - -After reboot check that anchor is created and referred from the main ruleset: -``` -[root@pfSense /]# pfctl -s nat -no nat proto carp all -nat-anchor "natearly/*" all -nat-anchor "natrules/*" all -................... -no rdr proto carp all -rdr-anchor "zapret" all -rdr-anchor "tftp-proxy/*" all -rdr-anchor "miniupnpd" all -[root@pfSense /]# pfctl -s nat -a zapret -rdr pass on em1 inet proto tcp from any to any port = http -> 127.0.0.1 port 988 -rdr pass on em1 inet proto tcp from any to any port = https -> 127.0.0.1 port 988 -rdr pass on em1 inet6 proto tcp from any to any port = http -> fe80::20c:29ff:5ae3:4821 port 988 -rdr pass on em1 inet6 proto tcp from any to any port = https -> fe80::20c:29ff:5ae3:4821 port 988 -``` - -Also there's a way to add redirect in the pfsense UI and start `tpws` from cron using `@reboot` prefix. -This way avoids modification of pfsense code. - -## OpenBSD - -In OpenBSD default `tpws` bind is ipv6 only. To bind to ipv4 specify -`--bind-addr=0.0.0.0`. - -Use `--bind-addr=0.0.0.0 --bind-addr=::` to achieve the same default bind as in -others OSes. - -`tpws` for forwarded traffic only (OLDER OS versions): - -`/etc/pf.conf`: -``` -pass in quick on em1 inet proto tcp to port {80,443} rdr-to 127.0.0.1 port 988 -pass in quick on em1 inet6 proto tcp to port {80,443} rdr-to ::1 port 988 -``` - -Then: -``` -pfctl -f /etc/pf.conf -tpws --port=988 --user=daemon --bind-addr=::1 --bind-addr=127.0.0.1 --enable-pf -``` - -Its not clear how to do rdr-to outgoing traffic. I could not make route-to -scheme work. rdr-to support is done using /dev/pf, that's why transparent mode -requires root. - -`tpws` for forwarded traffic only (NEWER OS versions): - -``` -pass on em1 inet proto tcp to port {80,443} divert-to 127.0.0.1 port 989 -pass on em1 inet6 proto tcp to port {80,443} divert-to ::1 port 989 -``` - -Then: -``` -pfctl -f /etc/pf.conf -tpws --port=988 --user=daemon --bind-addr=::1 --bind-addr=127.0.0.1 -``` - -tpws must be bound exactly to diverted IPs, not `0.0.0.0` or `::`. - -It's also not clear how to divert connections from local system. - - -`dvtws` for all traffic: - -`/etc/pf.conf`: -``` -pass in quick on em0 proto tcp from port {80,443} flags SA/SA divert-packet port 989 no state -pass in quick on em0 proto tcp from port {80,443} no state -pass out quick on em0 proto tcp to port {80,443} divert-packet port 989 -``` - -Then: -``` -pfctl -f /etc/pf.conf -./dvtws --port=989 --dpi-desync=multisplit --dpi-desync-split-pos=2 -``` - -`dwtws` only for table zapret with the exception of table nozapret : - -`/etc/pf.conf`: -``` -set limit table-entries 2000000 -table file "/opt/zapret/ipset/zapret-ip.txt" -table file "/opt/zapret/ipset/zapret-ip-user.txt" -table file "/opt/zapret/ipset/zapret-ip-exclude.txt" -pass out quick on em0 inet proto tcp to port {80,443} -pass in quick on em0 inet proto tcp from port {80,443} flags SA/SA divert-packet port 989 no state -pass in quick on em0 inet proto tcp from port {80,443} no state -pass out quick on em0 inet proto tcp to port {80,443} divert-packet port 989 no state -pass in quick on em0 inet proto tcp from port {80,443} flags SA/SA divert-packet port 989 no state -pass in quick on em0 inet proto tcp from port {80,443} no state -pass out quick on em0 inet proto tcp to port {80,443} divert-packet port 989 no state -table file "/opt/zapret/ipset/zapret-ip6.txt" -table file "/opt/zapret/ipset/zapret-ip-user6.txt" -table file "/opt/zapret/ipset/zapret-ip-exclude6.txt" -pass out quick on em0 inet6 proto tcp to port {80,443} -pass in quick on em0 inet6 proto tcp from port {80,443} flags SA/SA divert-packet port 989 no state -pass in quick on em0 inet6 proto tcp from port {80,443} no state -pass out quick on em0 inet6 proto tcp to port {80,443} divert-packet port 989 no state -pass in quick on em0 inet6 proto tcp from port {80,443} flags SA/SA divert-packet port 989 no state -pass in quick on em0 inet6 proto tcp from port {80,443} no state -pass out quick on em0 inet6 proto tcp to port {80,443} divert-packet port 989 no state -``` - -Then: -``` -pfctl -f /etc/pf.conf -./dvtws --port=989 --dpi-desync=multisplit --dpi-desync-split-pos=2 -``` - -divert-packet automatically adds the reverse rule. By default also incoming -traffic will be passwed to `dvtws`. This is highly undesired because it is waste -of cpu resources and speed limiter. The trick with "no state" and "in" rules -allows to bypass auto reverse rule. - -`dvtws` in OpenBSD sends all fakes through a divert socket because raw sockets -have critical artificial limitations. Looks like pf automatically prevent -reinsertion of diverted frames. Loop problem does not exist. - -OpenBSD forcibly recomputes tcp checksum after divert. Thats why most likely -dpi-desync-fooling=badsum will not work. `dvtws` will warn if you specify this -parameter. - -`ipset` scripts do not reload PF by default. To enable reload specify command in -`/opt/zapret/config`: -``` -LISTS_RELOAD="pfctl -f /etc/pf.conf" -``` - -Newer `pfctl` versions can reload tables only: -``` -pfctl -Tl -f /etc/pf.conf -``` - -But OpenBSD 6.8 `pfctl` is old enough and does not support that. Newer FreeBSD do. - -Don't forget to disable gzip compression: -``` -GZIP_LISTS=0 -``` - -If some list files do not exist and have references in pf.conf it leads to -error. You need to exclude those tables from pf.conf and referencing them -rules. After configuration is done you can put `ipset` script: -``` - crontab -e -``` - -Then write the line: -``` -0 12 */2 * * /opt/zapret/ipset/get_config.sh -``` - -## MacOS - -Initially, the kernel of this OS was based on BSD. That's why it is still BSD -but a lot was modified by Apple. As usual a mass commercial project priorities -differ from their free counterparts. Apple guys do what they want. - -MacOS used to have ipfw but it was removed later and replaced by PF. It looks -like divert sockets are internally replaced with raw. Its possible to request a -divert socket but it behaves exactly as raw socket with all its BSD inherited + -apple specific bugs and feature. The fact is that divert-packet in -`/etc/pf.conf` does not work. pfctl binary does not contain the word `divert`. - -`dvtws` does compile but is useless. - -After some efforts `tpws` works. Apple has removed some important stuff from -their newer SDKs (DIOCNATLOOK) making them undocumented and unsupported. - -With important definitions copied from an older SDK it was possible to make -transparent mode working again. But this is not guaranteed to work in the -future versions. - -Another MacOS unique feature is root requirement while polling `/dev/pf`. - -By default tpws drops root. Its necessary to specify `--user=root` to stay with -root. - -In other aspects PF behaves very similar to FreeBSD and shares the same pf.conf -syntax. - -In MacOS redirection works both for passthrough and outgoing traffic. Outgoing -redirection requires route-to rule. Because tpws is forced to run as root to -avoid loop its necessary to exempt root from the redirection. That's why DPI -bypass will not work for local requests from root. - -If you do ipv6 routing you have to get rid of "secured" ipv6 address -assignment. - -"secured" addresses are designed to be permanent and not related to the MAC -address. - -And they really are. Except for link-locals. - -If you just reboot the system link-locals will not change. But next day they -will change. - -Not necessary to wait so long. Just change the system time to tomorrow and reboot. -Link-locals will change (at least they change in vmware guest). Looks like its a kernel bug. -Link locals should not change. Its useless and can be harmful. Cant use LL as a gateway. - -The easiest solution is to disable "secured" addresses. - -Outgoing connections prefer randomly generated temporary addressesas like in other systems. - -Put the string `net.inet6.send.opmode=0` to `/etc/sysctl.conf`. If not present -- create it. - -Then reboot the system. - -If you dont like this solution you can assign an additional static ipv6 address -from `fc00::/7` range with `/128` prefix to your LAN interface and use it as -the gateway address. - -`tpws` transparent mode only for outgoing connections. - -`/etc/pf.conf`: -``` -rdr pass on lo0 inet proto tcp from !127.0.0.0/8 to any port {80,443} -> 127.0.0.1 port 988 -rdr pass on lo0 inet6 proto tcp from !::1 to any port {80,443} -> fe80::1 port 988 -pass out route-to (lo0 127.0.0.1) inet proto tcp from any to any port {80,443} user { >root } -pass out route-to (lo0 fe80::1) inet6 proto tcp from any to any port {80,443} user { >root } -``` - -Then: -``` -pfctl -ef /etc/pf.conf -/opt/zapret/tpws/tpws --user=root --port=988 --bind-addr=127.0.0.1 --bind-iface6=lo0 --bind-linklocal=force -``` - -`tpws` transparent mode for both passthrough and outgoing connections. en1 - LAN. - -``` -ifconfig en1 | grep fe80 - inet6 fe80::bbbb:bbbb:bbbb:bbbb%en1 prefixlen 64 scopeid 0x8 -``` - -`/etc/pf.conf`: -``` -rdr pass on en1 inet proto tcp from any to any port {80,443} -> 127.0.0.1 port 988 -rdr pass on en1 inet6 proto tcp from any to any port {80,443} -> fe80::bbbb:bbbb:bbbb:bbbb port 988 -rdr pass on lo0 inet proto tcp from !127.0.0.0/8 to any port {80,443} -> 127.0.0.1 port 988 -rdr pass on lo0 inet6 proto tcp from !::1 to any port {80,443} -> fe80::1 port 988 -pass out route-to (lo0 127.0.0.1) inet proto tcp from any to any port {80,443} user { >root } -pass out route-to (lo0 fe80::1) inet6 proto tcp from any to any port {80,443} user { >root } -``` - -Then: -``` -pfctl -ef /etc/pf.conf -/opt/zapret/tpws/tpws --user=root --port=988 --bind-addr=127.0.0.1 --bind-iface6=lo0 --bind-linklocal=force --bind-iface6=en1 --bind-linklocal=force -``` - -Build from source : `make -C /opt/zapret mac` - -`ipset/*.sh` scripts work. - - -### MacOS easy install - -`install_easy.sh` supports MacOS - -Shipped precompiled binaries are built for 64-bit MacOS with -`-mmacosx-version-min=10.8` option. They should run on all supported MacOS -versions. If no - its easy to build your own. Running `make` automatically -installs developer tools. - -**WARNING**: -**Internet sharing is not supported!** - -Routing is supported but only manually configured through PF. If you enable -internet sharing tpws stops functioning. When you disable internet sharing you -may lose web site access. - -To fix: -``` -pfctl -f /etc/pf.conf -``` - -If you need internet sharing use `tpws` socks mode. - -`launchd` is used for autostart (`/Library/LaunchDaemons/zapret.plist`) - -Control script: `/opt/zapret/init.d/macos/zapret` - -The following commands fork with both tpws and firewall (if `INIT_APPLY_FW=1` in config) -``` -/opt/zapret/init.d/macos/zapret start -/opt/zapret/init.d/macos/zapret stop -/opt/zapret/init.d/macos/zapret restart -``` - -Work with `tpws` only: -``` -/opt/zapret/init.d/macos/zapret start-daemons -/opt/zapret/init.d/macos/zapret stop-daemons -/opt/zapret/init.d/macos/zapret restart-daemons -``` - -Work with PF only: -``` -/opt/zapret/init.d/macos/zapret start-fw -/opt/zapret/init.d/macos/zapret stop-fw -/opt/zapret/init.d/macos/zapret restart-fw -``` - -Reloading PF tables: -``` -/opt/zapret/init.d/macos/zapret reload-fw-tables -``` - -Installer configures `LISTS_RELOAD` in the config so `ipset *.sh` scripts -automatically reload PF tables. Installer creates cron job for `ipset -/get_config.sh`, as in OpenWRT. - -start-fw script automatically patches `/etc/pf.conf` inserting there `zapret` -anchors. Auto patching requires pf.conf with apple anchors preserved. If your -`pf.conf` is highly customized and patching fails you will see the warning. Do -not ignore it. - -In that case you need to manually insert "zapret" anchors to your `pf.conf` -(keeping the right rule type ordering): -``` -rdr-anchor "zapret" -anchor "zapret" -unistall_easy.sh unpatches pf.conf -``` -start-fw creates 3 anchor files in `/etc/pf.anchors` : -zapret,zapret-v4,zapret-v6. - -- Last 2 are referenced by anchor `zapret`. -- Tables `nozapret`,`nozapret6` belong to anchor `zapret`. -- Tables `zapret`,`zapret-user` belong to anchor `zapret-v4`. -- Tables `zapret6`,`apret6-user` belong to anchor `zapret-v6`. - -If an ip version is disabled then corresponding anchor is empty and is not -referenced from the anchor `zapret`. Tables are only created for existing list -files in the `ipset` directory. diff --git a/docs/bsd.md b/docs/bsd.md deleted file mode 100644 index 5315c915..00000000 --- a/docs/bsd.md +++ /dev/null @@ -1,750 +0,0 @@ -# Настройка BSD-подобных систем - -* [Поддерживаемые версии](#поддерживаемые-версии) -* [Особенности BSD систем](#особенности-bsd-систем) - * [Отсутствие nfqueue](#отсутствие-nfqueue) - * [Типы Firewall](#типы-firewall) - * [Сборка](#сборка) - * [Divert сокеты](#divert-сокеты) - * [Lookup Tables](#lookup-tables) - * [Загрузка ip таблиц из файла](#загрузка-ip-таблиц-из-файла) - * [Отсутствие splice](#отсутствие-splice) - * [mdig и ip2net](#mdig-и-ip2net) -* [FreeBSD](#freebsd) - * [Подгрузка ipdivert](#подгрузка-ipdivert) - * [Авто-восстановление правил ipfw и работа в фоне](#авто-восстановление-правил-ipfw-и-работа-в-фоне) - * [tpws в прозрачном режиме](#tpws-в-прозрачном-режиме) - * [Запуск dvtws](#запуск-dvtws) - * [PF в FreeBSD](#pf-в-freebsd) - * [pfsense](#pfsense) -* [OpenBSD](#openbsd) - * [tpws bind на ipv4](#tpws-bind-на-ipv4) - * [tpws для проходящего трафика (старые системы)](#tpws-для-проходящего-трафика-старая-схема-не-работает-в-новых-версиях)) - * [tpws для проходящего трафика (новые системы)](#tpws-для-проходящего-трафика-новые-системы)) - * [Запуск dvtws](#запуск-dvtws) - * [Проблемы с badsum](#проблемы-с-badsum) - * [Особенность отправки fake пакетов](#особенность-отправки-fake-пакетов) - * [Перезагрузка PF таблиц](#перезагрузка-pf-таблиц) -* [MacOS](#macos) - * [Введение](#введение) - * [dvtws бесполезен](#dvtws-бесполезен) - * [tpws](#tpws) - * [Проблема link-local адреса](#проблема-link-local-адреса) - * [Сборка](#сборка) - * [Простая установка](#простая-установка) - * [Вариант Custom](#вариант-custom) - - -## Поддерживаемые версии -**FreeBSD** 11.x+ , **OpenBSD** 6.x+, частично **MacOS Sierra** + - -> [!CAUTION] -> На более старых может собираться, может не собираться, может работать или не -> работать. На **FreeBSD** 10 собирается и работает `dvtws`. С `tpws` есть -> проблемы из-за слишком старой версии компилятора clang. Вероятно, будет -> работать, если обновить компилятор. Возможна прикрутка к последним версиям -> pfsense без веб интерфейса в ручном режиме через консоль. - - -## Особенности BSD систем - -### Отсутствие nfqueue -В **BSD** нет `nfqueue`. Похожий механизм - divert sockets. Из каталога -[`nfq/`](../nfq/) под **BSD** собирается `dvtws` вместо `nfqws`. Он разделяет с -`nfqws` большую часть кода и почти совпадает по параметрам командной строки. - -### Типы Firewall -**FreeBSD** содержит 3 фаервола : **IPFilter**, **ipfw** и **Packet Filter (PF -в дальнейшем)**. **OpenBSD** содержит только **PF**. - -### Сборка -Под **FreeBSD** `tpws` и `dvtws` собираются через `make`. - -Под **OpenBSD**: -```sh -make bsd -``` - -Под **MacOS**: -```sh -make mac -``` - -**FreeBSD** make распознает BSDmakefile, **OpenBSD** и **MacOS** - нет. Поэтому -там используется отдельный target в Makefile. Сборка всех исходников: -```sh -make -C /opt/zapret -``` - -### Divert сокеты -Divert сокет это внутренний тип сокета ядра **BSD**. Он не привязывается ни к -какому сетевому адресу, не участвует в обмене данными через сеть и -идентифицируется по номеру порта `1..65535`. Аналогия с номером очереди -`NFQUEUE`. На divert сокеты заворачивается трафик посредством правил ipfw или -PF. Если в фаерволе есть правило divert, но на divert порту никто не слушает, -то пакеты дропаются. Это поведение аналогично правилам `NFQUEUE` без параметра -`--queue-bypass`. На **FreeBSD** divert сокеты могут быть только ipv4, хотя на -них принимаются и ipv4, и ipv6 фреймы. На **OpenBSD** divert сокеты создаются -отдельно для ipv4 и ipv6 и работают только с одной версией `ip` каждый. На -**MacOS** похоже, что divert сокеты из ядра вырезаны. См подробнее раздел про -**MacOS**. Отсылка в divert сокет работает аналогично отсылке через raw socket -на linux. Передается полностью IP фрейм, начиная с ip загловка. Эти особенности -учитываются в `dvtws`. - -### Lookup Tables -Скрипты [`ipset/*.sh`](../ipset/) при наличии ipfw работают с ipfw lookup -tables. Это прямой аналог ipset. lookup tables не разделены на v4 и v6. Они -могут содержать v4 и v6 адреса и подсети одновременно. Если ipfw отсутствует, -то действие зависит от переменной `LISTS_RELOAD` в config. Если она задана, то -выполняется команда из `LISTS_RELOAD`. В противном случае не делается ничего. -Если `LISTS_RELOAD=-`, то заполнение таблиц отключается даже при наличии ipfw. - -### Загрузка ip таблиц из файла -PF может загружать ip таблицы из файла. Чтобы использовать эту возможность -следует отключить сжатие gzip для листов через параметр файла config: -`GZIP_LISTS=0`. - -### Отсутствие splice -**BSD** не содержит системного вызова splice. `tpws` работает через переброску -данных в user mode в оба конца. Это медленнее, но не критически. Управление -асинхронными сокетами в `tpws` основано на linux-specific механизме epoll. В -**BSD** для его эмуляции используется epoll-shim - прослойка для эмуляции epoll -на базе kqueue. - -### mdig и ip2net -mdig и ip2net полностью работоспособны в **BSD**. В них нет ничего -системо-зависимого. - - -## FreeBSD - -### Подгрузка ipdivert -Divert сокеты требуют специального модуля ядра - `ipdivert`. - -- Поместите следующие строки в `/boot/loader.conf` (создать, если отсутствует): -``` -ipdivert_load="YES" -net.inet.ip.fw.default_to_accept=1 -``` - -`/etc/rc.conf`: -``` -firewall_enable="YES" -firewall_script="/etc/rc.firewall.my" -``` - -`/etc/rc.firewall.my`: -```sh -$ ipfw -q -f flush -``` - -### Авто-восстановление правил ipfw и работа в фоне -В `/etc/rc.firewall.my` можно дописывать правила ipfw, чтобы они -восстанавливались после перезагрузки. Оттуда же можно запускать и демоны -zapret, добавив в параметры `--daemon`. Например так: -```sh -$ pkill ^dvtws$ -$ /opt/zapret/nfq/dvtws --port=989 --daemon --dpi-desync=multisplit --dpi-desync-split-pos=2 -``` - -Для перезапуска фаервола и демонов достаточно будет сделать: -```sh -$ /etc/rc.d/ipfw restart -``` - -### tpws в прозрачном режиме -Краткая инструкция по запуску `tpws` в прозрачном режиме. - -> [!NOTE] -> Предполагается, что интерфейс LAN называется `em1`, WAN - `em0`. - -#### Весь трафик -```sh -$ ipfw delete 100 -$ ipfw add 100 fwd 127.0.0.1,988 tcp from me to any 80,443 proto ip4 xmit em0 not uid daemon -$ ipfw add 100 fwd ::1,988 tcp from me to any 80,443 proto ip6 xmit em0 not uid daemon -$ ipfw add 100 fwd 127.0.0.1,988 tcp from any to any 80,443 proto ip4 recv em1 -$ ipfw add 100 fwd ::1,988 tcp from any to any 80,443 proto ip6 recv em1 -$ /opt/zapret/tpws/tpws --port=988 --user=daemon --bind-addr=::1 --bind-addr=127.0.0.1 -``` - -#### Трафик только на таблицу zapret, за исключением таблицы nozapret - -```sh -$ ipfw delete 100 -$ ipfw add 100 allow tcp from me to table\(nozapret\) 80,443 -$ ipfw add 100 fwd 127.0.0.1,988 tcp from me to table\(zapret\) 80,443 proto ip4 xmit em0 not uid daemon -$ ipfw add 100 fwd ::1,988 tcp from me to table\(zapret\) 80,443 proto ip6 xmit em0 not uid daemon -$ ipfw add 100 allow tcp from any to table\(nozapret\) 80,443 recv em1 -$ ipfw add 100 fwd 127.0.0.1,988 tcp from any to any 80,443 proto ip4 recv em1 -$ ipfw add 100 fwd ::1,988 tcp from any to any 80,443 proto ip6 recv em1 -$ /opt/zapret/tpws/tpws --port=988 --user=daemon --bind-addr=::1 --bind-addr=127.0.0.1 -``` - -> [!NOTE] -> Таблицы zapret, nozapret, ipban создаются скриптами из ipset по аналогии с -> Linux. Обновление скриптов можно забить в cron под root: -> ```sh -> $ crontab -e -> ``` -> -> ``` -> <...> -> 0 12 */2 * * /opt/zapret/ipset/get_config.sh -> ``` - -> [!CAUTION] -> При использовании ipfw `tpws` не требует повышенных привилегий для реализации -> прозрачного режима. Однако, без рута невозможен bind на порты `< 1024` и -> смена UID/GID. Без смены UID будет рекурсия, поэтому правила ipfw нужно -> создавать с учетом UID, под которым работает `tpws`. Переадресация на порты -> `>= 1024` может создать угрозу перехвата трафика непривилегированным -> процессом, если вдруг `tpws` не запущен. - - -### Запуск dvtws - -#### Весь трафик -```sh -$ ipfw delete 100 -$ ipfw add 100 divert 989 tcp from any to any 80,443 out not diverted xmit em0 -# required for autottl mode only -$ ipfw add 100 divert 989 tcp from any 80,443 to any tcpflags syn,ack in not diverted recv em0 -$ /opt/zapret/nfq/dvtws --port=989 --dpi-desync=multisplit --dpi-desync-split-pos=2 -``` - -#### Трафик только на таблицу zapret, за исключением таблицы nozapret - -```sh -$ ipfw delete 100 -$ ipfw add 100 allow tcp from me to table\(nozapret\) 80,443 -$ ipfw add 100 divert 989 tcp from any to table\(zapret\) 80,443 out not diverted not sockarg xmit em0 -# required for autottl mode only -$ ipfw add 100 divert 989 tcp from table\(zapret\) 80,443 to any tcpflags syn,ack in not diverted not sockarg recv em0 -$ /opt/zapret/nfq/dvtws --port=989 --dpi-desync=multisplit --dpi-desync-split-pos=2 -``` - - -### PF в FreeBSD -Настройка аналогична **OpenBSD**, но есть важные нюансы. - -- В **FreeBSD** поддержка PF в `tpws` отключена по умолчанию. Чтобы ее - включить, нужно использовать параметр `--enable-pf`. -- Нельзя сделать ipv6 rdr на `::1`. Нужно делать на link-local адрес входящего - интерфейса. Смотрите через `ifconfig` адрес `fe80:...` и добавляете в правило. -- Синтаксис `pf.conf` немного отличается. Более новая версия PF. -- Лимит на количество элементов таблиц задается так: - ```sh - $ sysctl net.pf.request_maxcount=2000000 - ``` -- Сломан divert-to. Он работает, но не работает механизм предотвращения - зацикливаний. Кто-то уже написал патч, но в `14-RELEASE` проблема все еще - есть. Следовательно, на данный момент работа `dvtws` через PF невозможна. - - `/etc/pf.conf`: - ``` - rdr pass on em1 inet6 proto tcp to port {80,443} -> fe80::31c:29ff:dee2:1c4d port 988 - rdr pass on em1 inet proto tcp to port {80,443} -> 127.0.0.1 port 988 - ``` - - ```sh - $ /opt/zapret/tpws/tpws --port=988 --enable-pf --bind-addr=127.0.0.1 --bind-iface6=em1 --bind-linklocal=force - ``` - -> [!NOTE] - > В PF не выходит делать rdr-to с той же системы, где работает proxy. - > Вариант с route-to не сохраняет мета информацию. Адрес назначения теряется. - > Поэтому этот вариант годится для squid, берущего адрес из протокола прикладного уровня, но не годится для tpws, полагающегося на метаданные ОС. - > Поддержка rdr-to реализована через `/dev/pf`, поэтому прозрачный режим **требует root**. - - -### pfsense - -#### Описание -pfsense основан на **FreeBSD** и использует фаервол PF, имеющий проблемы с -divert. К счастью, модули ipfw и ipdivert присутствуют в поставке последних -версий pfsense. Их можно подгрузить через `kldload`. - -В некоторых более старых версиях pfsense требуется изменить порядок фаерволов -через `sysctl`, сделав ipfw первым. В более новых эти параметры `sysctl` -отсутствуют, но система работает как надо и без них. В некоторых случаях -фаервол PF может ограничивать возможности `dvtws`, в частности в области -фрагментации ip. - -Присутствуют по умолчанию правила scrub для реассемблинга фрагментов. - -Бинарики из [`binaries/freebsd-x64`](../binaries/freebsd-x64) собраны под -**FreeBSD 11**. Они должны работать и на последующих версиях **FreeBSD**, -включая pfsense. Можно пользоваться `install_bin.sh`. - -#### Автозапуск -Пример скрипта автозапуска лежит в [`init.d/pfsense`](../init.d/pfsense). Его -следует поместить в `/usr/local/etc/rc.d` и отредактировать на предмет правил -ipfw и запуска демонов. Есть встроенный редактор `edit` как более приемлемая -альтернатива `vi`. - -> [!NOTE] -> Поскольку `git` отсутствует, копировать файлы удобнее всего через `ssh`. -> `curl` присутствует по умолчанию. Можно скопировать zip с файлами zapret и -> распаковать в `/opt`, как это делается на других системах. Тогда `dvtws` -> нужно запускать как `/opt/zapret/nfq/dvtws`. Либо скопировать только `dvtws` -> в `/usr/local/sbin`. Как вам больше нравится. - -> [!NOTE] -> Скрипты ipset работают, крон есть. Можно сделать автообновление листов. - -> [!NOTE] -> Если вас напрягает бедность имеющегося репозитория, можно включить -> репозиторий от **FreeBSD**, который по умолчанию выключен. -> -> Поменяйте `no` на `yes` в `/usr/local/etc/pkg/repos/FreeBSD.conf` -> -> Можно установить весь привычный софт, включая `git`, чтобы напрямую скачивать -> zapret с github. - -`/usr/local/etc/rc.d/zapret.sh` (chmod `755`): -```sh -#!/bin/sh - -kldload ipfw -kldload ipdivert - -# for older pfsense versions. newer do not have these sysctls -sysctl net.inet.ip.pfil.outbound=ipfw,pf -sysctl net.inet.ip.pfil.inbound=ipfw,pf -sysctl net.inet6.ip6.pfil.outbound=ipfw,pf -sysctl net.inet6.ip6.pfil.inbound=ipfw,pf - -ipfw delete 100 -ipfw add 100 divert 989 tcp from any to any 80,443 out not diverted xmit em0 -pkill ^dvtws$ -dvtws --daemon --port 989 --dpi-desync=multisplit --dpi-desync-split-pos=2 - -# required for newer pfsense versions (2.6.0 tested) to return ipfw to functional state -pfctl -d ; pfctl -e -``` - -#### Проблемы tpws -Что касается `tpws`, то видимо имеется некоторый конфликт двух фаерволов, и -правила fwd в ipfw не работают. Работает перенаправление средствами PF как -описано в разделе по **FreeBSD**. В PF можно изменять правила только целыми -блоками - якорями (anchors). Нельзя просто так добавить или удалить что-то. Но -чтобы какой-то anchor был обработан, на него должна быть ссылка из основного -набора правил. Его трогать нельзя, иначе порушится весь фаервол. Поэтому -придется править код скриптов pfsense. - -1. Поправьте `/etc/inc/filter.inc` следующим образом: -``` - <...> - /* MOD */ - $natrules .= "# ZAPRET redirection\n"; - $natrules .= "rdr-anchor \"zapret\"\n"; - - $natrules .= "# TFTP proxy\n"; - $natrules .= "rdr-anchor \"tftp-proxy/*\"\n"; - <...> -``` - -2. Напишите файл с содержимым anchor-а (например, `/etc/zapret.anchor`): -``` -rdr pass on em1 inet proto tcp to port {80,443} -> 127.0.0.1 port 988 -rdr pass on em1 inet6 proto tcp to port {80,443} -> fe80::20c:29ff:5ae3:4821 port 988 -``` - -`fe80::20c:29ff:5ae3:4821` замените на ваш link local адрес LAN интерфейса, -либо уберите строчку, если ipv6 не нужен. - -3. Добавьте в автозапуск `/usr/local/etc/rc.d/zapret.sh`: -```sh -$ pfctl -a zapret -f /etc/zapret.anchor -$ pkill ^tpws$ -$ tpws --daemon --port=988 --enable-pf --bind-addr=127.0.0.1 --bind-iface6=em1 --bind-linklocal=force --split-pos=2 -``` - -4. После перезагрузки проверьте, что правила создались: -```sh -$ pfctl -s nat -no nat proto carp all -nat-anchor "natearly/*" all -nat-anchor "natrules/*" all -<...> -no rdr proto carp all -rdr-anchor "zapret" all -rdr-anchor "tftp-proxy/*" all -rdr-anchor "miniupnpd" all - -$ pfctl -s nat -a zapret -rdr pass on em1 inet proto tcp from any to any port = http -> 127.0.0.1 port 988 -rdr pass on em1 inet proto tcp from any to any port = https -> 127.0.0.1 port 988 -rdr pass on em1 inet6 proto tcp from any to any port = http -> fe80::20c:29ff:5ae3:4821 port 988 -rdr pass on em1 inet6 proto tcp from any to any port = https -> fe80::20c:29ff:5ae3:4821 port 988 -``` - -> [!NOTE] -> Так же есть более элегантный способ запуска `tpws` через @reboot в cron и -> правило перенаправления в UI. Это позволит не редактировать код pfsense. - - -## OpenBSD - -### tpws bind на ipv4 -В `tpws` bind по умолчанию только на ipv6. Для bind на ipv4 нужно указать `--bind-addr=0.0.0.0`. -Используйте `--bind-addr=0.0.0.0 --bind-addr=::` для достижения того же результата, как в других ОС по умолчанию. -Но лучше все же так не делать, а сажать на определенные внутренние адреса или интерфейсы. - - -### tpws для проходящего трафика (старая схема не работает в новых версиях) - -В этом варианте tpws обращается явно к редиректору pf и пытается от него получить оригинальный адрес назначения. -Как показывает практика, это не работает на новых версиях OpenBSD. Возвращается ошибка ioctl. -Последняя проверенная версия, где это работает, - 6.8 . Между 6.8 и 7.4 разработчики сломали этот механизм. - -`/etc/pf.conf`: -``` -pass in quick on em1 inet proto tcp to port {80,443} rdr-to 127.0.0.1 port 988 -pass in quick on em1 inet6 proto tcp to port {80,443} rdr-to ::1 port 988 -``` - -```sh -$ pfctl -f /etc/pf.conf -$ tpws --port=988 --user=daemon --bind-addr=::1 --bind-addr=127.0.0.1 --enable-pf -``` - -> [!NOTE] -> В PF не выходит делать rdr-to с той же системы, где работает proxy. -> Вариант с route-to не сохраняет мета информацию. Адрес назначения теряется. -> Поэтому этот вариант годится для squid, берущего адрес из протокола прикладного уровня, но не годится для tpws, полагающегося на метаданные ОС. -> Поддержка rdr-to реализована через `/dev/pf`, поэтому прозрачный режим **требует root**. - -### tpws для проходящего трафика (новые системы) - -В новых версиях предлагается использовать divert-to вместо rdr-to. -Минимально проверенная версия, где это работает, 7.4. Может работать или не работать на более старых - исследование не проводилось. - -`/etc/pf.conf`: -``` -pass on em1 inet proto tcp to port {80,443} divert-to 127.0.0.1 port 989 -pass on em1 inet6 proto tcp to port {80,443} divert-to ::1 port 989 -``` - -tpws должен иметь бинд на точно такой адрес, который указан в правилах pf. `0.0.0.0` или `::` не работает. - -```sh -$ pfctl -f /etc/pf.conf -$ tpws --port=988 --user=daemon --bind-addr=::1 --bind-addr=127.0.0.1 -``` - -> [!NOTE] -> Так же не понятно как делать divert с самой системы, где работает tpws. - -### Запуск dvtws - -#### Весь трафик -`/etc/pf.conf`: -``` -pass in quick on em0 proto tcp from port {80,443} flags SA/SA divert-packet port 989 no state -pass in quick on em0 proto tcp from port {80,443} no state -pass out quick on em0 proto tcp to port {80,443} divert-packet port 989 no state -``` - -```sh -$ pfctl -f /etc/pf.conf -$ ./dvtws --port=989 --dpi-desync=multisplit --dpi-desync-split-pos=2 -``` - -#### Трафик только на таблицу zapret, за исключением таблицы nozapret - -`/etc/pf.conf`: -``` -set limit table-entries 2000000 -table file "/opt/zapret/ipset/zapret-ip.txt" -table file "/opt/zapret/ipset/zapret-ip-user.txt" -table file "/opt/zapret/ipset/zapret-ip-exclude.txt" -pass out quick on em0 inet proto tcp to port {80,443} -pass in quick on em0 inet proto tcp from port {80,443} flags SA/SA divert-packet port 989 no state -pass in quick on em0 inet proto tcp from port {80,443} no state -pass out quick on em0 inet proto tcp to port {80,443} divert-packet port 989 no state -pass in quick on em0 inet proto tcp from port {80,443} flags SA/SA divert-packet port 989 no state -pass in quick on em0 inet proto tcp from port {80,443} no state -pass out quick on em0 inet proto tcp to port {80,443} divert-packet port 989 no state -table file "/opt/zapret/ipset/zapret-ip6.txt" -table file "/opt/zapret/ipset/zapret-ip-user6.txt" -table file "/opt/zapret/ipset/zapret-ip-exclude6.txt" -pass out quick on em0 inet6 proto tcp to port {80,443} -pass in quick on em0 inet6 proto tcp from port {80,443} flags SA/SA divert-packet port 989 no state -pass in quick on em0 inet6 proto tcp from port {80,443} no state -pass out quick on em0 inet6 proto tcp to port {80,443} divert-packet port 989 no state -pass in quick on em0 inet6 proto tcp from port {80,443} flags SA/SA divert-packet port 989 no state -pass in quick on em0 inet6 proto tcp from port {80,443} no state -pass out quick on em0 inet6 proto tcp to port {80,443} divert-packet port 989 no state -``` - -```sh -$ pfctl -f /etc/pf.conf -$ ./dvtws --port=989 --dpi-desync=multisplit --dpi-desync-split-pos=2 -``` - - -### Проблемы с badsum -**OpenBSD** принудительно пересчитывает tcp checksum после divert, поэтому -скорее всего `dpi-desync-fooling=badsum` у вас не заработает. При использовании -этого параметра `dvtws` предупредит о возможной проблеме. - - -### Особенность отправки fake пакетов -В **OpenBSD** `dvtws` все фейки отсылает через divert socket, поскольку эта -возможность через raw sockets заблокирована. Видимо PF автоматически -предотвращает повторный заворот diverted фреймов, поэтому проблемы зацикливания -нет. - -divert-packet автоматически вносит обратное правило для перенаправления. Трюк с -no state и in правилом позволяет обойти эту проблему, чтобы напрасно не гнать -массивный трафик через `dvtws`. - - -### Перезагрузка PF таблиц -Скрипты из ipset не перезагружают таблицы в PF по умолчанию. - -Чтобы они это делали, добавьте параметр в `/opt/zapret/config`: -``` -LISTS_RELOAD="pfctl -f /etc/pf.conf" -``` - -Более новые версии `pfctl` понимают команду перезагрузить только таблицы. Но это не относится к **OpenBSD**. В новых **FreeBSD** есть. -```sh -$ pfctl -Tl -f /etc/pf.conf -``` - -> [!IMPORTANT] -> Не забудьте выключить сжатие gzip: `GZIP_LISTS=0` - -> [!IMPORTANT] -> Если в вашей конфигурации какого-то файла листа нет, то его необходимо -> исключить из правил PF. Если вдруг листа нет, и он задан в pf.conf, будет -> ошибка перезагрузки фаервола. - -> [!NOTE] -> После настройки обновление листов можно поместить в cron: -> ```sh -> $ crontab -e -> ``` -> -> ``` -> <...> -> 0 12 */2 * * /opt/zapret/ipset/get_config.sh -> ``` - - -## MacOS - -### Введение -Иначально ядро этой ОС "darwin" основывалось на **BSD**, потому в ней много -похожего на другие версии **BSD**. Однако, как и в других массовых коммерческих -проектах, приоритеты смещаются в сторону от оригинала. Яблочники что хотят, то -и творят. - - -### dvtws бесполезен -Раньше был ipfw, потом его убрали, заменили на PF. Есть сомнения, что divert -сокеты в ядре остались. Попытка создать divert socket не выдает ошибок, но -полученный сокет ведет себя точно так же, как raw, со всеми его унаследованными -косяками + еще яблочно специфическими. В PF divert-packet не работает. Простой -grep бинарика `pfctl` показывает, что там нет слова "divert", а в других -версиях **BSD** оно есть. `dvtws` собирается, но совершенно бесполезен. - - -### tpws -`tpws` удалось адаптировать, он работоспособен. Получение адреса назначения для -прозрачного прокси в PF (`DIOCNATLOOK`) убрали из заголовков в новых SDK, -сделав фактически недокументированным. - -В `tpws` перенесены некоторые определения из более старых версий яблочных SDK. -С ними удалось завести прозрачный режим. Однако, что будет в следующих версиях -угадать сложно. Гарантий нет. Еще одной особенностью PF в **MacOS** является -проверка на рута в момент обращения к `/dev/pf`, чего нет в остальных **BSD**. -`tpws` по умолчанию сбрасывает рутовые привилегии. Необходимо явно указать -параметр `--user=root`. В остальном PF себя ведет похоже на **FreeBSD**. -Синтаксис `pf.conf` тот же. - -> [!IMPORTANT] -> На **MacOS** работает редирект как с проходящего трафика, так и с локальной -> системы через route-to. Поскольку `tpws` вынужден работать под root, для -> исключения рекурсии приходится пускать исходящий от root трафик напрямую. -> Отсюда имеем недостаток - **обход DPI для рута работать НЕ будет**. - -#### Работа в прозрачном режиме только для исходящих запросов -`/etc/pf.conf`: -``` -rdr pass on lo0 inet proto tcp from !127.0.0.0/8 to any port {80,443} -> 127.0.0.1 port 988 -rdr pass on lo0 inet6 proto tcp from !::1 to any port {80,443} -> fe80::1 port 988 -pass out route-to (lo0 127.0.0.1) inet proto tcp from any to any port {80,443} user { >root } -pass out route-to (lo0 fe80::1) inet6 proto tcp from any to any port {80,443} user { >root } -``` - -```sh -$ pfctl -ef /etc/pf.conf -$ /opt/zapret/tpws/tpws --user=root --port=988 --bind-addr=127.0.0.1 --bind-iface6=lo0 --bind-linklocal=force -``` - -#### Работа в прозрачном режиме -> [!NOTE] -> Предполагается, что имя LAN интерфейса - `en1` - -```sh -$ ifconfig en1 | grep fe80 - inet6 fe80::bbbb:bbbb:bbbb:bbbb%en1 prefixlen 64 scopeid 0x8 -``` - -`/etc/pf.conf`: -``` -rdr pass on en1 inet proto tcp from any to any port {80,443} -> 127.0.0.1 port 988 -rdr pass on en1 inet6 proto tcp from any to any port {80,443} -> fe80::bbbb:bbbb:bbbb:bbbb port 988 -rdr pass on lo0 inet proto tcp from !127.0.0.0/8 to any port {80,443} -> 127.0.0.1 port 988 -rdr pass on lo0 inet6 proto tcp from !::1 to any port {80,443} -> fe80::1 port 988 -pass out route-to (lo0 127.0.0.1) inet proto tcp from any to any port {80,443} user { >root } -pass out route-to (lo0 fe80::1) inet6 proto tcp from any to any port {80,443} user { >root } -``` - -```sh -$ pfctl -ef /etc/pf.conf -$ /opt/zapret/tpws/tpws --user=root --port=988 --bind-addr=127.0.0.1 --bind-iface6=lo0 --bind-linklocal=force --bind-iface6=en1 --bind-linklocal=force -``` - - -### Проблема link-local адреса -Если вы пользуетесь **MaсOS** в качестве ipv6 роутера, то нужно будет -решить вопрос с регулярно изменяемым link-local адресом. С некоторых версий -**MacOS** использует по умолчанию постоянные "secured" ipv6 адреса вместо -генерируемых на базе MAC адреса. - -Все замечательно, но есть одна проблема. Постоянными остаются только global -scope адреса. Link locals периодически меняются. Смена завязана на системное -время. Перезагрузки адрес не меняют, Но если перевести время на день вперед и -перезагрузиться - link local станет другим (по крайней мере в vmware это так). -Информации по вопросу крайне мало, но тянет на баг. Не должен меняться link -local. Скрывать link local не имеет смысла, а динамический link local нельзя -использовать в качестве адреса шлюза. Проще всего отказаться от "secured" -адресов. Для этого поместите строчку `net.inet6.send.opmode=0` в -`/etc/sysctl.conf` и перезагрузите систему. - -Все равно для исходящих соединений будут использоваться temporary адреса, как и -в других системах. Или вам идея не по вкусу, можно прописать дополнительный -статический ipv6 из диапазона маски `fc00::/7` - выберите любой с длиной -префикса `128`. Это можно сделать в системных настройках, создав дополнительный -адаптер на базе того же сетевого интерфейса, отключить в нем ipv4 и вписать -статический ipv6. Он добавится к автоматически настраеваемым. - - -### Сборка -```sh -$ make -C /opt/zapret mac -``` - - -### Простая установка -В **MacOS** поддерживается `install_easy.sh` - -В комплекте идут бинарики, собраные под 64-bit с опцией -`-mmacosx-version-min=10.8`. Они должны работать на всех поддерживаемых версиях -**MacOS**. Если вдруг не работают - можно собрать свои. Developer tools -ставятся автоматом при запуске `make`. - -> [!WARNING] -> Internet sharing средствами системы **не поддерживается**! -> -> Поддерживается только роутер, настроенный своими силами через PF. Если вы -> вдруг включили шаринг, а потом выключили, то доступ к сайтам может пропасть -> совсем. -> -> Лечение: -> ```sh -> $ pfctl -f /etc/pf.conf -> ``` -> -> Если вам нужен шаринг интернета, лучше отказаться от прозрачного режима и -> использовать socks прокси. - -Для автостарта используется launchd (`/Library/LaunchDaemons/zapret.plist`) -Управляющий скрипт : `/opt/zapret/init.d/macos/zapret` - -Следующие команды работают с `tpws` и фаерволом одновременно (если -`INIT_APPLY_FW=1` в config) - -```sh -$ /opt/zapret/init.d/macos/zapret start -$ /opt/zapret/init.d/macos/zapret stop -$ /opt/zapret/init.d/macos/zapret restart -``` - -Работа только с tpws: -```sh -$ /opt/zapret/init.d/macos/zapret start-daemons -$ /opt/zapret/init.d/macos/zapret stop-daemons -$ /opt/zapret/init.d/macos/zapret restart-daemons -``` - -Работа только с PF: -```sh -$ /opt/zapret/init.d/macos/zapret start-fw -$ /opt/zapret/init.d/macos/zapret stop-fw -$ /opt/zapret/init.d/macos/zapret restart-fw -``` - -Перезагрузка всех IP таблиц из файлов: -```sh -$ /opt/zapret/init.d/macos/zapret reload-fw-tables -``` - -> [!NOTE] -> Инсталятор настраивает `LISTS_RELOAD` в config, так что скрипты -> [`ipset/*.sh`](../ipset/) автоматически перезагружают IP таблицы в PF. - -> [!NOTE] -> Автоматически создается cron job на -> [`ipset/get_config.sh`](../ipset/get_config.sh), по аналогии с openwrt. - -При start-fw скрипт автоматически модицифирует `/etc/pf.conf`, вставляя туда -anchors "zapret". Модификация расчитана на `pf.conf`, в котором сохранены -дефолтные anchors от apple. Если у вас измененный `pf.conf` и модификация не -удалась, об этом будет предупреждение. Не игнорируйте его. В этом случае вам -нужно вставить в свой `pf.conf` (в соответствии с порядком типов правил): -``` -rdr-anchor "zapret" -anchor "zapret" -``` - -> [!NOTE] -> При деинсталяции через `uninstall_easy.sh` модификации `pf.conf` убираются. - -> [!NOTE] -> start-fw создает 3 файла anchors в `/etc/pf.anchors`: `zapret`, `zapret-v4`, -> `zapret-v6`. Последние 2 подключаются из anchor "zapret". - -> [!NOTE] -> Таблицы `nozapret` и `nozapret6` принадлежат anchor "zapret". -> -> Таблицы `zapret` и `zapret-user` в anchor "zapret-v4". -> -> Таблицы `zapret6` и `zapret6-user` в anchor "zapret-v6". -> -> Если какая-то версия протокола отключена - соответствующий anchor пустой и не -> упоминается в anchor "zapret". Таблицы и правила создаются только на те -> листы, которые фактически есть в директории ipset. - - -### Вариант Custom -Так же как и в других системах, поддерживаемых в простом инсталяторе, можно -создавать свои custom скрипты. - -Расположение: `/opt/zapret/init.d/macos/custom` - -`zapret_custom_daemons()` получает в `$1`: `0` или `1`. `0` = stop, `1` = start - -custom firewall отличается от linux варианта. Вместо заполнения `iptables` вам -нужно сгенерировать правила для `zapret-v4` и `zapret-v6` anchors и выдать их в -stdout. Это делается в функциях `zapret_custom_firewall_v4()` и -`zapret_custom_firewall_v6()`. Определения таблиц заполняются основным скриптом -\- вам это делать не нужно. Можно ссылаться на таблицы `zapret` и `zapret-user` -в v4, `zapret6` и `zapret6-user`. - -Cм. пример [в файле](../init.d/macos/custom.d.examples/50-extra-tpws). diff --git a/docs/bsdfw.txt b/docs/bsdfw.txt deleted file mode 100644 index 6abd0250..00000000 --- a/docs/bsdfw.txt +++ /dev/null @@ -1,110 +0,0 @@ -WAN=em0 LAN=em1 - -FreeBSD IPFW : - -ipfw delete 100 -ipfw add 100 fwd 127.0.0.1,988 tcp from me to any 80,443 proto ip4 xmit em0 not uid daemon -ipfw add 100 fwd ::1,988 tcp from me to any 80,443 proto ip6 xmit em0 not uid daemon -ipfw add 100 fwd 127.0.0.1,988 tcp from any to any 80,443 proto ip4 recv em1 -ipfw add 100 fwd ::1,988 tcp from any to any 80,443 proto ip6 recv em1 - -ipfw delete 100 -ipfw add 100 allow tcp from me to table\(nozapret\) 80,443 -ipfw add 100 fwd 127.0.0.1,988 tcp from me to table\(zapret\) 80,443 proto ip4 xmit em0 not uid daemon -ipfw add 100 fwd ::1,988 tcp from me to table\(zapret\) 80,443 proto ip6 xmit em0 not uid daemon -ipfw add 100 allow tcp from any to table\(nozapret\) 80,443 recv em1 -ipfw add 100 fwd 127.0.0.1,988 tcp from any to any 80,443 proto ip4 recv em1 -ipfw add 100 fwd ::1,988 tcp from any to any 80,443 proto ip6 recv em1 - -/opt/zapret/tpws/tpws --port=988 --user=daemon --bind-addr=::1 --bind-addr=127.0.0.1 - - -ipfw delete 100 -ipfw add 100 divert 989 tcp from any to any 80,443 out not diverted xmit em0 -; required for autottl mode -ipfw add 100 divert 989 tcp from any 80,443 to any tcpflags syn,ack in not diverted recv em0 -; udp -ipfw add 100 divert 989 udp from any to any 443 out not diverted xmit em0 - -ipfw delete 100 -ipfw add 100 allow tcp from me to table\(nozapret\) 80,443 -ipfw add 100 divert 989 tcp from any to table\(zapret\) 80,443 out not diverted xmit em0 - -/opt/zapret/nfq/dvtws --port=989 --debug --dpi-desync=split - - -sample ipfw NAT setup : - -WAN=em0 -LAN=em1 -ipfw -q flush -ipfw -q nat 1 config if $WAN unreg_only reset -ipfw -q add 10 allow ip from any to any via $LAN -ipfw -q add 20 allow ip from any to any via lo0 -ipfw -q add 300 nat 1 ip4 from any to any in recv $WAN -ipfw -q add 301 check-state -ipfw -q add 350 skipto 390 tcp from any to any out xmit $WAN setup keep-state -ipfw -q add 350 skipto 390 udp from any to any out xmit $WAN keep-state -ipfw -q add 360 allow all from any to me in recv $WAN -ipfw -q add 390 nat 1 ip4 from any to any out xmit $WAN -ipfw -q add 10000 allow ip from any to any - -Forwarding : -sysctl net.inet.ip.forwarding=1 -sysctl net.inet6.ip6.forwarding=1 - - -OpenBSD PF : - -(OLD OpenBSD versions) - -; dont know how to rdr-to from local system. doesn't seem to work. only works for routed traffic. - -/etc/pf.conf -pass in quick on em1 inet proto tcp to port {80,443} rdr-to 127.0.0.1 port 988 -pass in quick on em1 inet6 proto tcp to port {80,443} rdr-to ::1 port 988 -pfctl -f /etc/pf.conf -/opt/zapret/tpws/tpws --port=988 --user=daemon --bind-addr=::1 --bind-addr=127.0.0.1 --enable-pf - -(NEW OpenBSD versions) - -; dont know how to divert-to from local system - -/etc/pf.conf -pass on em1 inet proto tcp to port {80,443} divert-to 127.0.0.1 port 989 -pass on em1 inet6 proto tcp to port {80,443} divert-to ::1 port 989 -pfctl -f /etc/pf.conf -/opt/zapret/tpws/tpws --port=988 --user=daemon --bind-addr=::1 --bind-addr=127.0.0.1 - - -; dvtws works both for routed and local - -pass in quick on em0 proto tcp from port {80,443} flags SA/SA divert-packet port 989 no state -pass in quick on em0 proto tcp from port {80,443} no state -pass out quick on em0 proto tcp to port {80,443} divert-packet port 989 no state -pfctl -f /etc/pf.conf -./dvtws --port=989 --dpi-desync=multisplit --dpi-desync-split-pos=2 - -; dvtws with table limitations : to zapret,zapret6 but not to nozapret,nozapret6 -; reload tables : pfctl -f /etc/pf.conf -set limit table-entries 2000000 -table file "/opt/zapret/ipset/zapret-ip.txt" -table file "/opt/zapret/ipset/zapret-ip-user.txt" -table file "/opt/zapret/ipset/zapret-ip-exclude.txt" -pass out quick on em0 inet proto tcp to port {80,443} -pass in quick on em0 inet proto tcp from port {80,443} flags SA/SA divert-packet port 989 no state -pass in quick on em0 inet proto tcp from port {80,443} no state -pass out quick on em0 inet proto tcp to port {80,443} divert-packet port 989 no state -pass in quick on em0 inet proto tcp from port {80,443} flags SA/SA divert-packet port 989 no state -pass in quick on em0 inet proto tcp from port {80,443} no state -pass out quick on em0 inet proto tcp to port {80,443} divert-packet port 989 no state -table file "/opt/zapret/ipset/zapret-ip6.txt" -table file "/opt/zapret/ipset/zapret-ip-user6.txt" -table file "/opt/zapret/ipset/zapret-ip-exclude6.txt" -pass out quick on em0 inet6 proto tcp to port {80,443} -pass in quick on em0 inet6 proto tcp from port {80,443} flags SA/SA divert-packet port 989 no state -pass in quick on em0 inet6 proto tcp from port {80,443} no state -pass out quick on em0 inet6 proto tcp to port {80,443} divert-packet port 989 no state -pass in quick on em0 inet6 proto tcp from port {80,443} flags SA/SA divert-packet port 989 no state -pass in quick on em0 inet6 proto tcp from port {80,443} no state -pass out quick on em0 inet6 proto tcp to port {80,443} divert-packet port 989 no state diff --git a/docs/changes.txt b/docs/changes.txt deleted file mode 100644 index 26ef4d94..00000000 --- a/docs/changes.txt +++ /dev/null @@ -1,588 +0,0 @@ -v1 - -Initial release - -v2 - -nfqws : command line options change. now using standard getopt. -nfqws : added options for window size changing and "Host:" case change -ISP support : tested on mns.ru and beeline (corbina) -init scripts : rewritten init scripts for simple choise of ISP -create_ipset : now using 'ipset restore', it works much faster -readme : updated. now using UTF-8 charset. - -v3 - -tpws : added transparent proxy (supports TPROXY and DNAT). - can help when ISP tracks whole HTTP session, not only the beginning -ipset : added zapret-hosts-user.txt which contain user defined host names to be resolved - and added to zapret ip list -ISP support : dom.ru support via TPROXY/DNAT -ISP support : successfully tested sknt.ru on 'domru' configuration - other configs will probably also work, but cannot test -compile : openwrt compile howto - -v4 - -tpws : added ability to insert extra space after http method : "GET /" => "GET /" -ISP support : TKT support - -v5 - -nfqws : ipv6 support in nfqws - -v6 - -ipset : added "get_antizapret.sh" - -v7 - -tpws : added ability to insert "." after Host: name - -v8 - -openwrt init : removed hotplug.d/firewall because of race conditions. now only use /etc/firewall.user - -v9 - -ipban : added ipban ipset. place domains banned by ip to zapret-hosts-user-ipban.txt - these IPs must be soxified for both http and https -ISP support : tiera support -ISP support : added DNS filtering to ubuntu and debian scripts - -v10 - -tpws : added split-pos option. split every message at specified position - -v11 - -ipset : scripts optimizations - -v12 - -nfqws : fix wrong tcp checksum calculation if packet length is odd and platform is big-endian - -v13 - -added binaries - -v14 - -change get_antizapret script to work with https://github.com/zapret-info/z-i/raw/master/dump.csv -filter out 192.168.*, 127.*, 10.* from blocked ips - -v15 - -added --hostspell option to nfqws and tpws -ISP support : beeline now catches "host" but other spellings still work -openwrt/LEDE : changed init script to work with procd -tpws, nfqws : minor cosmetic fixes - -v16 - -tpws: split-http-req=method : split inside method name, not after -ISP support : mns.ru changed split pos to 3 (got redirect page with HEAD req : curl -I ej.ru) - -v17 - -ISP support : athome moved from nfqws to tpws because of instability and http request hangs -tpws : added options unixeol,methodeol,hosttab - -v18 - -tpws,nfqws : added hostnospace option - -v19 - -tpws : added hostlist option - -v20 - -added ip2net. ip2net groups ips from iplist into subnets and reduces ipset size twice - -v21 - -added mdig. get_reestr.sh is *real* again - -v22 - -total review of init script logic -dropped support of older debian 7 and ubuntu 12/14 systems -install_bin.sh : auto binaries preparation -docs: readme review. some new topics added, others deleted -docs: VPN setup with policy based routing using wireguard -docs: wireguard modding guide - -v23 - -major init system rewrite -openwrt : separate firewall include /etc/firewall.zapret -install_easy.sh : easy setup on openwrt, debian, ubuntu, centos, fedora, opensuse - -v24 - -separate config from init scripts -gzip support in ipset/*.sh and tpws - -v25 - -init : move to native systemd units -use links to units, init scripts and firewall includes, no more copying - -v26 - -ipv6 support -tpws : advanced bind options - -v27 - -tpws : major connection code rewrite. originally it was derived from not top quality example , with many bugs and potential problems. -next generation connection code uses nonblocking sockets. now its in EXPERIMENTAL state. - -v28 - -tpws : added socks5 support -ipset : major RKN getlist rewrite. added antifilter.network support - -v29 - -nfqws : DPI desync attack -ip exclude system - -v30 - -nfqws : DPI desync attack modes : fake,rst - -v31 - -nfqws : DPI desync attack modes : disorder,disorder2,split,split2. -nfqws : DPI desync fooling mode : badseq. multiple modes supported - -v32 - -tpws : multiple binds -init scripts : run only one instance of tpws in any case - -v33 - -openwrt : flow offloading support -config : MODE refactoring - -v34 - -nfqws : dpi-desync 2 mode combos -nfqws : dpi-desync without parameter no more supported. previously it meant "fake" -nfqws : custom fake http request and tls client hello - -v35 - -limited FreeBSD and OpenBSD support - -v36 - -full FreeBSD and OpenBSD support - -v37 - -limited MacOS support - -v38 - -MacOS easy install - -v39 - -nfqws: conntrack, wssize - -v40 - -init scripts : IFACE_LAN, IFACE_WAN now accept multiple interfaces -init scripts : openwrt uses now OPENWRT_LAN parameter to override incoming interfaces for tpws - -v41 - -install_easy : openrc support - -v42 - -blockcheck.sh - -v43 - -nfqws: UDP desync with conntrack support (any-protocol only for now) - -v44 - -nfqws: ipfrag - -v45 - -nfqws: hop-by-hop ipv6 desync and fooling - -v46 - -big startup script refactoring to support nftables and new openwrt snapshot builds with firewall4 - -v47 - -nfqws: QUIC initial decryption -nfqws: udplen, fakeknown dpi desync modes - -v48 - -nfqws, tpws : multiple --hostlist and --hostlist-exclude support -launch system, ipset : no more list merging. all lists are passed separately to nfqws and tpws -nfqws : udplen fooling supports packet shrinking (negative increment value) - -v49 - -QUIC support integrated to the main system and setup - -v50 - -DHT protocol support. -DPI desync mode 'tamper' for DHT. -HEX string support in addition to binary files. - -v51 - -tpws --tlsrec attack. - -v52 - -autohostlist mode - -v53 - -nfqws: tcp session reassemble for TLS ClientHello - -v54 - -tpws: out of band send when splitting (--oob) -nfqws: autottl -nfqws: datanoack fooling -nftables: use POSTNAT path for tcp redirections to allow NAT-breaking strategies. use additional mark bit DESYNC_MARK_POSTNAT. - -v55 - -tpws: incompatible oob parameter change. it doesn't take oob byte anymore. instead it takes optional protocol filter - http or tls. - the same is done with disorder. oob byte can be specified in parameter --oob-data. -blockcheck: quick mode, strategy order optimizations, QUIC protocol support -nfqws: syndata desync mode - -v56 - -tpws: mss fooling -tpws: multi thread resolver. eliminates blocks related to hostname resolve. - -v57 - -tpws: --nosplice option -nfqws: postnat fixes -nfqws: --dpi-desync-start option -nfqws: packet delay for kyber TLS and QUIC -nfqws: --dpi-desync-retrans obsolete -nfqws: --qnum is mandatory, no more default queue 0 - -v58 - -winws - -v59 - -tpws: --split-tls -tpws: --tlsrec=sniext -nfqws: --dpi-desync-split-http-req, --dpi-desync-split-tls. multi segment TLS support for split. -blockcheck: mdig dns cache - -v60 - -blockcheck: port block test, partial ip block test -nfqws: seqovl split/disorder modes - -v61 - -C code cleanups -dvtws: do not use raw sockets. use divert. -nfqws,tpws: detect TLS 1.2 ClientHello from very old libraries with SSL 3.0 version in record layer -nfqws,tpws: debug log to file and syslog -tpws: --connect-bind-addr option -tpws: log local endpoint (including source port number) for remote leg - -v62: - -tpws: connection close logic rewrite. tcp user timeout parameters for local and remote leg. -nfqws: multi-strategy - -v63: - -tpws: multi-strategy - -v64: - -blockcheck: warn if dpi bypass software is already running -blockcheck: TPWS_EXTRA, NFQWS_EXTRA -init.d: multiple custom scripts - -v65: - -init.d: dynamic number allocation for dnum,tpws_port,qnum -init.d: FW_EXTRA_PRE, FW_EXTRA_POST -init.d: zapret_custom_firewall_nft_flush -nfqws,tpws: l7proto and client ip:port info in autohostlist debug log -nfqws,tpws: user mode ipset filter support -nfqws,tpws: l7proto filter support -tpws: fixed MSS apply in transparent mode -nfqws: fixed autottl apply if desync profile changed -tpws,nfqws: fixed 100% cpu hang on gzipped list with comments -ipset: get_refilter_ipsum.sh , get_refilter_domain.sh - -v66: - -init.d: rewrite traffic interception and daemon launch parameters in config file. this break compatibility with old versions. -init.d: openwrt-minimal : tpws launch for low storage openwrt devices - -v67: - -mdig: --dns-make-query, --dns-parse-query for side-channel resolving (DoH) -blockcheck: use DoH resolvers if DNS spoof is detected -blockcheck: restring fooling to testing domain's IPs -nfqws,tpws: internal hostlist deduplication to save RAM -nfqws,tpws: hostlist/ipset auto reload on file change. no more HUP. -nfqws,tpws: --filter-tcp, --filter-udp take comma separated port range list -nfqws,tpws: @ - read config from a file -config: marker -binaries: remove zapret-winws. add win32. -blockcheck, install_easy.sh: preserve user environment variables during elevation -blockcheck: do not require root if SKIP_PKTWS=1 - -v68: - -docs : move russian version to markdown -nfqws,tpws: use alternate $ sign for $ -repo: binaries removed from repo. git actions binaries build in releases. -uninstall_easy.sh: offer to remove dependencies in openwrt -install_easy.sh: allow to download lists in autohostlist filter mode - -v69: - -nfqws, tpws: multisplit/multidisorder support. -nfqws: name change split->fakedsplit, disorder->fakeddisorder. compat : old names are synonyms -nfqws: --dpi-desync-split-http-req, --dpi-desync-split-tls deprecated. compat : these parameters add split point to multisplit. -nfqws: --dpi-desync=split2|disorder2 deprecated. compat: they are now synonyms for multisplit/multidisorder -nfqws: cancel seqovl if MTU is exceeded (linux only). cancel seqovl for disorder if seqovl>=first_part_size. -nfqws: fixed splits in multiple TLS segments. -tpws: --split-http-req,--split-tls deprecated. compat : these parameters add split point to multisplit. -tpws: --tlsrec now takes pos markers. compat : old names are converted to pos markers -tpws: --tlsrec-pos deprecated. compat : sets absolute pos marker -nfqws,tpws: chown autohostlist, autohostlist debug log and debug log files after options parse -nfqws,tpws: set EXEDIR env var to use in @config (won't work for stadalone winws without /bin/sh) -dvtws: set random/increasing ip_id value in generated packets -mdig: fixed parsing of DNS reply in windows (stdin is opened as text, not binary) -tpws: support compile for android NDK api level >= 21 (Android 5.0) -tpws: --fix-seg segmentation fixer -repo: build for android NDK api level 21 (Android 5.0) -install_easy: support for APK package manager in openwrt -blockcheck: removed ignore CA question -blockcheck: removed IGNORE_CA, CURL_VERBOSE -blockcheck: added CURL_OPT -blockcheck: new strategies support -blockcheck: test sequence rework -blockcheck: view all working strategies in summary - -v69.1: - -init.d: keenetic udp fix custom -tpws: fixed incorrect hostlist checks - -v69.2: - -nfqws,tpws: --skip -nfqws: --methodeol -init.d: do not use pgrep in sysv for busybox compat - -v69.3 - -nfqws,tpws: fixed ipsets and hostlists -all progs: version numbers for github, build date/time for self built -repo: light release for openwrt and embedded systems -repo: sha256sum - -v69.4 - -nfqws: fakedsplit/fakeddisorder fakes for both split segments -nfqws: --dpi-desync-fakedsplit-pattern - -v69.5 - -nfqws,tpws: --dry-run -install_easy: check tpws and nfqws options validity - -v69.6 - -nfqws: set NETLINK_NO_ENOBUFS to fix possible nfq recv errors -init.d: unify custom scripts for linux -init.d: new custom scripts : 20-fw-extra, 50-wg4all - -v69.7 - -nfqws,tpws: --comment -nfqws: trash flood warning -winws: exclude empty outgoing ack packets in windivert filter - -v69.8 - -winws: accept empty outgoing RST and FIN packets for conntrack needs -repo: lexra build - -v69.9 - -init.d: exclude ipban from tpws redirection -macos: fix install_easy -macos: fix national decimal separator in sleep -ipset: scripts maintenance - -v70 - -blockcheck: override all dialog questions and enable batch mode -blockcheck: parallel attempts -nfqws: weaken wireguard initiation recognition. use len=148 and data[0]=1 signature -nfqws: apply split+seqovl only to the first reasm fragment -install_easy: dnf packager support -nfqws,tpws: hostlist/ipset track not only file mod time but also file size -nfqws,tpws,ipset: return lists reload on HUP -nfqws,blockcheck: --dpi-desync-fake-tls-mod - -v70.1 - -nfqws: --dpi-desync-fake-tls-mod=dupsid -nfqws,tpws: test accessibility of list files after privs drop -nfqws,tpws: --version - -v70.4 - -nfqws,tpws: ^ prefix in hostlist to disable subdomain matches -nfqws,tpws: optional systemd notify support. compile using 'make systemd' -nfqws,tpws: systemd instance templates for nfqws and tpws -nfqws,tpws: separate droproot from dropcaps -tpws: detect WSL 1 and warn about non-working options - -v70.5 - -nfqws: multiple --dpi-desync-fake-xxx -nfqws: support of inter-packet fragmented QUIC CRYPTO - -v70.6 - -nfqws: detect Discord Voice IP discovery packets -nfqws: detect STUN message packets -nfqws: change SNI to specified value tls mod : --dpi-desync-fake-tls-mod sni= -nfqws: update default TLS ClientHello fake. firefox 136.0.4 finger, no kyber, SNI=microsoft.com -nfqws: multiple mods for multiple TLS fakes -init.d: remove 50-discord -blockcheck: use tpws --fix-seg on linux for multiple splits - -v71 - -nfqws,tpws: debug tls version, alpn, ech -nfqws: --dpi-desync-fake-tls=! means default tls fake -nfqws: --dup* -nfqws: --orig* -nfqws: ipcache of hop count and host names -nfqws: --ctrack-disable -nfqws: --synack-split -nfqws: --autottl=- or --autottl=0:0-0 disable autottl. previous "0" does not work anymore. -tpws: ipcache of host names -nfqws,tpws: set 1024 repeat limit to fakes and dups -nfqws,tpws: do more before daemonize -nfqws,tpws: accept multiple gids in --gid -nfqws,tpws: display "android" in version string if built for android -init.d: remove --ipset parameter prohibition -init.d, blockcheck: drop time exceeded icmp for nfqws-related connections -blockcheck: some dup and orig-ttl mods -blockcheck: PKTWS_EXTRA_PRE -blockcheck: report test function and domain every test - -v71.1 - -nfqws,tpws: much faster ipset implementation. move from hash to avl tree -nfqws,tpws: check list files accessibility with dropped privs in --dry-run mode -nfqws,tpws: --debug=android for NDK builds -nfqws,tpws: use initgroups instead of setgroups if --user specified -nfqws: --filter-ssid (linux-only) -install_easy: stop if running embedded release on traditional linux system (some files missing) -install_bin: add "read elf" arch detection method -binaries: renamed arch dirs in binaries - -v71.1.1 - -nfqws: use wireless ext in case nl80211 does not return SSID - -v71.2 - -nfqws: apply udp desync to replayed packets with non-zero reasm offset (except fake) -blockcheck: display curl version and kernel version -install_bin: stop if no binaries found. display help text. -winws: increase buffers for port filter -tpws: tpws no more opens /dev/pf in OpenBSD by default. requires --enable-pf like in FreeBSD. this is migration from rdr-to to divert-to redirection scheme. -install_easy: warn if --ipset parameter is specified - -v71.3 - -init.d: FILTER_MARK -nfqws: ts fooling -blockcheck: test ts fooling -blockcheck: auto enable tcp timestamps in windows -tpws,nfqws: special handling of IP-like hostnames. strip :port from hostname:port - -v71.4 - -nfqws,tpws: fix possible crashes or high memory usage if hostlist has duplicate hostnames -init.d: custom scripts 50-discord-media, 50-stun4all -init.d: windivert filters for discord media, stun, wireguard -readme: hardware problems description - -v72 - -winws: --wf-raw-part -nfqws: --dpi-desync=hostfakesplit -nfqws: --dpi-desync-fake-tcp-mod=seq -nfqws: --dpi-desync-fake-tls=!+offset , --dpi-desync-fake-xxx=[+offset]@filename -nfqws: --dpi-desync-fakedsplit-mod=altorder for fakedsplit/fakeddisorder -nfqws: --dpi-desync-hostfakesplit-mod=altorder -nfqws: fakedsplit/fakeddisorder normalize fake split pattern offset to reasm offset -nfqws: optimize ipv4 ip_id. apply ip_id on all OS the same way. -blockcheck: print PRETTY_NAME and some OPENWRT_xxx from /etc/os-release -blockcheck: new strategies -blockcheck: curl test simulation : SIMULATE=1 - -v72.1 - -nfqws: --ip-id=seq|seqgroup|rnd|zero -blockcheck: MIN_AUTOTTL_DELTA,MAX_AUTOTTL_DELTA -init.d: 50-quic4all custom - -72.2 - -nfqws: --wssize-forced-cutoff -nfqws: --orig-tcp-flags, --dup-tcp-flags, --dpi-desync-tcp-flags -nfqws: --dup-ip-id - -73.3 - -blockcheck: support URIs -blockcheck: CURL_HTTPS_GET=1 suppresses -I curl option for https (HEAD -> GET) - -73.4 - -blockcheck: fix broken dns cache - -73.5 - -nfqws: fix broken l7proto profile rediscovery -nfqws: backport from nfqws2 nl80211 ssid discovery fix for newer kernels - -73.6 - -ipset: remove zapret-info based scripts because it's gone -blockcheck: fix tpws test regression diff --git a/docs/compile/build_howto_openwrt.txt b/docs/compile/build_howto_openwrt.txt deleted file mode 100644 index d5ddd26e..00000000 --- a/docs/compile/build_howto_openwrt.txt +++ /dev/null @@ -1,57 +0,0 @@ -How to compile native programs for use in openwrt -------------------------------------------------- - -1) Install required packages to the host system : - -debian,ubuntu : apt install build-essential patch libncurses-dev python3-distutils unzip gawk wget git -fedora: dnf install make patch gcc g++ ncurses-devel git perl - -Other packages may be required on your distribution. Look for the errors. - -2) Download latest SDK for your target platform from https://downloads.openwrt.org - -examples : - -curl -o - https://downloads.openwrt.org/releases/23.05.5/targets/x86/64/openwrt-sdk-23.05.5-x86-64_gcc-12.3.0_musl.Linux-x86_64.tar.xz | tar -Jxv -cd openwrt-sdk-23.05.5-x86-64_gcc-12.3.0_musl.Linux-x86_64 - -curl -o - https://downloads.openwrt.org/snapshots/targets/x86/64/openwrt-sdk-x86-64_gcc-13.3.0_musl.Linux-x86_64.tar.zst | tar --zstd -xv -cd openwrt-sdk-x86-64_gcc-13.3.0_musl.Linux-x86_64 - -3) Install required libs - -./scripts/feeds update base packages -./scripts/feeds install libnetfilter-queue zlib libcap - -4) Prepare openwrt package definitions - -cp -R /opt/zapret/docs/compile/openwrt/. . -cp -R /opt/zapret/tpws package/zapret/tpws -cp -R /opt/zapret/nfq package/zapret/nfqws -cp -R /opt/zapret/mdig package/zapret/mdig -cp -R /opt/zapret/ip2net package/zapret/ip2net -rm -f package/zapret/tpws/tpws/tpws package/zapret/nfqws/nfq/nfqws package/zapret/mdig/mdig/mdig package/zapret/ip2net/ip2net/ip2net - -5) Prepare .config - -make defconfig - -If you only need bins without packages comment 'CONFIG_AUTOREMOVE=y' line in .config - -6) Compile - -dynamic build : make package/{tpws,nfqws,mdig,ip2net}/compile -static build : make CFLAGS=-static package/{tpws,nfqws,mdig,ip2net}/compile - -7) Get result - -executables only : build_dir/target/ -ipk or apk packages : bin/packages/*/base - -8) Installing to openwrt to use with zapret - -zapret with or without binaries should be already installed in /opt/zapret. -Install ipk's or apk's with all compiled progs using opkg or apk. -Bins are placed to /opt/zapret/binaries/my. -Or copy binaries there manually and set chmod 755 to them. -Run install_bin.sh or install_easy.sh. They will use bins in 'my' folder. diff --git a/docs/compile/build_howto_unix.txt b/docs/compile/build_howto_unix.txt deleted file mode 100644 index ae85c3c7..00000000 --- a/docs/compile/build_howto_unix.txt +++ /dev/null @@ -1,16 +0,0 @@ -debian,ubuntu : - -apt install make gcc zlib1g-dev libcap-dev libnetfilter-queue-dev libmnl-dev libsystemd-dev -make -C /opt/zapret systemd - -FreeBSD : - -make -C /opt/zapret - -OpenBSD : - -make -C /opt/zapret bsd - -MacOS : - -make -C /opt/zapret mac diff --git a/docs/compile/build_howto_windows.txt b/docs/compile/build_howto_windows.txt deleted file mode 100644 index 6b43b37a..00000000 --- a/docs/compile/build_howto_windows.txt +++ /dev/null @@ -1,29 +0,0 @@ -Windows x64 - -1) Download latest cygwin for windows 7 - -curl -O https://www.cygwin.com/setup-x86_64.exe -setup-x86_64.exe --allow-unsupported-windows --no-verify --site http://ctm.crouchingtigerhiddenfruitbat.org/pub/cygwin/circa/64bit/2024/01/30/231215 - -2) During setup install packages : make gcc-core zlib-devel - -3) Run Cygwin.bat - -4) cd to %ZAPRET_BASE%/nfq - -cd C:/Users/user/Downloads/zapret/nfq - -5) Compile - -make cygwin64 - -use winws.exe - -6) Take windivert.dll and windivert64.sys here : https://reqrypt.org/download -Choose version 2.2.2 for Windows 10 and 2.2.0 for Windows 7. - -7) Copy cygwin1.dll, winws.exe, windivert.dll and windivert64.sys to one folder. - -8) Run winws.exe from cmd.exe running as administrator. -winws will not run from cygwin shell with cygwin1.dll copy in it's folder. -winws will not run without cygwin1.dll outside of cygwin shell. diff --git a/docs/compile/openwrt/package/zapret/ip2net/Makefile b/docs/compile/openwrt/package/zapret/ip2net/Makefile deleted file mode 100644 index bec7c009..00000000 --- a/docs/compile/openwrt/package/zapret/ip2net/Makefile +++ /dev/null @@ -1,32 +0,0 @@ -# - -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/binaries/my - $(INSTALL_BIN) $(PKG_BUILD_DIR)/ip2net $(1)/opt/zapret/binaries/my -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 deleted file mode 100644 index a5674f51..00000000 --- a/docs/compile/openwrt/package/zapret/ip2net/readme.txt +++ /dev/null @@ -1 +0,0 @@ -Copy "ip2net" folder here ! diff --git a/docs/compile/openwrt/package/zapret/mdig/Makefile b/docs/compile/openwrt/package/zapret/mdig/Makefile deleted file mode 100644 index e587539d..00000000 --- a/docs/compile/openwrt/package/zapret/mdig/Makefile +++ /dev/null @@ -1,32 +0,0 @@ -# - -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/binaries/my - $(INSTALL_BIN) $(PKG_BUILD_DIR)/mdig $(1)/opt/zapret/binaries/my -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 deleted file mode 100644 index b1865b23..00000000 --- a/docs/compile/openwrt/package/zapret/mdig/readme.txt +++ /dev/null @@ -1 +0,0 @@ -Copy "mdig" folder here ! diff --git a/docs/compile/openwrt/package/zapret/nfqws/Makefile b/docs/compile/openwrt/package/zapret/nfqws/Makefile deleted file mode 100644 index 35c983f3..00000000 --- a/docs/compile/openwrt/package/zapret/nfqws/Makefile +++ /dev/null @@ -1,34 +0,0 @@ -# - -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 +lmnl +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/binaries/my - $(INSTALL_BIN) $(PKG_BUILD_DIR)/nfqws $(1)/opt/zapret/binaries/my -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 deleted file mode 100644 index 4c5b89fd..00000000 --- a/docs/compile/openwrt/package/zapret/nfqws/readme.txt +++ /dev/null @@ -1 +0,0 @@ -Copy "nfq" folder here ! diff --git a/docs/compile/openwrt/package/zapret/tpws/Makefile b/docs/compile/openwrt/package/zapret/tpws/Makefile deleted file mode 100644 index 831c2550..00000000 --- a/docs/compile/openwrt/package/zapret/tpws/Makefile +++ /dev/null @@ -1,33 +0,0 @@ -# - -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/binaries/my - $(INSTALL_BIN) $(PKG_BUILD_DIR)/tpws $(1)/opt/zapret/binaries/my -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 deleted file mode 100644 index 60297f89..00000000 --- a/docs/compile/openwrt/package/zapret/tpws/readme.txt +++ /dev/null @@ -1 +0,0 @@ -Copy "tpws" folder here ! diff --git a/docs/iptables.txt b/docs/iptables.txt deleted file mode 100644 index 742f926c..00000000 --- a/docs/iptables.txt +++ /dev/null @@ -1,63 +0,0 @@ -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 dpi desync attack : - -iptables -t mangle -I POSTROUTING -p tcp -m multiport --dports 80,443 -m connbytes --connbytes-dir=original --connbytes-mode=packets --connbytes 1:6 -m mark ! --mark 0x40000000/0x40000000 -j NFQUEUE --queue-num 200 --queue-bypass -iptables -t mangle -I POSTROUTING -p tcp --dport 443 -m mark ! --mark 0x40000000/0x40000000 -j NFQUEUE --queue-num 200 --queue-bypass -iptables -t mangle -I POSTROUTING -p udp --dport 443 -m mark ! --mark 0x40000000/0x40000000 -j NFQUEUE --queue-num 200 --queue-bypass - -# auto hostlist with avoiding wrong ACK numbers in RST,ACK packets sent by russian DPI -sysctl net.netfilter.nf_conntrack_tcp_be_liberal=1 -iptables -t mangle -I POSTROUTING -p tcp -m multiport --dports 80,443 -m connbytes --connbytes-dir=original --connbytes-mode=packets --connbytes 1:12 -m mark ! --mark 0x40000000/0x40000000 -j NFQUEUE --queue-num 200 --queue-bypass -iptables -t mangle -I PREROUTING -p tcp -m multiport --sports 80,443 -m connbytes --connbytes-dir=reply --connbytes-mode=packets --connbytes 1:3 -m mark ! --mark 0x40000000/0x40000000 -j NFQUEUE --queue-num 200 --queue-bypass - - -For TPROXY : - -sysctl -w net.ipv4.ip_forward=1 -iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE - -ip -f inet rule add fwmark 1 lookup 100 -ip -f inet route add local default dev lo table 100 -# prevent loop -iptables -t filter -I INPUT -p tcp --dport 988 -j REJECT -iptables -t mangle -A PREROUTING -i eth1 -p tcp --dport 80 -j MARK --set-mark 1 -iptables -t mangle -A PREROUTING -i eth1 -p tcp --dport 80 -j TPROXY --tproxy-mark 0x1/0x1 --on-port 988 - -iptables -t mangle -A PREROUTING -i eth1 -p tcp --dport 80 -m set --match-set zapret dst -j MARK --set-mark 1 -iptables -t mangle -A PREROUTING -i eth1 -p tcp --dport 80 -m mark --mark 0x1/0x1 -j TPROXY --tproxy-mark 0x1/0x1 --on-port 988 - -For DNAT : - -# run tpws as user "tpws". its required to avoid loops. -sysctl -w net.ipv4.conf.eth1.route_localnet=1 -iptables -t nat -I PREROUTING -p tcp --dport 80 -j DNAT --to 127.0.0.127:988 -iptables -t nat -I OUTPUT -p tcp --dport 80 -m owner ! --uid-owner tpws -j DNAT --to 127.0.0.127:988 - - -Reset all iptable rules : - -iptables -F -iptables -X -iptables -t nat -F -iptables -t nat -X -iptables -t mangle -F -iptables -t mangle -X -iptables -t raw -F -iptables -t raw -X - -Reset iptable policies : - -iptables -P INPUT ACCEPT -iptables -P FORWARD ACCEPT -iptables -P OUTPUT ACCEPT -iptables -t mangle -P POSTROUTING ACCEPT -iptables -t mangle -P PREROUTING ACCEPT -iptables -t mangle -P INPUT ACCEPT -iptables -t mangle -P FORWARD ACCEPT -iptables -t mangle -P OUTPUT ACCEPT -iptables -t raw -P PREROUTING ACCEPT -iptables -t raw -P OUTPUT ACCEPT diff --git a/docs/nftables.txt b/docs/nftables.txt deleted file mode 100644 index 43f21504..00000000 --- a/docs/nftables.txt +++ /dev/null @@ -1,32 +0,0 @@ -nftables test cheat sheet -simplified rules to test nfqws and tpws - - -For DNAT : - -# run tpws as user "tpws". its required to avoid loops. - -nft delete table inet ztest -nft create table inet ztest -nft add chain inet ztest pre "{type nat hook prerouting priority dstnat;}" -nft add rule inet ztest pre tcp dport "{80,443}" redirect to :988 -nft add chain inet ztest out "{type nat hook output priority -100;}" -nft add rule inet ztest out tcp dport "{80,443}" skuid != tpws redirect to :988 - - -For dpi desync attack : - -nft delete table inet ztest -nft create table inet ztest -nft add chain inet ztest post "{type filter hook postrouting priority mangle;}" -nft add rule inet ztest post meta mark and 0x40000000 == 0 tcp dport "{80,443}" ct original packets 1-6 queue num 200 bypass -nft add rule inet ztest post meta mark and 0x40000000 == 0 udp dport 443 ct original packets 1-6 queue num 200 bypass - -# auto hostlist with avoiding wrong ACK numbers in RST,ACK packets sent by russian DPI -sysctl net.netfilter.nf_conntrack_tcp_be_liberal=1 -nft add chain inet ztest pre "{type filter hook prerouting priority filter;}" -nft add rule inet ztest pre tcp sport "{80,443}" ct reply packets 1-3 queue num 200 bypass - - -show rules : nft list table inet ztest -delete table : nft delete table inet ztest diff --git a/docs/nftables_notes.txt b/docs/nftables_notes.txt deleted file mode 100644 index 75fa845f..00000000 --- a/docs/nftables_notes.txt +++ /dev/null @@ -1,120 +0,0 @@ -nftables - это технология, пришедшая на замену iptables. -В ней собрали все, что относилось к различным iptables. А их немало. iptables, ip6tables, ebtables, arptables, ipset. -Весь код из разрозненных, но похожих компонент, собрали в одно целое с единым синтаксисом. -Добавили различные конструкции языка, позволяющие писать правила более лаконично, не повторяя одни и те же команды с небольшими различиями. -На nftables можно сделать почти все, что можно было сделать на iptables. Есть то, что можно сделать на nftables, но нельзя на iptables. -Есть и наоборот. - -К сожалению, не обошлось и без боли. - -Главная боль N1. Очень серьезная, актуальная для openwrt, и решения не видно. - -ipset-ы позволяли загонять пересекающиеся интервалы ip адресов или подсетей. -nftables sets это не позволяют. Любое пересечение вызывает ошибку. -Есть auto-merge, но это работает только в user mode в процессе nft, при условии, что весь блок адресов загоняется одной командой -и нет пересечений с уже имеющимся контентом в set. -Это не было бы критической проблемой, поскольку скрипты zapret и так загоняют ipset целиком. -Проблема в катастрофическом расходе памяти при операции загона больших интервальных листов, то есть с подсетями и диапазонами. -Чтобы загнать 100000 ipv4 записей, едва хватает 300 Mb памяти устройства. -При успехе операции в ядре список столько не занимает, но суть дела это не меняет. -Для традиционных linux систем это не проблема, но почти все роутеры загнутся. -Приемлемого решения не просматривается. -Сделать записи непересекающимися в листах - задача непростая. Потребуется переписать алгоритм auto-merge из nft, -но с пониженным расходом памяти. -Загонять записи по одному отдельными вызовами nft, игнорируя ошибки, займет вечность. -Загонять блоком отдельных команд, игнорируя ошибки, - nft такого не умеет. Похоже, при любой ошибке происходит откат всего скрипта. -К тому же при таком подходе будут неточности в итоговом результате. -Swap позволяет немного сгладить проблему, но лишь незначительно. -Скажем, если вдруг list загоняется без ошибок с 300 Mb памяти и с падением на 256 Mb, то swap спасает. -Если памяти становится 200 Mb, то swap уже не спасет. Все равно вызывается OOM killer, заодно убивая и другие процессы, кроме nft, -а это уже совсем плохо. Может быть убито что-то важное. - -Боль N2, не смертельная, но тоже не айс. - -Какие-то нерациональные алгоритмы разбора таблиц в nft. -Например, есть 1 большой set на 100000 элементов и 1 маленький на 2 элемента. -Чтобы просто пролистать мелкий set или добавить туда еще что-то nft будет мусолить несколько секунд. -Что он делает за это время ? Тащит из ядра огромный блоб, в котором все в куче, и разбирает его, чтобы выделить искомую мелочь ? -В какой-то мере удается это сгладить, обьединяя несколько команд в единый скрипт. - -Боль N3 - -Система nftables построена на виртуальной машине. Правила, которые вы пишите, переводятся в псевдокод VM. -Чтобы потом их показать , nft декомпилирует код и переводит в читаемый язык. -Это довольно сложно, и регулярно случаются баги, связанные с неверным отображением. - -Кроме этого, часто встречаются и баги парсера. -Например, все версии nft вплоть до 1.0.1 имеют баг, который не разрешает названия интерфейсов в кавычках в -определении flowtable. Без кавычек нельзя вставить интерфейсы , имя которых начинается с цифры. -OpenWRT решает эту проблему отдельным патчем в snapshot версии, но на традиционных системах и в openwrt 21.x- его нет. -Почему бы не наплевать на интерфейсы, начинающиеся с цифры ? Потому что для openwrt 6to4-6to4, 6in4-he-net - обычное явление. -На текущий момент этой проблемы в openwrt уже нет, если использовать актуальную версию. - -Но тем не менее, хоть nft и давно перешел отметку 1.0, всякая мелочь, особенно на не совсем стандартных правилах, -регулярно всплывает. Потому чем новее у вас будет версия nft, тем больше там выловлено проблем. -Здесь обновления важны, чтобы потом не мучаться из-за давно исправленного велосипеда. - -Боль N4 - -Невозможно , не копаясь в других таблицах и хуках, ничего не зная об их содержании, предотвратить DROP или REJECT. -Нельзя написать такое правило, которое что-то важное ACCEPTнет, игнорируя остальные хуки во всех таблицах. -Если у вас есть какой-то фаервол, и он что-то дропает, то как от этого отказаться, если надо временно что-то принять ? -Это особенность netfilter, он так работает, но в iptables есть лишь стандартные таблицы с их хуками, куда можно -вставить ACCEPT. Здесь хуков может быть сколько угодно в каких угодно таблицах. -Эта проблема частично ломает кайф от независимого управления таблицами. - - -Плюс N1, главный - -iptables хороши, когда ими пользуется кто-то один. Иначе это проходной двор. -Когда есть система управления фаерволом, то приходится как-то к ней прикручиваться, чтобы не нарушить ее работу -и управлять правилами синхронно. Нужно уметь внести и удалить отдельные правила когда это нужно, не трогая все остальное. -Некоторые системы управления фаерволом вообще не предполагают, чтобы кто-то еще лез в iptables, и это очень сильно портит жизнь. -У iptables есть предопределенный набор хуков netfilter с фиксированным приоритетом. -В nftables хуков можно создать неограниченное количество с выбранным приоритетом, управляя ими независимо в отдельных таблицах. -Система управления фаерволом может работать в одной таблице (fw4 в случае openwrt) и не трогать все остальное. -zapret может работать в другой таблице и не трогать систему управления фаерволом. Они друг другу не мешают. -Это снимает множество боли. -Но есть и исключение. nfset-ы - аналог ipset-ов - нельзя использовать из другой таблицы. Потому если вам нужен ipset, -создаваемый zapret скриптами, вам понадобится писать правила в той же таблице. Но нет никакой необходимости влезать в цепочки zapret. -Создаете свои цепочки и хуки и делаете в них что угодно. - -Плюс N2 - -Возможность выбора приоритета хука позволяет легко решить проблему хаотической и принудительной дефрагментацией L3 ipv6, -без танцев с загрузкой модулей ядра со специальными параметрами или перекомпиляцией nftables-nft. -Это же позволяет перехватить трафик после SNAT/MASQUERADE, что на iptables невозможно. -Атаки на проходящий трафик, ломающие NAT, крайне затруднены на iptables. - -Плюс N3 - -Наличие множеств (anonymous/named sets) позволяет не писать кучу однообразных правил там, где в iptables их пришлось бы написать. - -Плюс N4 - -Если у вас есть nftables, то там наверняка есть уже все или почти все. -Нет кучи разных модулей ядра и .so плагинов для iptables user-mode процесса. -Отдельные модули ядра есть, но их меньше, чем в iptables, и openwrt их делит на меньшее число пакетов, большинство из которых -и так ставятся по умолчанию. user-mode процесс nft и вовсе неделим. EXE-шник + lib. - -Плюс N5 - -Пишут, что nftables работают быстрее. Но это не точно и зависит от много чего. -В целом - чем меньше правил, тем выше скорость. Но в nftables правил можно писать меньше, значит скрость тоже может быть выше. -У разработчиков есть идея перевести backend nftables на BPF, а это наверняка будет существенно быстрее. - - -Выводы - -Без больших листов все почти прекрасно. Но большие ip листы убивают все. Не для домашних это роутеров. -А ipset-ы к nftables не прикрутить. -Зато есть возможность задействовать более продвинутые атаки, конфликтующие с NAT, которые на iptables могут быть невозможны. -Делать нечего. Openwrt отошел от iptables. С этим придется как-то жить. -Поэтому пришлось сделать для openwrt поддержку и iptables, и nftables (только с версии openwrt 21.xx, в более старых будут проблемы). -iptables можно задействовать на любой openwrt версии. -Если используется fw3, применяется старый механизм интеграции в fw3. -Если он не используется, то правилами iptables управляем как в традиционных linux системах - то есть с возможностью -запуска и остановки, а скрипт запуска вносит в том числе и правила iptables. - -На новых openwrt возможно снести nftables и firewall4 и установить firewall3 и iptables. -Если вам никак без больших ip листов на слабой системе, это может быть единственным спасением. diff --git a/docs/quick_start.md b/docs/quick_start.md deleted file mode 100644 index e19e3587..00000000 --- a/docs/quick_start.md +++ /dev/null @@ -1,274 +0,0 @@ -# Быстрая настройка Linux/OpenWrt - -> [!CAUTION] -> Не пишите в issue вопросы типа "как скопировать файл", "как скачать", "как -> запустить" и т.п. То есть все, что касается базовых навыков обращения с ОС -> Linux. Эти вопросы будут закрывать сразу. Если у вас подобные вопросы -> возникают, рекомендую не использовать данный софт или искать помощь где-то в -> другом месте. То же самое могу сказать тем, кто хочет нажать 1 кнопку, чтобы -> все заработало, и совсем не хочет читать и изучать. Увы, такое не подвезли и -> не подвезут. Ищите другие более простые методы обхода. Этот метод **не для -> рядового пользователя**. - - -## Вступление -Специально для тех, кто хочет побыстрее начать, но не хочет слишком углубляться -в простыню [readme.md](readme.md). - -Обход DPI является хакерской методикой. Под этим словом понимается метод, -которому оказывается активное противодействие и поэтому автоматически не -гарантирована работоспособность в любых условиях и на любых ресурсах, требуется -настройка под специфические условия у вашего провайдера. Условия могут меняться -со временем, и методика может начинать или переставать работать, может -потребоваться повторный анализ ситуации. Могут обнаруживаться отдельные -ресурсы, которые заблокированы иначе, и которые не работают или перестали -работать. Могут и сломаться отдельные не заблокированные ресурсы. Поэтому очень -желательно иметь знания в области сетей, чтобы иметь возможность -проанализировать техническую ситуацию. Не будет лишним иметь обходные каналы -проксирования трафика на случай, если обход DPI не помогает. - -Вариант, когда вы нашли стратегию где-то в интернете и пытаетесь ее приспособить к своему случаю - заведомо проблемный. -Нет универсальной таблетки. Везде ситуация разная. В сети гуляют написанные кем-то откровенные глупости, которые тиражируются массово ничего не понимающей публикой. -Такие варианты чаще всего работают нестабильно, только на части ресурсов, только на части провайдеров, не работают вообще или ломают другие ресурсы. В худших случаях еще и устраивают флуд в сети. -Если даже вариант когда-то и работал неплохо, завтра он может перестать, а в сети останется устаревшая информация. - -Будем считать, что у вас есть система на базе традиционного **linux** или -**openwrt**. Если у вас традиционный linux - задача обойти блокировки только на -этой системе, если openwrt - обойти блокировки для подключенных устройств. Это -наиболее распространенный случай. - - -## Настройка -> [!TIP] -> Чтобы процедура установки сработала в штатном режиме на openwrt, нужно -> рассчитывать на свободное место около 1-2 Mb для установки самого zapret и -> необходимых дополнительных пакетов. Если места мало и нет возможности его -> увеличить за счет `extroot`, возможно придется отказаться от варианта простой -> установки и прикручивать в ручном режиме без имеющихся скриптов запуска. -> Можно использовать [облегченный `tpws` вариант](../init.d/openwrt-minimal), -> либо попробовать засунуть требуемые zapret дополнительные пакеты в сжатый -> образ `squashfs` с помощью `image builder` и перешить этим вариантом роутер. - -1. Скачайте последний [tar.gz релиз](https://github.com/bol-van/zapret/releases) в /tmp, распакуйте его, затем удалите архив. - Для openwrt и прошивок используйте вариант `openwrt-embedded`. - Для экономия места в /tmp можно качать через curl в stdout и сразу распаковывать. - Пример под openwrt для версии zapret 71.4 (для других URL отличается) : - ``` - $ curl -Lo - https://github.com/bol-van/zapret/releases/download/v71.4/zapret-v71.4-openwrt-embedded.tar.gz | tar -zx - $ wget -O - https://github.com/bol-van/zapret/releases/download/v71.4/zapret-v71.4-openwrt-embedded.tar.gz | tar -zx - ``` - Пример под традиционный linux для версии zapret 71.4 (для других URL отличается) : - ``` - $ curl -Lo - https://github.com/bol-van/zapret/releases/download/v71.4/zapret-v71.4.tar.gz | tar -zx - $ wget -O - https://github.com/bol-van/zapret/releases/download/v71.4/zapret-v71.4.tar.gz | tar -zx - ``` - curl должен быть предварительно установлен. Но он в любом случае понадобится далее. - Вариант с wget будет работать только если установленный wget поддерживает https. - -2. Убедитесь, что у вас отключены все средства обхода блокировок, в том числе и - сам zapret. Гарантированно уберет zapret скрипт `uninstall_easy.sh`. - -3. Если вы работаете в виртуальной машине, необходимо использовать соединение с - сетью в режиме bridge. NAT **не** подходит. - -4. Выполните однократные действия по установке требуемых пакетов в ОС и - настройке исполняемых файлов правильной архитектуры: - ```sh - $ install_bin.sh - $ install_prereq.sh - ``` - - > Вас могут спросить о типе фаервола (iptables/nftables) и использовании - > ipv6. Это нужно для установки правильных пакетов в ОС, чтобы не - > устанавливать лишнее. - -5. Запустите `blockcheck.sh`. Скрипт вначале проверяет DNS. Если выводятся - сообщения о подмене адресов, то нужно будет решить проблему с DNS. - `blockcheck.sh` перейдет в этом случае на DoH и будет пытаться получить и - использовать реальные IP адреса. Но если вы не настроите решение этой - проблемы, обход будет работать только для тех программ или ОС, которые сами - реализуют механизмы SecureDNS. Для других программ обход работать не будет. - - > Решение проблемы DNS выходит за рамки проекта. Обычно она решается либо - > заменой DNS серверов от провайдера на публичные (`1.1.1.1`, `8.8.8.8`), - > либо в случае перехвата провайдером обращений к сторонним серверам - через - > специальные средства шифрования DNS запросов, такие как `dnscrypt`, `DoT`, - > `DoH`. - > - > Еще один эффективный вариант - использовать ресолвер от yandex - > (`77.88.8.88`) на нестандартном порту `1253`. Многие провайдеры не - > анализируют обращения к DNS на нестандартных портах. - > - > Проверить работает ли этот вариант можно так: - > ```sh - > $ dig -p 53 @77.88.8.88 rutracker.org - > $ dig -p 1253 @77.88.8.88 rutracker.org - > ``` - > - > Если DNS действительно подменяется, и ответ на эти 2 команды разный, - > значит метод вероятно работает. - > - > В openwrt DNS на нестандартном порту можно прописать в `/etc/config/dhcp` - > таким способом: - > - > ``` - > config dnsmasq - > <...> - > list server '77.88.8.88#1253' - > ``` - > - > Если настройки IP и DNS получаются автоматически от провайдера, в - > `/etc/config/network` найдите секцию интерфейса `wan` и сделайте так: - > - > ``` - > config interface 'wan' - > <...> - > option peerdns '0' - > ``` - > - > ```sh - > $ /etc/init.d/network restart - > $ /etc/init.d/dnsmasq restart - > ``` - > - > Если это не подходит, можно перенаправлять обращения на UDP и TCP порты - > `53` вашего DNS сервера на `77.88.8.88:1253` средствами - > `iptables`/`nftables`. В `/etc/resolv.conf` нельзя прописать DNS на - > нестандартном порту. - -6. `blockcheck.sh` позволяет выявить рабочую стратегию обхода блокировок. По - результатам скрипта нужно понять какой вариант будете использовать : `nfqws` - или `tpws` и запомнить найденные стратегии для дальнейшего применения. - - Следует понимать, что скрипт проверяет доступность только конкретного - домена, который вы вводите в начале, конкретной программой curl. - У разных клиентов есть свой фингерпринт. У броузеров один, у curl другой. - Может применяться или не применяться многопакетный TLS с постквантовой криптографией (kyber). - От этого может зависеть работоспособность стратегий. - Обычно остальные домены блокированы подобным образом, **но не факт**. - Бывают специальные блокировки. Некоторые параметры требуют тюнинга под "общий знаменатель". - В большинстве случаев можно объединить несколько стратегий в одну универсальную, и это **крайне - желательно**, но это требует понимания как работают стратегии. zapret **не может - пробить блокировку по IP адресу**. Для проверки нескольких доменов вводите - их через пробел. - - > Сейчас блокираторы ставят на магистральных каналах. В сложных случаях у - > вас может быть несколько маршрутов с различной длиной по ХОПам, с DPI на - > разных хопах. Приходится преодолевать целый зоопарк DPI, которые еще и - > включаются в работу хаотичным образом или образом, зависящим от - > направления (IP сервера). скрипт не всегда может выдать вам в итогах - > оптимальную стратегию, которую надо просто переписать в настройки. В - > некоторых случаях надо реально думать что происходит, анализируя результат - > на разных стратегиях. - > - > Далее, имея понимание что работает на http, https, quic нужно - > сконструировать параметры запуска `tpws` и/или `nfqws` с использованием - > мультистратегии. Как работают мультистратегии описано в [readme.md](./readme.md#множественные-стратегии). - > - > Если кратко, то обычно параметры конструируются так: - > ```sh - > "--filter-udp=443 'параметры для quic' --new - > --filter-tcp=80,443 'объединенные параметры для http и https' " - > ``` - > - > Или так: - > ```sh - > "--filter-udp=443 'параметры для quic' --new - > --filter-tcp=80 'параметры для http' --new - > --filter-tcp=443 'параметры для https' " - > ``` - > - > `` и `` так и пишутся. Их не надо на что-то - > заменять. Это сделают скрипты запуска, если вы выбрали режим фильтрации по - > хостлистам, и уберут в противном случае. Если для какого-то протокола надо - > дурить все без стандартного хостлиста - просто уберите оттуда `` - > и ``. Можно писать свои параметры `--hostlist` и - > `--hostlist-exclude` для дополнительных хостлистов или в профилях - > специализаций под конкретный ресурс. В последнем случае стандартный - > хостлист там не нужен. Следует избегать указания собственных параметров - > `--hostlist` на листы из директории ipset. Эта логика включена в - > `` и ``. Отличие `` в том, что - > стандартный автолист по этому профилю используется как обычный, то есть - > без автоматического добавления доменов. Однако, добавления в других - > профилях автоматически отражаются во всех остальных. - > - > Если стратегии отличаются по версии ip протокола, и вы не можете их - > обьединить, фильтр пишется так: - > ```sh - > "--filter-l3=ipv4 --filter-udp=443 lпараметры для quic ipv4' --new - > --filter-l3=ipv4 --filter-tcp=80 'параметры для http ipv4' --new - > --filter-l3=ipv4 --filter-tcp=443 'параметры для https ipv4' --new - > --filter-l3=ipv6 --filter-udp=443 'параметры для quic ipv6' --new - > --filter-l3=ipv6 --filter-tcp=80 'параметры для http ipv6' --new - > --filter-l3=ipv6 --filter-tcp=443 'параметры для https ipv6' " - > ``` - > - > Но здесь совсем "копи-пастный" вариант. Чем больше вы объедините стратегий и - > сократите их общее количество, тем будет лучше. - > - > Если вам не нужно дурение отдельных протоколов, лучше всего будет убрать - > лишние порты из системы перехвата трафика через параметры `TPWS_PORTS`, - > `NFQWS_PORTS_TCP`, `NFQWS_PORTS_UDP` и убрать соответствующие им профили - > мультистратегии. - > - > | Протокол | Порт | Примечание | - > |---|---|---| - > | `tcp` | `80` | `http` соединение | - > | `tcp` | `443` | `https` соединение | - > | `udp` | `443` | `quic` соединение | - > - > Если используются методы нулевой фазы десинхронизации (`--mss`, - > `--wssize`, `--dpi-desync=syndata`) и режим фильтрации `hostlist`, то все - > параметры, относящиеся к этим методам, следует помещать в отдельные - > профили мультистратегии, которые получат управление до определения имени - > хоста. Необходимо понимать алгоритм работы мультистратегий. Самым надежным - > вариантом будет дублирование этих параметров на 2 профиля. Какой-нибудь - > сработает в зависимости от параметра `MODE_FILTER`. - > - > ```sh - > "--filter-tcp=80 'параметры для http' --new - > --filter-tcp=443 'параметры для https' --wssize 1:6 --new - > --filter-tcp=443 --wssize 1:6" - > ``` - > - > В этом примере `wssize` будет применяться всегда к порту tcp `443` вне - > зависимости от параметра `MODE_FILTER`. Хостлист будет игнорироваться, - > если таковой имеется. К http применять `wssize` вредно и бессмысленно. - > - > Иногда требуется дописать к стратегиям свои собственные параметры. - > Например, нужно изменить количество повторов фейков или задать свой фейк. - > Это делается через шелл-переменные `PKTWS_EXTRA`, `TPWS_EXTRA`. - > - > ```PKTWS_EXTRA="--dpi-desync-repeats=10 --dpi-desync-fake-tls=/tmp/tls.bin" ./blockcheck.sh``` - > - > Перебор всех комбинаций может привести к ожиданию неделями, поэтому выбран разумный - > костяк проверки, на который вы можете навешивать свои кустомизации. - -7. Запустите скрипт облегченной установки - `install_easy.sh` Выберите `nfqws` - и/или `tpws`, затем согласитесь на редактирование параметров. Откроется - редактор, куда впишите созданную на предыдущем этапе стратегию. - -8. На все остальные вопросы `install_easy.sh` отвечайте согласно выводимой - аннотации. - -9. Удалите директорию из /tmp, откуда производилась установка. - -## Полное удаление - -1. Прогоните `/opt/zapret/uninstall_easy.sh`. -2. Cогласитесь на удаление зависимостей в openwrt. -3. Удалите каталог `/opt/zapret`. - -## Итог -Это минимальная инструкция, чтобы быстро сориентироваться с чего начать. -Однако, это не гарантированное решение и в некоторых случаях вы не обойдетесь -без знаний и основного "талмуда". Подробности и полное техническое описание -расписаны в [README](readme.md). - -Если ломаются отдельные **не заблокированные** ресурсы, следует вносить их в -исключения, либо пользоваться ограничивающим `ipset` или хост листом. Лучше -подбирать такие стратегии, которые вызывают минимальные поломки. Есть стратегии -довольно безобидные, а есть сильно ломающие, которые подходят только для -точечного пробития отдельных ресурсов, когда ничего лучше нет. Хорошая -стратегия может сильно ломать из-за плохо подобранных ограничителей для фейков -\- ttl, fooling. diff --git a/docs/quick_start_windows.md b/docs/quick_start_windows.md deleted file mode 100644 index 1b7f8a47..00000000 --- a/docs/quick_start_windows.md +++ /dev/null @@ -1,205 +0,0 @@ -# Быстрая настройка Windows - -Специально для тех, кто хочет побыстрее начать, но не хочет слишком углубляться в простыню [readme.md](./readme.md). -> [!CAUTION] -> Как обычно, компьютерная грамотность ложится полностью на вас. -> Вы должны уметь работать с консолью windows и иметь минимальные навыки обращения с командными файлами `bat`, `cmd`. -> Если грамотность отсутствует и возникает куча _"как?"_ на базовых вещах, значит эта программа не для вас. -> Разработчик не будет отвечать на вопросы из серии школы компьютерной грамотности. -> Если вы все-таки хотите продолжать, задавайте вопросы в дискуссиях на github или на форумах. -> Возможно, кто-то вам поможет. Но не надо писать issue на github. Они будут закрываться сразу. - -## Немного разъяснений - -Обход DPI является хакерской методикой. Под этим словом понимается метод, которому оказывается активное противодействие и поэтому -автоматически не гарантирована работоспособность в любых условиях и на любых ресурсах, -требуется настройка под специфические условия у вашего провайдера. Условия могут меняться со временем, -и методика может начинать или переставать работать, может потребоваться повторный анализ ситуации. -Могут обнаруживаться отдельные ресурсы, которые заблокированы иначе, и которые не работают или перестали работать. -Могут и сломаться отдельные незаблокированные ресурсы. -Поэтому очень желательно иметь знания в области сетей, чтобы иметь возможность проанализировать техническую ситуацию. -Не будет лишним иметь обходные каналы проксирования трафика на случай, если обход DPI не помогает. - -Вариант, когда вы нашли стратегию где-то в интернете и пытаетесь ее приспособить к своему случаю - заведомо проблемный. -Нет универсальной таблетки. Везде ситуация разная. В сети гуляют написанные кем-то откровенные глупости, которые тиражируются массово ничего не понимающей публикой. -Такие варианты чаще всего работают нестабильно, только на части ресурсов, только на части провайдеров, не работают вообще или ломают другие ресурсы. В худших случаях еще и устраивают флуд в сети. -Если даже вариант когда-то и работал неплохо, завтра он может перестать, а в сети останется устаревшая информация. - -Особо осторожным нужно быть со сторонними сборками. Там могут быть вирусы. Не в каждой сборке, но уже были замечены скаммеры. -Видео на ютубе как просто обойти блокировку, прилагающийся архив, в котором какая-то ерунда, написанная на питоне, скачивающая зловред. - -Будем считать, что у вас есть windows 7 или выше. Задача - обойти блокировки с самой системы. - -## Я ЧТО-ТО ДЕЛАЛ, НЕ ПОМОГЛО, КАК ТЕПЕРЬ ЭТО УДАЛИТЬ - -Если вы не устанавливали zapret как службу или запланированную задачу (а это требует редактирования cmd файлов), -достаточно закрыть окно с winws и запустить windivert_delete.cmd. -Альтернатива - перезагрузить компьютер. -После чего можно удалить папку с zapret. На этом деинсталляция закончена. -Если же вы устанавливали zapret как службу, то вы наверняка знаете как ее удалить. - -Если вдруг среди того, на что вы нажимали, есть слова "general", "alt", ".bat", "автозапуск", или же есть файлы, которые отсутствуют -в оригинальных репозиториях, то это сборка. Вы не получите ответа как ее удалить от разработчика zapret. -Спрашивайте самих сборщиков. Разработчик не предоставляет простого решения, этим занимаются сборщики, но они и сами отвечают за свои продукты. - -## Настройка - -1. Скачайте и распакуйте архив https://github.com/bol-van/zapret-win-bundle/archive/refs/heads/master.zip. - -2. Если у вас Windows 7 x64, однократно запустите `win7/install_win7.cmd`. Батник заменит файлы windivert на совместимую с Windows 7 версию. - - > Для 32-битных систем Windows нет готового полного варианта. - - > На windows 11 arm64 выполните `arm64/install_arm64.cmd` от имени администратора и перезагрузите компьютер. - > Читайте [docs/windows.md](./windows.md) - > - > Имейте в виду, что антивирусы могут плохо реагировать на windivert. - > cygwin так же имеет внушительный список несовместимостей с антивирусами, хотя современные антивирусы - > более-менее научились с ним дружить. - > Если проблема имеет место , используйте исключения. Если не помогает - отключайте антивирус совсем. - -3. Убедитесь, что у вас отключены все средства обхода блокировок, в том числе и сам zapret. - -4. Если вы работаете в виртуальной машине, необходимо использовать соединение с сетью в режиме bridge. nat не подходит - -5. Запустите `blockcheck\blockcheck.cmd`. blockcheck в начале проверяет **DNS**. - Если выводятся сообщения о подмене адресов, то нужно будет решить проблему с **DNS**. - blockcheck перейдет в этом случае на **DoH** _(DNS over HTTPS)_ и будет пытаться получить и использовать реальные IP адреса. - Но если вы не настроите решение этой проблемы, обход будет работать только для тех программ, - которые сами реализуют механизмы SecureDNS. Для других программ обход работать не будет. - - > Решение проблемы DNS выходит за рамки проекта. Обычно она решается либо заменой DNS серверов - > от провайдера на публичные (`1.1.1.1`, `8.8.8.8`), либо в случае перехвата провайдером обращений - > к сторонним серверам - через специальные средства шифрования DNS запросов, такие как [dnscrypt](https://www.dnscrypt.org/), **DoT** _(DNS over TLS)_, **DoH**. - > В современных броузерах чаще всего DoH включен по умолчанию, но curl будет использовать обычный системный DNS. - > win11 поддерживает системные DoH из коробки. Они не настроены по умолчанию. - > В последних билдах win10 существует неофициальный обходной путь для включения DoH. - > Для остальных систем нужно стороннее решение, работающие по принципу DNS proxy. - > - > Тут все разжевано как и где это включается : https://hackware.ru/?p=13707 - -6. blockcheck позволяет выявить рабочую стратегию обхода блокировок. - Лог скрипта будет сохранен в `blockcheck\blockcheck.log`. - Запомните/перепишите найденные стратегии. - - Следует понимать, что скрипт проверяет доступность только конкретного - домена, который вы вводите в начале, конкретной программой curl. - У разных клиентов есть свой фингерпринт. У броузеров один, у curl другой. - Может применяться или не применяться многопакетный TLS с постквантовой криптографией (kyber). - От этого может зависеть работоспособность стратегий. - Обычно остальные домены блокированы подобным образом, **но не факт**. - Бывают специальные блокировки. Некоторые параметры требуют тюнинга под "общий знаменатель". - В большинстве случаев можно объединить несколько стратегий в одну универсальную, и это **крайне - желательно**, но это требует понимания как работают стратегии. zapret **не может - пробить блокировку по IP адресу**. Для проверки нескольких доменов вводите их через пробел. - - > Сейчас блокираторы ставят на магистральных каналах. В сложных случаях у - > вас может быть несколько маршрутов с различной длиной по ХОПам, с DPI на - > разных хопах. Приходится преодолевать целый зоопарк DPI, которые еще и - > включаются в работу хаотичным образом или образом, зависящим от - > направления (IP сервера). скрипт не всегда может выдать вам в итогах - > оптимальную стратегию, которую надо просто переписать в настройки. В - > некоторых случаях надо реально думать что происходит, анализируя результат - > на разных стратегиях. - > - > Далее, имея понимание что работает на http, https, quic, нужно сконструировать параметры запуска winws - > с использованием мультистратегии. Как работают мультистратегии описано в [readme.md](./readme.md#множественные-стратегии). - > - > Прежде всего вам нужно собрать фильтр перехватываемого трафика. Это делается через параметры - > `--wf-l3`, `--wf-tcp`, `--wf-udp`. - > `--wf-l3` относится к версии ip протокола - ipv4 или ipv6. - > `--wf-tcp` и `--wf-udp` содержат перечень портов или диапазонов портов через запятую. - > - > Пример стандартного фильтра для перехвата http, https, quic : `--wf-tcp=80,443` `--wf-udp=443` - > - > Фильтр должен быть минимально необходимым. Перехват лишнего трафика приведет только к бессмысленному расходованию ресурсов процессора и замедлению интернета. - > - > Если кратко по мультистратегии, то обычно параметры конструируются так : - > ``` - > --filter-udp=443 'параметры для quic' --new - > --filter-tcp=80,443 'обьединенные параметры для http и https' - > ``` - > - > Или так : - > ``` - > --filter-udp=443 'параметры для quic' --new - > --filter-tcp=80 'параметры для http' --new - > --filter-tcp=443 'параметры для https' - > ``` - > - > Если стратегии отличаются по версии ip протокола, и вы не можете их обьединить, фильтр пишется так : - > ``` - > --filter-l3=ipv4 --filter-udp=443 "параметры для quic ipv4" --new - > --filter-l3=ipv4 --filter-tcp=80 'параметры для http ipv4' --new - > --filter-l3=ipv4 --filter-tcp=443 'параметры для https ipv4' --new - > --filter-l3=ipv6 --filter-udp=443 "параметры для quic ipv6" --new - > --filter-l3=ipv6 --filter-tcp=80 "параметры для http ipv6" --new - > --filter-l3=ipv6 --filter-tcp=443 "параметры для https ipv6" - > ``` - > - > Но здесь совсем _"копи-пастный"_ вариант. - > Чем больше вы обьедините стратегий и сократите их общее количество, тем будет лучше. - > - > Если вам не нужно дурение отдельных протоколов, лучше всего будет их убрать из системы перехвата трафика через - > параметры `--wf-*` и убрать соответствующие им профили мультистратегии. - > tcp 80 - http, tcp 443 - https, udp 443 - quic. - > - > Если используются методы нулевой фазы десинхронизации (`--mss`, `--wssize`, `--dpi-desync=syndata`) и фильтрация hostlist, - > то все параметры, относящиеся к этим методам, следует помещать в отдельные профили мультистратегии, которые получат - > управление до определения имени хоста. Необходимо понимать алгоритм работы мультистратегий. - > - > ``` - > --filter-tcp=80 'параметры для http' --new - > --filter-tcp=443 'параметры для https' --hostlist=d:/users/user/temp/list.txt --new - > --filter-tcp=443 --wssize 1:6 - > ``` - > - > autohostlist профиль приоритетен, поэтому wssize нужно писать туда : - > - > ``` - > --filter-tcp=80 'параметры для http' --new - > --filter-tcp=443 'параметры для https' --wssize 1:6 --hostlist-auto=d:/users/user/temp/autolist.txt - > ``` - > - > В этих примерах wssize будет применяться всегда к порту tcp 443, а хостлист будет игнорироваться. - > К http применять wssize вредно и бессмысленно. - > - > Иногда требуется дописать к стратегиям свои собственные параметры. - > Например, нужно изменить количество повторов фейков или задать свой фейк. - > Это делается через шелл-переменные `PKTWS_EXTRA`, `TPWS_EXTRA`. Пользуйтесь шеллом `cygwin/cygwin-admin.cmd`. - > - > ```PKTWS_EXTRA="--dpi-desync-repeats=10 --dpi-desync-fake-tls=/tmp/tls.bin" blockcheck``` - > - > Перебор всех комбинаций может привести к ожиданию неделями, поэтому выбран разумный - > костяк проверки, на который вы можете навешивать свои кустомизации. - -7. Протестируйте найденные стратегии на winws. Winws следует брать из zapret-winws. - Для этого откройте командную строку windows от имени администратора в директории zapret-winws. - Проще всего это сделать через `_CMD_ADMIN.cmd`. Он сам поднимет права и зайдет в нужную директорию. - -8. Обеспечьте удобную загрузку обхода блокировок. - - > Есть 2 варианта. Ручной запуск через ярлык или автоматический при старте системы, вне контекста текущего пользователя. - > Последний вариант разделяется на запуск через планировщик задач и через службы windows. - > - > Если хотите ручной запуск, скопируйте `preset_russia.cmd` в `preset_my.cmd` (`<ваше_название>.cmd`) и адаптируйте его под ваши параметра запуска. - > Потом можно создать ярлык на рабочем столе на `preset_my.cmd`. Не забудьте, что требуется запускать от имени администратора. - > - > Но лучше будет сделать неинтерактивный автоматический запуск вместе с системой. - > В zapret-winws есть командные файлы `task_*`, предназначенные для управления задачами планировщика. - > Там следует поменять содержимое переменной `WINWS1` на свои параметры запуска winws. Пути с пробелами нужно экранировать кавычками с обратным слэшем : `\"`. - > После создания задач запустите их. Проверьте, что обход встает после перезагрузки windows. - > - > Аналогично настраиваются и службы windows. Смотрите `service_*.cmd` - -9. Если ломаются отдельные незаблокированные ресурсы, нужно пользоваться ограничивающим - ipset или хост листом. Читайте основной талмуд [readme.md](./readme.md) ради подробностей. - Но еще лучше будет подбирать такие стратегии, которые ломают минимум. - Есть стратегии довольно безобидные, а есть сильно ломающие, которые подходят только для точечного пробития отдельных ресурсов, - когда ничего лучше нет. Хорошая стратегия может сильно ломать из-за плохо подобранных ограничителей для фейков - ttl, fooling. - -> [!CAUTION] -> Это минимальная инструкция, чтобы соориентироваться с чего начать. Однако, это - не панацея. -> В некоторых случаях вы не обойдетесь без знаний и основного "талмуда". - -Подробности и полное техническое описание расписаны в [readme.md](./readme.md) diff --git a/docs/readme.en.md b/docs/readme.en.md deleted file mode 100644 index 9c3d3bfb..00000000 --- a/docs/readme.en.md +++ /dev/null @@ -1,1709 +0,0 @@ -# SCAMMER WARNING - -This software is free and open source under [MIT license](./LICENSE.txt). -If anyone demands you to download this software only from their webpage, telegram channel, forces you to delete links, videos, makes copyright claims, you are dealing with scammers. -However, [donations](#donations) are welcome. - -# Multilanguage/Мультиязычный README -___ -[![en](https://img.shields.io/badge/lang-en-red.svg)](https://github.com/bol-van/zapret/tree/master/docs/readme.en.md) -[![ru](https://img.shields.io/badge/lang-ru-green.svg)](https://github.com/bol-van/zapret/tree/master/docs/readme.md) - -*** - -- [What is it for](#what-is-it-for) -- [How it works](#how-it-works) -- [How to put this into practice in the linux system](#how-to-put-this-into-practice-in-the-linux-system) -- [When it will not work](#when-it-will-not-work) -- [nfqws](#nfqws) - - [DPI desync attack](#dpi-desync-attack) - - [Fakes](#fakes) - - [Fake mods](#fake-mods) - - [TCP segmentation](#tcp-segmentation) - - [Sequence numbers overlap](#sequence-numbers-overlap) - - [IP_ID assignment](#ip_id-assignment) - - [ipv6 specific modes](#ipv6-specific-modes) - - [Original modding](#original-modding) - - [Duplicates](#duplicates) - - [Server reply reaction](#server-reply-reaction) - - [SYNDATA mode](#syndata-mode) - - [DPI desync combos](#dpi-desync-combos) - - [IP cache](#ip-cache) - - [CONNTRACK](#conntrack) - - [Reassemble](#reassemble) - - [UDP support](#udp-support) - - [IP fragmentation](#ip-fragmentation) - - [Multiple strategies](#multiple-strategies) - - [WIFI filtering](#wifi-filtering) - - [Virtual machines](#virtual-machines) - - [IPTABLES for nfqws](#iptables-for-nfqws) - - [NFTABLES for nfqws](#nftables-for-nfqws) - - [Flow offloading](#flow-offloading) - - [Server side fooling](#server-side-fooling) -- [tpws](#tpws) - - [TCP segmentation in tpws](#tcp-segmentation-in-tpws) - - [TLSREC](#tlsrec) - - [MSS](#mss) - - [Other tamper options](#other-tamper-options) - - [Supplementary options](#supplementary-options) - - [Multiple strategies](#multiple-strategies-1) - - [IPTABLES for tpws](#iptables-for-tpws) - - [NFTABLES for tpws](#nftables-for-tpws) -- [Ways to get a list of blocked IP](#ways-to-get-a-list-of-blocked-ip) -- [Domain name filtering](#domain-name-filtering) -- [**autohostlist** mode](#autohostlist-mode) -- [Choosing parameters](#choosing-parameters) -- [Screwing to the firewall control system or your launch system](#screwing-to-the-firewall-control-system-or-your-launch-system) -- [Installation](#installation) - - [Checking ISP](#checking-isp) - - [desktop linux system](#desktop-linux-system) - - [OpenWRT](#openwrt) - - [Android](#android) - - [FreeBSD, OpenBSD, MacOS](#freebsd-openbsd-macos) - - [Windows (WSL)](#windows-wsl) - - [Other devices](#other-devices) -- [Donations](#donations) -*** - -## What is it for - -A stand-alone (without 3rd party servers) DPI circumvention tool. -May allow to bypass http(s) website blocking or speed shaping, resist signature tcp/udp protocol discovery. - -The project is mainly aimed at the Russian audience. -Some features of the project are russian reality specific (such as getting list of sites -blocked by Roskomnadzor), but most others are common. - -Mainly OpenWRT targeted but also supports traditional Linux, FreeBSD, OpenBSD, Windows, partially MacOS. - -Most features are also supported in Windows. - -## How it works - -In the simplest case you are dealing with passive DPI. Passive DPI can read passthrough traffic, -inject its own packets, but cannot drop packets. - -If the request is prohibited the passive DPI will inject its own RST packet and optionally http redirect packet. - -If fake packets from DPI are only sent to client, you can use iptables commands to drop them if you can write -correct filter rules. This requires manual in-deep traffic analysis and tuning for specific ISP. - -This is how we bypass the consequences of a ban trigger. - -If the passive DPI sends an RST packet also to the server, there is nothing you can do about it. -Your task is to prevent ban trigger from firing up. Iptables alone will not work. -This project is aimed at preventing the ban rather than eliminating its consequences. - -To do that send what DPI does not expect and what breaks its algorithm of recognizing requests and blocking them. - -Some DPIs cannot recognize the http request if it is divided into TCP segments. -For example, a request of the form `GET / HTTP / 1.1 \ r \ nHost: kinozal.tv ......` -we send in 2 parts: first go `GET`, then `/ HTTP / 1.1 \ r \ nHost: kinozal.tv .....`. - -Other DPIs stumble when the `Host:` header is written in another case: for example, `host:`. - -Sometimes work adding extra space after the method: `GET /` => `GET /` -or adding a dot at the end of the host name: `Host: kinozal.tv.` - -There is also more advanced magic for bypassing DPI at the packet level. - -## How to put this into practice in the linux system - -In short, the options can be classified according to the following scheme: - -1. Passive DPI not sending RST to the server. ISP tuned iptables commands can help. -This option is out of the scope of the project. If you do not allow ban trigger to fire, then you won’t have to -deal with its consequences. -2. Modification of the TCP connection at the stream level. Implemented through a proxy or transparent proxy. -3. Modification of TCP connection at the packet level. Implemented through the NFQUEUE handler and raw sockets. - -For options 2 and 3, **tpws** and **nfqws** programs are implemented, respectively. -You need to run them with the necessary parameters and redirect certain traffic with iptables or nftables. - -## 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. This project targets DPI only, not full OS stack and not server applications. - -## nfqws - -This program is a packet modifier and a NFQUEUE queue handler. -For BSD systems there is dvtws. Its built from the same source and has almost the same parameters (see [bsd.en.md](./bsd.en.md)). -nfqws takes the following parameters: - -``` - @ ; read file for options. must be the only argument. other options are ignored. - - --debug=0|1 - --dry-run ; verify parameters and exit with code 0 if successful - --version ; print version and exit - --comment ; any text (ignored) - --qnum= - --daemon ; daemonize - --pidfile= ; write pid to file - --user= ; drop root privs - --uid=uid[:gid1,gid2,...] ; drop root privs - --bind-fix4 ; apply outgoing interface selection fix for generated ipv4 packets - --bind-fix6 ; apply outgoing interface selection fix for generated ipv6 packets - --wsize=[:] ; set window size. 0 = do not modify. OBSOLETE ! - --wssize=[:] ; set window size for server. 0 = do not modify. default scale_factor = 0. - --wssize-cutoff=[n|d|s]N ; apply server wsize only to packet numbers (n, default), data packet numbers (d), relative sequence (s) less than N - --wssize-forced-cutoff=0|1 ; 1(default)=auto cutoff wssize on known protocol - --ctrack-timeouts=S:E:F[:U] ; internal conntrack timeouts for TCP SYN, ESTABLISHED, FIN stages, UDP timeout. default 60:300:60:60 - --ctrack-disable=[0|1] ; 1 or no argument disables conntrack - --ipcache-lifetime= ; time in seconds to keep cached hop count and domain name (default 7200). 0 = no expiration - --ipcache-hostname=[0|1] ; 1 or no argument enables ip->hostname caching - --hostcase ; change Host: => host: - --hostspell ; exact spelling of "Host" header. must be 4 chars. default is "host" - --hostnospace ; remove space after Host: and add it to User-Agent: to preserve packet size - --domcase ; mix domain case : Host: TeSt.cOm - --methodeol ; add '\n' before method and remove space after Host: - --synack-split=[syn|synack|acksyn] ; perform TCP split handshake : send SYN only, SYN+ACK or ACK+SYN - --orig-ttl= ; set TTL for original packets - --orig-ttl6= ; set ipv6 hop limit for original packets. by default ttl value is used - --orig-autottl=[[:[-]]|-] ; auto ttl mode for both ipv4 and ipv6. default: +5:3-64. "0:0-0" or "-" disables autottl. - --orig-autottl6=[[:[-]]|-] ; overrides --orig-autottl for ipv6 only - --orig-tcp-flags-set= ; set these tcp flags (flags |= value). value can be int, hex or comma separated list : FIN,SYN,RST,PSH,ACK,URG,ECE,CWR,AE,R1,R2,R3 - --orig-tcp-flags-unset= ; unset these tcp flags (flags &= ~value) - --orig-mod-start=[n|d|s]N ; apply orig TTL mod to packet numbers (n, default), data packet numbers (d), relative sequence (s) greater or equal than N - --orig-mod-cutoff=[n|d|s]N ; apply orig TTL mod to packet numbers (n, default), data packet numbers (d), relative sequence (s) less than N - --dup= ; duplicate original packets. send N dups before original. - --dup-replace=[0|1] ; 1 or no argument means do not send original, only dups - --dup-ttl= ; set TTL for dups - --dup-ttl6= ; set ipv6 hop limit for dups. by default ttl value is used - --dup-autottl=[[:[-]]|-] ; auto ttl mode for both ipv4 and ipv6. default: -1:3-64. "0:0-0" or "-" disables autottl. - --dup-autottl6=[[:[-]]|-] ; overrides --dup-autottl for ipv6 only - --dup-tcp-flags-set= ; set these tcp flags (flags |= value). value can be int, hex or comma separated list : FIN,SYN,RST,PSH,ACK,URG,ECE,CWR,AE,R1,R2,R3 - --dup-tcp-flags-unset= ; unset these tcp flags (flags &= ~value) - --dup-fooling=[,] ; can use multiple comma separated values. modes : none md5sig badseq badsum datanoack hopbyhop hopbyhop2 - --dup-ts-increment= ; ts fooling TSval signed increment for dup. default -600000 - --dup-badseq-increment= ; badseq fooling seq signed increment for dup. default -10000 - --dup-badack-increment= ; badseq fooling ackseq signed increment for dup. default -66000 - --dup-ip-id=same|zero|seq|rnd ; ipv4 ip_id mode for dupped packets - --dup-start=[n|d|s]N ; apply dup to packet numbers (n, default), data packet numbers (d), relative sequence (s) greater or equal than N - --dup-cutoff=[n|d|s]N ; apply dup to packet numbers (n, default), data packet numbers (d), relative sequence (s) less than N - --ip-id=zero|seq|seqgroup|rnd ; ipv4 ip_id assignment scheme - --dpi-desync=[,][,] ; try to desync dpi state. modes : synack fake fakeknown rst rstack hopbyhop destopt ipfrag1 multisplit multidisorder fakedsplit hostfakesplit fakeddisorder ipfrag2 udplen tamper - --dpi-desync-fwmark= ; override fwmark for desync packet. default = 0x40000000 (1073741824) - --dpi-desync-ttl= ; set ttl for desync packet - --dpi-desync-ttl6= ; set ipv6 hop limit for desync packet. by default ttl value is used. - --dpi-desync-autottl=[[:[-]]|-] ; auto ttl mode for both ipv4 and ipv6. default: -1:3-20. "0:0-0" or "-" disables autottl. - --dpi-desync-autottl6=[[:[-]]|-] ; overrides --dpi-desync-autottl for ipv6 only - --dpi-desync-tcp-flags-set= ; set these tcp flags (flags |= value). value can be int, hex or comma separated list : FIN,SYN,RST,PSH,ACK,URG,ECE,CWR,AE,R1,R2,R3 - --dpi-desync-tcp-flags-unset= ; unset these tcp flags (flags &= ~value) - --dpi-desync-fooling=[,] ; can use multiple comma separated values. modes : none md5sig ts badseq badsum datanoack hopbyhop hopbyhop2 - --dpi-desync-repeats= ; send every desync packet N times - --dpi-desync-skip-nosni=0|1 ; 1(default)=do not act on ClientHello without SNI (ESNI ?) - --dpi-desync-split-pos=N|-N|marker+N|marker-N ; comma separated list of split positions - ; markers: method,host,endhost,sld,endsld,midsld,sniext - ; full list is only used by multisplit and multidisorder - ; fakedsplit/fakeddisorder use first l7-protocol-compatible parameter if present, first abs value otherwise - --dpi-desync-split-seqovl=N|-N|marker+N|marker-N ; use sequence overlap before first sent original split segment - --dpi-desync-split-seqovl-pattern=[+ofs]@|0xHEX ; pattern for the fake part of overlap - --dpi-desync-fakedsplit-pattern=[+ofs]@|0xHEX ; fake pattern for fakedsplit/fakeddisorder - --dpi-desync-fakedsplit-mod=mod[,mod] ; mods can be none,altorder=0|1|2|3 + 0|8|16 - --dpi-desync-hostfakesplit-midhost=marker+N|marker-N ; additionally split real hostname at specified marker. must be within host..endhost or won't be splitted. - --dpi-desync-hostfakesplit-mod=mod[,mod] ; can be none, host=, altorder=0|1 - --dpi-desync-ipfrag-pos-tcp=<8..9216> ; ip frag position starting from the transport header. multiple of 8, default 8. - --dpi-desync-ipfrag-pos-udp=<8..9216> ; ip frag position starting from the transport header. multiple of 8, default 32. - --dpi-desync-ts-increment= ; ts fooling TSval signed increment. default -600000 - --dpi-desync-badseq-increment= ; badseq fooling seq signed increment. default -10000 - --dpi-desync-badack-increment= ; badseq fooling ackseq signed increment. default -66000 - --dpi-desync-any-protocol=0|1 ; 0(default)=desync only http and tls 1=desync any nonempty data packet - --dpi-desync-fake-tcp-mod=mod[,mod] ; comma separated list of tcp fake mods. available mods : none,seq - --dpi-desync-fake-http=[+ofs]@|0xHEX ; file containing fake http request - --dpi-desync-fake-tls=[+ofs]@|0xHEX|![+offset] ; file containing fake TLS ClientHello (for https). '!' = standard fake - --dpi-desync-fake-tls-mod=mod[,mod] ; comma separated list of TLS fake mods. available mods : none,rnd,rndsni,sni=,dupsid,padencap - --dpi-desync-fake-unknown=[+ofs]@|0xHEX ; file containing unknown protocol fake payload - --dpi-desync-fake-syndata=[+ofs]@|0xHEX ; file containing SYN data payload - --dpi-desync-fake-quic=[+ofs]@|0xHEX ; file containing fake QUIC Initial - --dpi-desync-fake-wireguard=[+ofs]@|0xHEX ; file containing fake wireguard handshake initiation - --dpi-desync-fake-dht=[+ofs]@|0xHEX ; file containing fake DHT (d1..e) - --dpi-desync-fake-discord=[+ofs]@|0xHEX ; file containing fake Discord voice connection initiation packet (IP Discovery) - --dpi-desync-fake-stun=[+ofs]@|0xHEX ; file containing fake STUN message - --dpi-desync-fake-unknown-udp=[+ofs]@|0xHEX ; file containing unknown udp protocol fake payload - --dpi-desync-udplen-increment= ; increase or decrease udp packet length by N bytes (default 2). negative values decrease length. - --dpi-desync-udplen-pattern=[+ofs]@|0xHEX ; udp tail fill pattern - --dpi-desync-start=[n|d|s]N ; apply dpi desync only to packet numbers (n, default), data packet numbers (d), relative sequence (s) greater or equal than N - --dpi-desync-cutoff=[n|d|s]N ; apply dpi desync only to packet numbers (n, default), data packet numbers (d), relative sequence (s) less than N - --hostlist= ; apply dpi desync only to the listed hosts (one host per line, subdomains auto apply if not prefixed with `^`, gzip supported, multiple hostlists allowed) - --hostlist-domains= ; comma separated fixed domain list - --hostlist-exclude= ; do not apply dpi desync to the listed hosts (one host per line, subdomains auto apply if not prefixed with `^`, gzip supported, multiple hostlists allowed) - --hostlist-exclude-domains= ; comma separated fixed domain list - --hostlist-auto= ; detect DPI blocks and build hostlist automatically - --hostlist-auto-fail-threshold= ; how many failed attempts cause hostname to be added to auto hostlist (default : 3) - --hostlist-auto-fail-time= ; all failed attemps must be within these seconds (default : 60) - --hostlist-auto-retrans-threshold= ; how many request retransmissions cause attempt to fail (default : 3) - --hostlist-auto-debug= ; debug auto hostlist positives - --new ; begin new strategy (new profile) - --skip ; do not use this profile - --filter-l3=ipv4|ipv6 ; L3 protocol filter. multiple comma separated values allowed. - --filter-tcp=[~]port1[-port2]|* ; TCP port filter. ~ means negation. setting tcp and not setting udp filter denies udp. comma separated list supported. - --filter-udp=[~]port1[-port2]|* ; UDP port filter. ~ means negation. setting udp and not setting tcp filter denies tcp. comma separated list supported. - --filter-l7= ; L6-L7 protocol filter. multiple comma separated values allowed. proto: http tls quic wireguard dht discord stun unknown - --filter-ssid=ssid1[,ssid2,ssid3,...] ; per profile wifi SSID filter - --ipset= ; ipset include filter (one ip/CIDR per line, ipv4 and ipv6 accepted, gzip supported, multiple ipsets allowed) - --ipset-ip= ; comma separated fixed subnet list - --ipset-exclude= ; ipset exclude filter (one ip/CIDR per line, ipv4 and ipv6 accepted, gzip supported, multiple ipsets allowed) - --ipset-exclude-ip= ; comma separated fixed subnet list -``` - -Many parameters dealing with binary data support loading from hex string prefixed by "0x" or from a file. -Filename can be "as is" or prefixed with "@". If there's "+number" prefix before "@" it means offset of data inside the file. -Offset must be less that data size. - -### DPI desync attack - -The idea is to take original message, modify it, add additional fake information in such a way that the server OS accepts original data only -but DPI cannot recostruct original message or sees what it cannot identify as a prohibited request. - -There's a set of instruments to achieve that goal. -It can be fake packets that reach DPI but do not reach server or get rejected by server, TCP segmentation or IP fragmentation. -There're attacks based on TCP sequence numbers. Methods can be combined in many ways. - -### Fakes - -Fakes are separate generated by nfqws packets carrying false information for DPI. They must either not reach the server or be rejected by it. Otherwise TCP connection or data stream would be broken. There're multiple ways to solve this task. - -* **md5sig** does not work on all servers. It typically works only on Linux servers. MD5 tcp option requires additional space in TCP header - and can cause MTU overflow during fakedsplit/fakeddisorder on low positions when multisegment query (TLS kyber) is transmitted. - `nfqws` cannot redistribute data between original TCP segments. The error displayed is 'message too long'. -* **badsum** doesn't work if your device is behind NAT which does not pass invalid packets. - The most common Linux NAT router configuration does not pass them. Most home routers are Linux based. - The default sysctl configuration `net.netfilter.nf_conntrack_checksum=1` causes conntrack to verify tcp and udp checksums - and set INVALID state for packets with invalid checksum. - Typically, iptables rules include a rule for dropping packets with INVALID state in the FORWARD chain. - The combination of these factors does not allow badsum packets to pass through the router. - In openwrt mentioned sysctl is set to 0 from the box, in other routers its often left in the default "1" state. - For nfqws to work properly through the router set `net.netfilter.nf_conntrack_checksum=0` on the router. - System never verifies checksums of locally generated packets so nfqws will always work on the router itself. - If you are behind another NAT, such as a ISP, and it does not pass invalid packages, there is nothing you can do about it. - But usually ISPs pass badsum. - Some adapters/switches/drivers enable hardware filtering of rx badsum not allowing it to pass to the OS. - This behavior was observed on a Mediatek MT7621 based device. - Tried to modify mediatek ethernet driver with no luck, likely hardware enforced limitation. - However the device allowed to send badsum packets, problem only existed for passthrough traffic from clients. -* **badseq** packets will be dropped by server, but DPI also can ignore them. - default badseq increment is set to -10000 because some DPIs drop packets outside of the small tcp window. - But this also can cause troubles when `--dpi-desync-any-protocol` is enabled. - To be 100% sure fake packet cannot fit to server tcp window consider setting badseq increment to 0x80000000 -* **TTL** looks like the best option, but it requires special tuning for each 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`. - Some router stock firmwares fix outgoing TTL. Without switching this option off TTL fooling will not work. -* **hopbyhop** is ipv6 only. This fooling adds empty extension header `hop-by-hop options` or two headers in case of `hopbyhop2`. - Packets with two hop-by-hop headers violate RFC and discarded by all operating systems. - All OS accept packets with one hop-by-hop header. - Some ISPs/operators drop ipv6 packets with hop-by-hop options. Fakes will not be processed by the server either because - ISP drops them or because there are two same headers. - DPIs may still anaylize packets with one or two hop-by-hop headers. -* **datanoack** sends tcp fakes without ACK flag. Servers do not accept this but DPI may accept. - This mode may break NAT and may not work with iptables if masquerade is used, even from the router itself. - Works with nftables properly. Likely requires external IP address (some ISPs pass these packets through their NAT). -* Manipulate **tcp flags** with `--dpi-desync-tcp-flags-set` and `--dpi-desync-tcp-flags-unset`. - Invalid tcp flags combination may cause server to drop the packet but DPI can accept it. - For example, set SYN in fakes. This may not work with all servers. - `datanoack` can be replaced to `--dpi-desync-tcp-flags-unset=ACK`. -* **ts** adds to TSval ts increment value (-600000 by default). Servers discard packets with TSval in some range. - Practical tests suggest increment between -100 and -0x80000000. - Timestamps are generated by client OS. In linux timestamps are enabled by default. In windows by default timestamps are disabled. - They can be enabled with this command : `netsh interface tcp set global timestamps=enabled` . - ts fooling requires that timestamps are enabled. They must be enabled on every client OS. - TSecr is left unmodified. -* **autottl** tries to automatically guess hop count to the server and compute TTL by adding some delta value that can be positive or negative. - Positive deltas must be preceeded by unary `+` sign. Deltas without any unary sign are treated negative for old versions compatibility reasons. - This tech relies on well known TTL default values used by OS : 64,128,255. - nfqws needs first incoming packet to see it's TTL. You must redirect it too. - If resulting value TTL is outside the range (min,max) then its normalized to min or max. - If delta is negative and TTL is longer than guessed hop count or delta is positive and TTL is shorter than guessed hop count - then autottl fails and falls back to the fixed value. - This can help if multiple DPIs exists on backbone channels, not just near the ISP. - Can fail if inbound and outbound paths are not symmetric. - - -`--dpi-desync-fooling` takes multiple comma separated values. - - -Multiple parameters `--dpi-desync-fake-???` are supported except for the `--dpi-desync-fake-syndata`. -Fakes are sent in the specified order. `--dpi-desync-repeats` resends each fake. -Resulting order would be : `fake1 fake1 fake1 fake2 fake2 fake2 fake3 fake3 fake3 .....` - - -### FAKE mods - -By default all tcp fakes are sent with the same sequence as original packet. -This can be changed by `--dpi-desync-fake-tcp-mod=seq`. In the latter case all fakes are sent as if they would be tcp segments of a single fake. - -**nfqws** has built-in TLS fake. It can be customized with `--dpi-desync-fake-tls` option. -Customized fake data can be anything - valid TLS Client Hello or arbitrary data. -It's possible to use TLS Client Hello with any fingerprint and any SNI. - -**nfqws** can do some modifications of valid TLS Client Hello fakes in runtime with `--dpi-desync-fake-tls-mod` option. - - * `none`. Do not do any mods. - * `rnd`. Randomize `random` and `session id` fields. Applied on every request. - * `rndsni`. Randomize SNI. If SNI >=7 symbols random SLD is applied with known TLD. Otherwise filled with random symbols. Applied only once at startup. - * `dupsid`. Copy `session ID` from original TLS Client Hello. Takes precedence over `rnd`. Applied on every request. - * `sni=`. Set specified SNI value. Changes TLS fake length, fixes lengths in TLS structure. Applied once at startup before `rndsni`. - * `padencap`. Padding extension is extended by original TLS Client Hello size (including multi packet variation with kyber). Padding extension is added to the end if not present, otherwise it must be the last extension. All lengths are increased. Fake size is not changed. Can be useful if DPI does not analyze sequence numbers properly. Applied on every request. - -By default if custom fake is not defined `rnd,rndsni,dupsid` mods are applied. If defined - `none`. -This behaviour is compatible with previous versions with addition of `dupsid`. - -If multiple TLS fakes are present each one takes the last mod. -If a mod is specified after fake it replaces previous mod. -This way it's possible to use different mods for every TLS fake. - -If a mod is set to non-TLS fake it causes error. Use `--dpi-desync-fake-tls-mod=none'. - -Example : `--dpi-desync-fake-tls=iana_org.bin --dpi-desync-fake-tls-mod=rndsni --dpi-desync-fake-tls=0xaabbccdd --dpi-desync-fake-tls-mod=none' - -### TCP segmentation - - * `multisplit`. split request at specified in `--dpi-desync-split-pos` positions - * `multidisorder`. same as `multisplit` but send in reverse order - * `fakedsplit`. sequental one position split with fake mix - * `hostfakesplit` (altorder=0). fake host part of the request : before host, random fake host, real host (optionally split this part), random fake host repeat, after host - * `hostfakesplit` (altorder=1). fake host part of the request : before host, random fake host, after host, real host (optionally split this part) - * `fakedsplit`. reverse one position split with fake mix - -`--dpi-desync-fakedsplit-mod=altorder=N` specifies number which influence to the presence of individual fakes in `fakedsplit`/`fakeddisorder`. - -`fakedsplit` TCP segments of multi-packet messages with split pos : - - * `altorder=0`. fake 1st segment, 1st segment, fake 1st segment, fake 2nd segment, 2nd segment, fake 2nd segment - * `altorder=1`. 1st segment, fake 1st segment, fake 2nd segment, 2nd segment, fake 2nd segment - * `altorder=2`. 1st segment, fake 2nd segment, 2nd segment, fake 2nd segment - * `altorder=3`. 1st segment, fake 2nd segment, 2nd segment - -`fakeddisorder` TCP segments of multi-packet messages with split pos : - - * `altorder=0`. fake 2nd segment, 2nd segment, fake 2nd segment, fake 1st segment, 1st segment, fake 1st segment - * `altorder=1`. 2nd segment, fake 2nd segment, fake 1st segment, 1st segment, fake 1st segment - * `altorder=2`. 2nd segment, fake 1st segment, 1st segment, fake 1st segment - * `altorder=3`. 1st segment, fake 1st segment, 1st segment - -`fakedsplit`/`fakeddisorder` TCP segments of multi-packet messages without split pos : - - * `altorder=0`. fake, original, fake - * `altorder=8`. original, fake - * `altorder=16`. original - -Resulting `altorder=N` is the sum of two `altorder` parts mentioned above. - -`--dpi-desync-fakedsplit-pattern` defines data payload of fakes in `fakedsplit`/`fakeddisorder`. By default pattern is simple `0x00`. -Offset of split part + offset of current packet in multi-packet message define offset in the pattern. - -Positions are defined by markers. - -* **Absolute positive marker** - numeric offset inside one packet or group of packets starting from the start -* **Absolute negative marker** - numeric offset inside one packet or group of packets starting from the next byte after the end -* **Relative marker** - positive or negative offset relative to a logical position within a packet or group of packets - -Relative positions : - -* **method** - HTTP method start ('GET', 'POST', 'HEAD', ...). Method is usually always at position 0 but can shift because of `--methodeol` fooling. If fooled position can become 1 or 2. -* **host** - hostname start in a known protocol (http, TLS) -* **endhost** - the byte next to the last hostname's byte -* **sld** - second level domain start in the hostname -* **endsld** - the byte next to the last SLD byte -* **midsld** - middle of SLD -* **sniext** - start of the data field in the SNI TLS extension. Any extension has 2-byte type and length fields followed by data field. - -Marker list example : `100,midsld,sniext+1,endhost-2,-10`. - -When splitting all markers are resolved to absolute offsets. If a relative position is absent in the current protocol its dropped. Then all resolved offsets are normalized to the current packet offset in multi packet group (multi-packet TLS with kyber, for example). Positions outside of the current packet are dropped. Remaining positions are sorted and deduplicated. - -In `multisplit`or `multidisorder` case split is cancelled if no position remained. - -`fakedsplit` и `fakeddisorder` use only one split position. It's searched from the `--dpi-desync-split-pos` list by a special alorightm. -First relative markers are searched. If no suitable found absolute markers are searched. If nothing found position 1 is used. - -For example, `--dpi-desync-split-pos=method+2,midsld,5` means `method+2` for http, `midsld` for TLS and 5 for others. - -`--dpi-desync-fakedsplit-mod=altorder=N` switches `fakedsplit` to alternate segment ordering. - -`hostfakesplit` only fakes hostname part of the request making it hard to destinguish between real and fake host names. -It works for tcp protocols with host : TLS and HTTP. Real hostname can be additionally split using `--dpi-desync-hostfakesplit-midhost` marker. -For example, `--dpi-desync-hostfakesplit-midhost=midsld`. Position must be within host range or split won't happen. -Multi-packet queries are supported if hostname part is not already split. If it is fooling is cancelled. - -By default fake host names are generated randomly on the fly using `[0-9a-z]` pattern. If host length is >= 7 dot is placed to simulate 3-char TLD and last 3 chars are replaces with a random known 3-char TLD. -It's possible to set fake host template : `--dpi-desync-hostfakesplit-mod=host=`. -Template hostname will be expanded to the left to original hostname size with random characters from `[0-9a-z]` pattern : "www.networksolutions.com" -> "h8xmdba4tv7a8.google.com". -If original hostname size is less than template size it will be cut : "habr.com" -> "ogle.com". -If original hostname size is larger than template size by one, dot will be appended to the left : "www.xxx.com" => ".google.com".. -That's why it's a good idea to use short hostnames in template : "ya.ru", "vk.com", "x.com". - -`--dpi-desync-hostfakesplit-mod=altorder=1` switches `hostfakesplit` to alternate segment ordering. `altorder=1` sends the whole request with faked host sequentally, then real host segment. - -### Sequence numbers overlap - -`seqovl` adds to one of the original segment `seqovl` bytes to the beginning and decreases sequence number. For `split` - to the first segment, for `disorder` - to the beginning of the penultimate segment sent (second in the original sequence). - -In `split` mode this creates partially in-window packet. OS receives only in-window part. -In `disorder` mode OS receives fake and real part of the second segment but does not pass received data to the socket until first segment is received. First segment overwrites fake part of the second segment. Then OS passes original data to the socket. -All unix OS except Solaris preserve last received data. This is not the case for Windows servers and `disorder` with `seqovl` will not work. -Disorder requires `seqovl` to be less than split position. Otherwise `seqovl` is not possible and will be cancelled. -Method allows to avoid separate fakes. Fakes and real data are mixed. - -### IP_ID assignment - -Some DPIs check ipv4 ip_id value. OS normally increment ip_id value every packet. Some anti-DPI software may send fakes or tcp segments with the same ip_id as original causing block trigger. - -Sequental ip_id will be broken in case of sending fakes or additional tcp segments because OS knows nothing about them. But still there are options how to assignt ip_id to generated packets. - -`ip-id` parameter sets ip_id assignment scheme for a desync profile : - - * `seq` (default) : increment ip_id for every next packet. in `multidisorder` case increase ip_id for the number of tcp segments then decrease by 1 every packet. - * `seqgroup` : same as `seq` but send fake replacements with the same ip_id as original parts. related only to fake tcp segments with the same size and same sequence as originals. - * `rnd` : assign random ip_id - * `zero` : always set zero. Linux and BSD will send zero, Windows will replace zero with it's own counter. - -ipv6 header lacks ip_id field, `ip-id` parameter ignored for ipv6. - -### ipv6 specific modes - -`hopbyhop`, `destopt` and `ipfrag1` desync modes (they're not the same as `hopbyhop` fooling !) are ipv6 only. One `hop-by-hop`, -`destination options` or `fragment` header is added to all desynced packets. -Extra header increases packet size and can't be applied to the maximum size packets. -If it's not possible to send modified packet original one will be sent. -The idea here is that DPI sees 0 in the next header field of the main ipv6 header and does not -walk through the extension header chain until transport header is found. -`hopbyhop`, `destopt`, `ipfrag1` modes can be used with any second phase mode except `ipfrag1+ipfrag2`. -For example, `hopbyhop,multisplit` means split original tcp packet into several pieces and add hop-by-hop header to each. -With `hopbyhop,ipfrag2` header sequence will be : `ipv6,hop-by-hop,fragment,tcp/udp`. -`ipfrag1` mode may not always work without special preparations. See "IP Fragmentation" notices. - -### Original modding - -Parameters `--orig-ttl` and `--orig-ttl6` allow to set TTL on original packets. -All further packet manipulations, e.g. segmentation, take modded original as data source and inherit modded TTL. - -`--orig-autottl` and `--orig-autottl6` work the same way as `dpi-desync-autottl`, but on original packets. -Delta should have unary `+` sign to produce TTL longer than guessed hop count. Otherwise nothing will reach the server. -Example : `--orig-autottl=+5:3-64`. - -`--orig-mod-start` and `--orig-mod-cutoff` specify start and end conditions for original modding. The work the same way as -`--dpi-desync-start` and `--dpi-desync-cutoff`. - -This function can be useful when DPI hunts for fakes and blocks suspicious connections. -DPI can compute TTL difference between packets and fire block trigger if it exceedes some threshold. - -### Duplicates - -Duplicates are copies of original packets which are sent before them. Duplicates are enabled by `--dup=N`, where N is dup count. -`--dup-replace` disables sending of original. - -Dups are sent only when original would also be sent without reconstruction. -For example, if TCP segmentation happens, original is actually dropped and is being replaced by artificially constructed new packets. -Dups are not sent in this case. - -All dup fooling modes are available : `--dup-ttl`. `--dup-ttl6`, `--dup-fooling`. -You decide whether these packets need to reach the server and in what form, according to the intended strategy. - -`--dup-autottl` and `--dup-autottl6` work the same way as `dpi-desync-autottl`. -Delta can be preceeded by unary `+` or `-` sign. -Example : `--dup-autottl=-2:3-64`. - -`--dup-start` and `--dup-cutoff` specify start and end conditions for dupping. The work the same way as -`--dpi-desync-start` and `--dpi-desync-cutoff`. - -This function can help if DPI compares some characteristics of fake and original packets and block connection if they differ some way. -Fooled duplicates can convince DPI that the whole session has an anomaly. -For example, all connection is protected by MD5 signature, not individual packets. - -### Server reply reaction - -There are DPIs that analyze responses from the server, particularly the certificate from the ServerHello that contain domain name(s). The ClientHello delivery confirmation is an ACK packet from the server with ACK sequence number corresponding to the length of the ClientHello+1. -In the disorder variant, a selective acknowledgement (SACK) usually arrives first, then a full ACK. -If, instead of ACK or SACK, there is an RST packet with minimal delay, DPI cuts you off at the request stage. -If the RST is after a full ACK after a delay of about ping to the server, then probably DPI acts on the server response. The DPI may be satisfied with good ClientHello and stop monitoring the TCP session without checking ServerHello. Then you were lucky. 'fake' option could work. -If it does not stop monitoring and persistently checks the ServerHello, `--wssize` parameter may help (see [CONNTRACK](#conntrack)). -Otherwise it is hardly possible to overcome this without the help of the server. -The best solution is to enable TLS 1.3 support on the server. TLS 1.3 sends the server certificate in encrypted form. -This is recommendation to all admins of blocked sites. Enable TLS 1.3. You will give more opportunities to overcome DPI. - -### SYNDATA mode - -Normally SYNs come without data payload. If it's present it's ignored by all major OS if TCP fast open (TFO) is not involved, but may not be ignored by DPI. -Original connections with TFO are not touched because otherwise they would be definitely broken. -Without extra parameter payload is 16 zero bytes. - -### DPI desync combos - -`--dpi-desync` takes up to 3 comma separated modes. - -* 0 phase modes work during the connection establishement : `synack`, `syndata` `--wsize`, `--wssize`. [hostlist](#multiple-strategies) filters are applicable only if [`--ipcache-hostname`](#ip-cache) is enabled. -* In the 1st phase fakes are sent before original data : `fake`, `rst`, `rstack`. -* In the 2nd phase original data is sent in a modified way (for example `fakedsplit` or `ipfrag2`). - -Modes must be specified in phase ascending order. - -### IP cache - -`ipcache` is the structure in the process memory that stores some information by IP address and interface name key. -This information can be used as missing data. Currently it's used in the following cases : - -1. IP,interface => hop count . This is used to apply autottl at 0 phase since the first session packet. If the record is absent autottl will not be applied immediately. Second time it will be applied immediately using cached hop count. - -2. IP => hostname . Hostname is cached to be used in 0 phase strategies. Mode is disabled by default and can be enabled by `ipcache-hostname` parameter. -This tech is experimental. There's no one-to-one correspondence between IP and domain name. Multiple domains can resolve to the same IP. -If collision happens hostname is replaced. On CDNs a domain can resolve to different IPs over time. `--ipcache-lifetime` limits how long cached record is valid. It's 2 hours by default. -Be prepared for unexpected results that can be explained only by reading debug logs. - -SIGUSR2 forces process to output it's ipcache to stdout. - -### CONNTRACK - -nfqws is equipped with minimalistic connection tracking system (conntrack) -It's used if some specific DPI circumvention methods are involved and helps to reassemble multi-packet requests. - -Conntrack can track connection phase : SYN,ESTABLISHED,FIN , packet counts in both directions , sequence numbers. - -It can be fed with unidirectional or bidirectional packets. - -A SYN or SYN,ACK packet creates an entry in the conntrack table. - -That's why iptables redirection must start with the first packet although can be cut later using connbytes filter. - -First seen UDP packet creates UDP stream. It defines the stream direction. Then all packets with the same -`src_ip,src_port,dst_ip,dst_port` are considered to belong to the same UDP stream. UDP stream exists till inactivity timeout. - -A connection is deleted from the table as soon as it's no more required to satisfy nfqws needs or when a timeout happens. - -There're 3 timeouts for each connection state. They can be changed in `--ctrack-timeouts` parameter. - -`--wssize` changes tcp window size for the server to force it to send split replies. -In order for this to affect all server operating systems, it is necessary to change the window size in each outgoing packet -before sending the message, the answer to which must be split (for example, TLS ClientHello). -That's why conntrack is required to know when to stop applying low window size. - -If you do not stop and set the low wssize all the time, the speed will drop catastrophically. -Linux can overcome this using connbytes filter but other OS may not include similar filter. - -In http(s) case wssize stops after the first http request or TLS ClientHello unless `--wssize-forced-cutoff=0` is specified. - -If you deal with a non-http(s) protocol you need `--wssize-cutoff`. It sets the threshold where wssize stops. - -Threshold can be prefixed with 'n' (packet number starting from 1), 'd' (data packet number starting from 1), -'s' (relative sequence number - sent by client bytes + 1). - -If a http request or TLS ClientHello packet is detected wssize stops immediately ignoring wssize-cutoff option. -This action is called "forced wssize cutoff" and can disabled using `--wssize-forced-cutoff=0`. - -If your protocol is prone to long inactivity, you should increase ESTABLISHED phase timeout using `--ctrack-timeouts`. - -Default timeout is low - only 5 mins. - -Don't forget that nfqws feeds with redirected packets. If you have limited redirection with connbytes -ESTABLISHED entries can remain in the table until dropped by timeout. - -To diagnose conntrack state send SIGUSR1 signal to nfqws : `killall -SIGUSR1 nfqws`. - -nfqws will dump current conntrack table to stdout. - -Typically, in a SYN packet, client sends TCP extension **scaling factor** in addition to window size. -scaling factor is the power of two by which the window size is multiplied : 0=>1, 1=>2, 2=>4, ..., 8=>256, ... - -The wssize parameter specifies the scaling factor after a colon. - -Scaling factor can only decrease, increase is blocked to prevent the server from exceeding client's window size. - -To force a TLS server to fragment ServerHello message to avoid hostname detection on DPI use `--wssize=1:6` - -The main rule is to set scale_factor as much as possible so that after recovery the final window size -becomes the possible maximum. If you set `scale_factor` 64:0, it will be very slow. - -On the other hand, the server response must not be large enough for the DPI to find what it is looking for. - -`--wssize` is not applied in desync profiles with hostlist filter because it works since the connection initiation when it's not yet possible -to extract the host name. But it works with auto hostlist profiles. - -`--wssize` may slow down sites and/or increase response time. It's desired to use another methods if possible. - -`--dpi-desync-cutoff` allows you to set the threshold at which it stops applying dpi-desync. -Can be prefixed with 'n', 'd', 's' symbol the same way as `--wssize-cutoff`. -Useful with `--dpi-desync-any-protocol=1`. -If the connection falls out of the conntrack and `--dpi-desync-cutoff` is set, `dpi desync` will not be applied. - -Set conntrack timeouts appropriately. - -### Reassemble - -nfqws supports reassemble of TLS and QUIC ClientHello. -They can consist of multiple packets if kyber crypto is used (default starting from chromium 124). -Chromium randomizes TLS fingerprint. SNI can be in any packet or in-between. -Stateful DPIs usually reassemble all packets in the request then apply block decision. -If nfqws receives a partial ClientHello it begins reassemble session. Packets are delayed until it's finished. -Then they go through desync using fully reassembled message. -On any error reassemble is cancelled and all delayed packets are sent immediately without desync. - -There is special support for all tcp split options for multi segment TLS. Split position is treated as message-oriented, not packet oriented. For example, if your client sends TLS ClientHello with size 2000 and SNI is at 1700, desync mode is `fake,multisplit`, then fake is sent first, then original first segment and the last splitted segment. 3 segments total. - -### UDP support - -UDP attacks are limited. Its not possible to fragment UDP on transport level, only on network (ip) level. -Only desync modes `fake`,`fakeknown`,`hopbyhop`,`destopt`,`ipfrag1`,`ipfrag2`,`udplen` and `tamper` are applicable. -`fake`,`fakeknown`,`hopbyhop`,`destopt`,`ipfrag1` are 1st phase modes, others - 2nd phase. -As always it's possible to combine one mode from 1st phase with one mode from 2nd phase but not possible to mix same phase modes. - -`udplen` increases udp payload size by `--dpi-desync-udplen-increment` bytes. Padding is filled with zeroes by default but can be overriden with a pattern. -This option can resist DPIs that track outgoing UDP packet sizes. -Requires that application protocol does not depend on udp payload size. - -QUIC initial packets are recognized. Decryption and hostname extraction is supported so `--hostlist` parameter will work. -Wireguard handshake initiation, DHT, STUN and [Discord Voice IP Discovery](https://discord.com/developers/docs/topics/voice-connections#ip-discovery) packets are also recognized. -For other protocols desync use `--dpi-desync-any-protocol`. - -Conntrack supports udp. `--dpi-desync-cutoff` will work. UDP conntrack timeout can be set in the 4th parameter of `--ctrack-timeouts`. - -Fake attack is useful only for stateful DPI and useless for stateless dealing with each packet independently. -By default fake payload is 64 zeroes. Can be overriden using `--dpi-desync-fake-unknown-udp`. - -### IP fragmentation - -Modern network can be very hostile to IP fragmentation. Fragmented packets are often not delivered or refragmented/reassembled on the way. -Frag position is set independently for tcp and udp. By default 24 and 8, must be multiple of 8. -Offset starts from the transport header. - -tcp fragments are almost always filtered. It's absolutely not suitable for arbitrary websites. -udp fragments have good chances to survive but not everywhere. It's good to assume success rate on QUIC between 50..75%. -Likely more with your VPS. Sometimes filtered by DDoS protection. - -There are important nuances when working with fragments in Linux. - -ipv4 : Linux allows to send ipv4 fragments but standard firewall rules in OUTPUT chain can cause raw send to fail. - -ipv6 : There's no way for an application to reliably send fragments without defragmentation by conntrack. -Sometimes it works, sometimes system defragments packets. -Looks like kernels <4.16 have no simple way to solve this problem. Unloading of `nf_conntrack` module -and its dependency `nf_defrag_ipv6` helps but this severely impacts functionality. -Kernels 4.16+ exclude from defragmentation untracked packets. -See `blockcheck.sh` code for example. - -Sometimes it's required to load `ip6table_raw` kernel module with parameter `raw_before_defrag=1`. -In openwrt module parameters are specified after module names separated by space in files located in `/etc/modules.d`. - -In traditional linux check whether `iptables-legacy` or `iptables-nft` is used. If legacy create the file -`/etc/modprobe.d/ip6table_raw.conf` with the following content : -``` -options ip6table_raw raw_before_defrag=1 -``` -In some linux distros its possible to change current ip6tables using this command: `update-alternatives --config ip6tables`. -If you want to stay with `nftables-nft` you need to patch and recompile your version. -In `nft.c` find : -``` - { - .name = "PREROUTING", - .type = "filter", - .prio = -300, /* NF_IP_PRI_RAW */ - .hook = NF_INET_PRE_ROUTING, - }, - { - .name = "OUTPUT", - .type = "filter", - .prio = -300, /* NF_IP_PRI_RAW */ - .hook = NF_INET_LOCAL_OUT, - }, -``` -and replace -300 to -450. - -It must be done manually, `blockcheck.sh` cannot auto fix this for you. - -Or just move to `nftables`. You can create hooks with any priority there. - -Looks like there's no way to do ipfrag using iptables for forwarded traffic if NAT is present. -`MASQUERADE` is terminating target, after it `NFQUEUE` does not work. -nfqws sees packets with internal network source address. If fragmented NAT does not process them. -This results in attempt to send packets to internet with internal IP address. -You need to use nftables instead with hook priority 101 or higher. - -### Multiple strategies - -**nfqws** can apply different strategies to different requests. It's done with multiple desync profiles. -Profiles are delimited by the `--new` parameter. First profile is created automatically and does not require `--new`. -Each profile has a filter. By default it's empty and profile matches any packet. -Filter can have hard parameters : ip version, ipset and tcp/udp port range. -Hard parameters are always identified unambiguously even on zero-phase when hostname and L7 are unknown yet. -Hostlists can also act as a filter. They can be combined with hard parameters. -When a packet comes profiles are matched from the first to the last until first filter condition match. -Hard filter is matched first. If it does not match verification goes to the next profile. -If a profile matches hard filter , L7 filter and has autohostlist it's selected immediately. -If a profile matches hard filter , L7 filter and has normal hostlist(s) and hostname is unknown yet verification goes to the next profile. -Otherwise profile hostlist(s) are checked for the hostname. If it matches profile is selected. -Otherwise verification goes to the next profile. - -It's possible that before knowing L7 and hostname connection is served by one profile and after -this information is revealed it's switched to another profile. -If you use 0-phase desync methods think carefully what can happen during strategy switch. -Use `--debug` logging to understand better what **nfqws** does. - -Profiles are numbered from 1 to N. There's last empty profile in the chain numbered 0. -It's used when no filter matched. - -IMPORTANT : multiple strategies exist only for the case when it's not possible to combine all to one strategy. -Copy-pasting blockcheck results of different websites to multiple strategies lead to the mess. -This way you may never unblock all resources and only confuse yourself. - -IMPORTANT : user-mode ipset implementation was not designed as a kernel version replacement. Kernel version is much more effective. -It's for the systems that lack ipset support : Windows and Linux without nftables and ipset kernel modules (Android, for example). - -### WIFI filtering - -Wifi interface name is not related to connected SSID. -It's possible to connect interface to different SSIDs. -They may require different strategies. How to solve this problem ? - -You can run and stop nfqws instances manually. But you can also automate this. -Windows version `winws` has global filter `--ssid-filter`. -It connects or disconnects `winws` depending on connected SSIDs. -Routing is not take into account. This approach is possible because windivert can have multiple handlers with intersecting filter. -If SSID changes one `winws` connects and others disconnect. - -`winws` solution is hard to implement in Linux because one nfqueue can have only one handler and it's impossible to pass same traffic to multiple queues. -One must connect when others have already disconnected. -Instead, `nfqws` has per-profile `--filter-ssid` parameter. Like `--ssid-filter` it takes comma separated SSID list. -`nfqws` maintains ifname->SSID list which is updated not faster than once a second. -When a packet comes incoming or outgoing interface name is matched to the SSID and then used in profile selection algorithm. - -SSID info is taken the same way as `iw dev info` does (nl80211). -Unfortunately it's broken since kernel 5.19 and still unfixed in 6.14. -In the latter case `iwgetid` way is used (wireless extensions). -Wireless extensions are deprecated. Some kernels can be built without wext support. -Before using `--filter-ssid` check that any of the mentioned commands can return SSID. - -### Virtual machines - -Most of nfqws packet magic does not work from VMs powered by virtualbox and vmware when network is NATed. -Hypervisor forcibly changes TTL and does not forward fake packets. -Set up bridge networking. - -### IPTABLES for nfqws - -> [!CAUTION] -> Starting from Linux kernel 6.17 there's CONFIG_NETFILTER_XTABLES_LEGACY parameter which is not set by default. Many distributions will likely not turn it on making iptables-legacy non working. This is part of iptables deprecation. However iptables-nft still works because their backend is based on nftables. - -This is the common way to redirect some traffic to nfqws : - -``` -iptables -t mangle -I POSTROUTING -o -p tcp -m multiport --dports 80,443 -m connbytes --connbytes-dir=original --connbytes-mode=packets --connbytes 1:6 -m mark ! --mark 0x40000000/0x40000000 -j NFQUEUE --queue-num 200 --queue-bypass -``` - -This variant works if DPI is stateful and does not track all packets separately in search for "bad requests". If it's stateless you have to redirect all outgoing plain http packets. - -``` -iptables -t mangle -I POSTROUTING -o -p tcp --dport 443 -m connbytes --connbytes-dir=original --connbytes-mode=packets --connbytes 1:6 -m mark ! --mark 0x40000000/0x40000000 -j NFQUEUE --queue-num 200 --queue-bypass -iptables -t mangle -I POSTROUTING -o -p tcp --dport 80 -m mark ! --mark 0x40000000/0x40000000 -j NFQUEUE --queue-num 200 --queue-bypass -``` - -mark bit is used to prevent loops. **nfqws** sets this mark in each injected packet. -It's also necessary for correct injected packet ordering and for deadlock prevention. - -`autottl` requires incoming `SYN,ACK` packet or first reply packet (it's usually the same). - -`autohostlist` needs incoming `RST` and `http redirect`. - -It's possible to build tcp flags and u32 based filter but connbytes is easier. - -` -iptables -t mangle -I PREROUTING -i -p tcp -m multiport --sports 80,443 -m connbytes --connbytes-dir=reply --connbytes-mode=packets --connbytes 1:3 -m mark ! --mark 0x40000000/0x40000000 -j NFQUEUE --queue-num 200 --queue-bypass -` - -For QUIC : - -``` -iptables -t mangle -I POSTROUTING -o -p udp --dport 443 -m connbytes --connbytes-dir=original --connbytes-mode=packets --connbytes 1:6 -m mark ! --mark 0x40000000/0x40000000 -j NFQUEUE --queue-num 200 --queue-bypass -``` - -6 packets cover possible retransmissions of quic initials and feed `autohostlist` mode. - -### NFTABLES for nfqws - -This is the start configuration : - -``` -IFACE_WAN=wan - -nft create table inet ztest - -nft add chain inet ztest post "{type filter hook postrouting priority mangle;}" -nft add rule inet ztest post oifname $IFACE_WAN meta mark and 0x40000000 == 0 tcp dport "{80,443}" ct original packets 1-6 queue num 200 bypass -nft add rule inet ztest post oifname $IFACE_WAN meta mark and 0x40000000 == 0 udp dport 443 ct original packets 1-6 queue num 200 bypass - -# auto hostlist with avoiding wrong ACK numbers in RST,ACK packets sent by russian DPI -sysctl net.netfilter.nf_conntrack_tcp_be_liberal=1 -nft add chain inet ztest pre "{type filter hook prerouting priority filter;}" -nft add rule inet ztest pre iifname $IFACE_WAN tcp sport "{80,443}" ct reply packets 1-3 queue num 200 bypass -``` - -To engage `datanoack` or `ipfrag` for passthrough traffic special POSTNAT configuration is required. Generated packets must be marked as **notrack** in the early stage to avoid being invalidated by linux conntrack. - -``` -IFACE_WAN=wan - -nft create table inet ztest - -nft add chain inet ztest postnat "{type filter hook postrouting priority srcnat+1;}" -nft add rule inet ztest postnat oifname $IFACE_WAN meta mark and 0x40000000 == 0 tcp dport "{80,443}" ct original packets 1-6 queue num 200 bypass -nft add rule inet ztest postnat oifname $IFACE_WAN meta mark and 0x40000000 == 0 udp dport 443 ct original packets 1-6 queue num 200 bypass - -nft add chain inet ztest predefrag "{type filter hook output priority -401;}" -nft add rule inet ztest predefrag "mark & 0x40000000 != 0x00000000 notrack" -``` - -Delete nftable : - -``` -nft delete table inet ztest -``` - -### Flow offloading - -If your device supports flow offloading (hardware acceleration) iptables and nftables may not work. With offloading enabled packets bypass standard netfilter flow. It must be either disabled or selectively controlled. - -Newer linux kernels have software flow offloading (SFO). The story is the same with SFO. - -In `iptables` flow offloading is controlled by openwrt proprietary extension `FLOWOFFLOAD`. Newer `nftables` implement built-in offloading support. - -Flow offloading does not interfere with **tpws** and `OUTPUT` traffic. It only breaks nfqws that fools `FORWARD` traffic. - -### Server side fooling - -It's also possible. -nfqws is intended for client side attacks. That's why it recognizes direct and reply traffic based on role in connection establishement. -If it sees SYN then source IP is client IP. If it sees SYN,ACK then source ip is server IP. -For UDP client address is considered as source IP of the first seen packet of src_ip,src_port,dst_ip,dst_port tuple. - -This does not work correctly on the server side. Client traffic is reply traffic, server traffic is direct traffic. - -`--wsize` works in any case. It can be used on both client and server. -Other techs work only if nfqws treats traffic as direct traffic. -To apply them to server originated traffic disable conntrack by `--ctrack-disable` parameter. -If a packet is not found in conntrack it's treated as direct and techs like `multidisorder` will be applied. - -Most of the protocols will not be recognized because protocol recognition system only reacts to client packets. -To make things working use `--dpi-desync-any-protocol` with connbytes or packet payload limiter. -start/cutoff are unavailable because they are conntrack based. - -`--synack-split` removes standard SYN,ACK packet and replaces it with one SYN packet, SYN then ACK separate packets or ACK then SYN separate packets. -Client sends SYN,ACK in reply which usually only server does. -This makes some DPI's to treat connection establishement roles wrong. They stop to block. -See [split handshake](https://nmap.org/misc/split-handshake.pdf). - -On server side traffic should be redirected to nfqws using source port numbers and original connbytes direction. - - -## tpws - -tpws is transparent proxy. - -``` - @ ; read file for options. must be the only argument. other options are ignored. - - --debug=0|1|2|syslog|@ ; 1 and 2 means log to console and set debug level. for other targets use --debug-level. - --debug-level=0|1|2 ; specify debug level for syslog and @ - --dry-run ; verify parameters and exit with code 0 if successful - --version ; print version and exit - --bind-addr=| ; for v6 link locals append %interface_name : fe80::1%br-lan - --bind-iface4= ; bind to the first ipv4 addr of interface - --bind-iface6= ; bind to the first ipv6 addr of interface - --bind-linklocal=no|unwanted|prefer|force - ; no : bind only to global ipv6 - ; unwanted (default) : prefer global address, then LL - ; prefer : prefer LL, then global - ; force : LL only - --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 - --bind-wait-only ; wait for bind conditions satisfaction then exit. return code 0 if success. - --connect-bind-addr=| ; address for outbound connections. for v6 link locals append %%interface_name - --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 - --nosplice ; do not use splice to transfer data between sockets - --skip-nodelay ; do not set TCP_NODELAY for outgoing connections. incompatible with split. - --local-tcp-user-timeout= ; set tcp user timeout for local leg (default : 10, 0 = system default) - --remote-tcp-user-timeout= ; set tcp user timeout for remote leg (default : 20, 0 = system default) - --fix-seg= ; recover failed TCP segmentation at the cost of slowdown. wait up to N msec. - --ipcache-lifetime= ; time in seconds to keep cached domain name (default 7200). 0 = no expiration - --ipcache-hostname=[0|1] ; 1 or no argument enables ip->hostname caching - --no-resolve ; disable socks5 remote dns - --resolver-threads= ; number of resolver worker threads - --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 - - --new ; begin new strategy (new profile) - --skip ; do not use this profile - --filter-l3=ipv4|ipv6 ; L3 protocol filter. multiple comma separated values allowed. - --filter-tcp=[~]port1[-port2]|* ; TCP port filter. ~ means negation. comma separated list supported. - --filter-l7=[http|tls|unknown] ; L6-L7 protocol filter. multiple comma separated values allowed. - --ipset= ; ipset include filter (one ip/CIDR per line, ipv4 and ipv6 accepted, gzip supported, multiple ipsets allowed) - --ipset-ip= ; comma separated fixed subnet list - --ipset-exclude= ; ipset exclude filter (one ip/CIDR per line, ipv4 and ipv6 accepted, gzip supported, multiple ipsets allowed) - --ipset-exclude-ip= ; comma separated fixed subnet list - - --hostlist= ; only act on hosts in the list (one host per line, subdomains auto apply if not prefixed with '^', gzip supported, multiple hostlists allowed) - --hostlist-domains= ; comma separated fixed domain list - --hostlist-exclude= ; do not act on hosts in the list (one host per line, subdomains auto apply if not prefixed with '^', gzip supported, multiple hostlists allowed) - --hostlist-exclude-domains= ; comma separated fixed domain list - --hostlist-auto= ; detect DPI blocks and build hostlist automatically - --hostlist-auto-fail-threshold= ; how many failed attempts cause hostname to be added to auto hostlist (default : 3) - --hostlist-auto-fail-time= ; all failed attemps must be within these seconds (default : 60) - --hostlist-auto-debug= ; debug auto hostlist positives - - --split-pos=N|-N|marker+N|marker-N ; comma separated list of split positions - ; markers: method,host,endhost,sld,endsld,midsld,sniext - --split-any-protocol ; split not only http and TLS - --disorder[=http|tls] ; when splitting simulate sending second fragment first - --oob[=http|tls] ; when splitting send out of band byte. default is HEX 0x00. - --oob-data=|0xHEX ; override default 0x00 OOB byte. - --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: - --domcase ; mix domain case after Host: like this : TeSt.cOm - --methodspace ; add extra space after method - --methodeol ; add end-of-line before method - --unixeol ; replace 0D0A to 0A - --tlsrec=N|-N|marker+N|marker-N ; make 2 TLS records. split at specified logical part. don't split if SNI is not present. - --tlsrec-pos= ; make 2 TLS records. split at specified pos - --mss= ; set client MSS. forces server to split messages but significantly decreases speed ! - --tamper-start=[n] ; start tampering only from specified outbound stream position. byte pos or block number ('n'). default is 0. - --tamper-cutoff=[n] ; do not tamper anymore after specified outbound stream position. byte pos or block number ('n'). default is unlimited. - --daemon ; daemonize - --pidfile= ; write pid to file - --user= ; drop root privs - --uid=uid[:gid1,gid2,...] ; drop root privs -``` - -### TCP segmentation in tpws - -**tpws** like **nfqws** supports multiple splits. Split [markers](#tcp-segmentation) are specified in `--split-pos` parameter. - -On the socket level there's no guaranteed way to force OS to send pieces of data in separate packets. OS has a send buffer for each socket. If `TCP_NODELAY` socket option is enabled and send buffer is empty OS will likely send data immediately. If send buffer is not empty OS will coalesce it with new data and send in one packet if possible. - -In practice outside of massive transmissions it's usually enough to enable `TCP_NODELAY` and use separate `send()` calls to force custom TCP segmentation. But if there're too many split segments Linux can combined some pieces and break desired behaviour. BSD and Windows are more predictable in this case. That's why it's not recommended to use too many splits. Tests revealed that 8+ can become problematic. - -Since linux kernel 4.6 **tpws** can recognize TCP segmentation failures and warn about them. `--fix-seg` can fix segmentation failures at the cost of some slowdown. It waits for several msec until all previous data is sent. This breaks async processing model and slows down every other connection going through **tpws**. Thus it's not recommended on highly loaded systems. But can be compromise for home systems. - -If you're attempting to split massive transmission with `--split-any-protocol` option it will definitely cause massive segmentation failures. Do not do that without `--tamper-start` and `--tamper-cutoff` limiters. - -**tpws** works on socket level and receives in one shot long requests (TLS with kyber) that should normally require several TCP packets. It tampers entire received block without knowing how much packets it will take. OS will do additional segmenation to meet MTU. - -`--disorder` sends every odd packet with TTL=1. Server receives even packets fastly. Then client OS retransmits odd packets with normal TTL and server receives them. In case of 6 segments server and DPI will see them in this order : `2 4 6 1 3 5`. This way of disorder causes some delays. Default retransmission timeout in Linux is 200 ms. - -`--oob` sends one out-of-band byte in the end of the first split segment. - -`--oob` and `--disorder` can be combined only in Linux. Others OS do not handle this correctly. - -### TLSREC - -`--tlsrec` allow to split TLS ClientHello into 2 TLS records in one TCP segment. It accepts single pos marker. - -`--tlsrec` breaks significant number of sites. Crypto libraries on servers usually accept fine modified ClientHello but middleboxes such as CDNs and ddos guards - not always. Use of `--tlsrec` without filters is discouraged. - -### MSS - -`--mss` sets TCP_MAXSEG socket option. Client sets this value in MSS TCP option in the SYN packet. -Server replies with it's own MSS in SYN,ACK packet. Usually servers lower their packet sizes but they still don't fit to supplied MSS. The greater MSS client sets the bigger server's packets will be. -If it's enough to split TLS 1.2 ServerHello, it may fool DPI that checks certificate domain name. -This scheme may significantly lower speed. Hostlist filter is possible only in socks mode if client uses remote resolving (firefox `network.proxy.socks_remote_dns`) or if `ipcache-hostname` is enabled. -`--mss` is not required for TLS1.3. If TLS1.3 is negotiable then MSS make things only worse. Use only if nothing better is available. Works only in Linux, not BSD or MacOS. - -### Other tamper options - -`--hostpad=` adds padding headers before `Host:` with specified number of bytes. If `` is too large headers are split by 2K. Padding more that 64K is not supported and not accepted by http servers. - -It's useful against stateful DPI's that reassemble only limited amount of data. Increase padding `` until website works. If minimum working `` is close to MTU then it's likely DPI is not reassembling packets. Then it's better to use regular split instead of `--hostpad`. - -### Supplementary options - -**tpws** can bind to multiple interfaces and IP addresses (up to 32). - -Port number is always the same. - -Parameters `--bind-iface*` and `--bind-addr` create new bind. - -Other parameters `--bind-*` are related to the last bind. - -link local ipv6 (`fe80::/8`) mode selection : - -``` ---bind-iface6 --bind-linklocal=no : first selects private address fc00::/7, then global address ---bind-iface6 --bind-linklocal=unwanted : first selects private address fc00::/7, then global address, then LL ---bind-iface6 --bind-linklocal=prefer : first selects LL, then private address fc00::/7, then global address ---bind-iface6 --bind-linklocal=force : select only LL -``` - -To bind to all ipv4 specify `--bind-addr "0.0.0.0"`, all ipv6 - `::`. - -`--bind-addr=""` - mean bind to all ipv4 and ipv6. - -If no binds are specified default bind to all ipv4 and ipv6 addresses is created. - -To bind to a specific link local address do : `--bind-iface6=fe80::aaaa:bbbb:cccc:dddd%iface-name` - -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." - -To bind to a specific ip when its interface may not be configured yet do : `--bind-addr=192.168.5.3 --bind-wait-ip=20` - -It's possible to bind to any nonexistent address in transparent mode but in socks mode address must exist. - -In socks proxy mode no additional system privileges are required. Connections 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 activities. Domain names are resolved in multi threaded pool. -Resolving does not freeze other connections. But if there're too many requests resolving delays may increase. -Number of resolver threads is choosen automatically proportinally to `--maxconn` and can be overriden using `--resolver-threads`. -To disable hostname resolve use `--no-resolve` option. - -### Multiple strategies - -**tpws** like **nfqws** supports multiple strategies. They work mostly like with **nfqws** with minimal differences. -`filter-udp` is absent because **tpws** does not support udp. 0-phase desync methods (`--mss`) can work with hostlist in socks modes with remote hostname resolve. -This is the point where you have to plan profiles carefully. If you use `--mss` and hostlist filters, behaviour can be different depending on remote resolve feature enabled or not. -Use `--mss` both in hostlist profile and profile without hostlist. -Use `curl --socks5` and `curl --socks5-hostname` to issue two kinds of proxy queries. -See `--debug` output to test your setup. - -### IPTABLES for tpws - -Use the following rules to redirect TCP connections to 'tpws' : -``` -iptables -t nat -I OUTPUT -o -p tcp --dport 80 -m owner ! --uid-owner tpws -j DNAT --to 127.0.0.127:988 -iptables -t nat -I PREROUTING -i -p tcp --dport 80 -j DNAT --to 127.0.0.127:988 -``` - -First rule redirects outgoing from the same system traffic, second redirects passthrough traffic. - -DNAT to localhost works only in the **OUTPUT** chain and does not work in the **PREROUTING** chain without setting this sysctl : - -`sysctl -w net.ipv4.conf..route_localnet=1` - -It's also possible to use `-j REDIRECT --to-port 988` instead of DNAT but in the latter case transparent proxy must listen on all IP addresses or on a LAN interface address. It's not too good to listen on all IP and it's not trivial to get specific IP in a shell script. `route_localnet` has it's own security impact if not protected by additional rules. You open `127.0.0.0/8` subnet to the net. - -This is how to open only single `127.0.0.127` address : -``` -iptables -A INPUT ! -i lo -d 127.0.0.127 -j ACCEPT -iptables -A INPUT ! -i lo -d 127.0.0.0/8 -j DROP -``` - -Owner filter is required to avoid redirection loops. **tpws** must be run with `--user tpws` parameter. - -ip6tables work almost the same with minor differences. ipv6 addresses should be enclosed in square brackets : -``` -ip6tables -t nat -I OUTPUT -o -p tcp --dport 80 -m owner ! --uid-owner tpws -j DNAT --to [::1]:988 -``` - -There's no `route_localnet` for ipv6. DNAT to localhost (`::1`) is possible only in **OUTPUT** chain. In **PREROUTING** chain DNAT is possible to any global address or link local address of the interface where packet came from. - -### NFTABLES for tpws - -Base nftables scheme : -``` -IFACE_WAN=wan -IFACE_LAN=br-lan - -sysctl -w net.ipv4.conf.$IFACE_LAN.route_localnet=1 - -nft create table inet ztest - -nft create chain inet ztest localnet_protect -nft add rule inet ztest localnet_protect ip daddr 127.0.0.127 return -nft add rule inet ztest localnet_protect ip daddr 127.0.0.0/8 drop -nft create chain inet ztest input "{type filter hook input priority filter - 1;}" -nft add rule inet ztest input iif != "lo" jump localnet_protect - -nft create chain inet ztest dnat_output "{type nat hook output priority dstnat;}" -nft add rule inet ztest dnat_output meta skuid != tpws oifname $IFACE_WAN tcp dport { 80, 443 } dnat ip to 127.0.0.127:988 -nft create chain inet ztest dnat_pre "{type nat hook prerouting priority dstnat;}" -nft add rule inet ztest dnat_pre meta iifname $IFACE_LAN tcp dport { 80, 443 } dnat ip to 127.0.0.127:988 -``` - -Delete nftable : -``` -nft delete table inet ztest -``` - - -## Ways to get a list of blocked IP - -nftables can't work with ipsets. Native nf sets require lots of RAM to load large ip lists with subnets and intervals. -In case you're on a low RAM system and need large lists it may be required to fall back to iptables+ipset. - -1. Enter the blocked domains to `ipset/zapret-hosts-user.txt` and run `ipset/get_user.sh` -At the output, you get `ipset/zapret-ip-user.txt` with IP addresses. - -2. `ipset/get_reestr_*.sh`. Russian specific - -3. `ipset/get_antifilter_*.sh`. Russian specific - -4. `ipset/get_config.sh`. This script calls what is written into the GETLIST variable from the config file. - -If the variable is not defined, then only lists for ipsets nozapret/nozapret6 are resolved. - -So, if you're not russian, the only way for you is to manually add blocked domains. -Or write your own `ipset/get_iran_blocklist.sh` , if you know where to download this one. - -On routers, it is not recommended to call these scripts more than once in 2 days to minimize flash memory writes. - -`ipset/create_ipset.sh` executes forced ipset update. -With `no-update` parameter `create_ipset.sh` creates ipset but populate it only if it was actually created. - -It's useful when multiple subsequent calls are possible to avoid wasting of cpu time redoing the same job. - -Ipset loading is resource consuming. Its a good idea to call create_ipset without `no-update` parameter - -only once a several days. Use it with `no-update` option in other cases. - -ipset scripts automatically call ip2net utility. -ip2net helps to reduce ip list size by combining IPs to subnets. Also it cuts invalid IPs from the list. -Stored lists are already processed by ip2net. They are error free and ready for loading. - -`create_ipset.sh` supports loading ip lists from gzip files. First it looks for the filename with the ".gz" extension, -such as `zapret-ip.txt.gz`, if not found it falls back to the original name `zapret-ip.txt`. - -So your own get_iran_blockslist.sh can use "zz" function to produce gz. Study how other russian `get_XXX.sh` work. - -Gzipping helps saving a lot of precious flash space on embedded systems. - -User lists are not gzipped because they are not expected to be very large. - -You can add a list of domains to `ipset/zapret-hosts-user-ipban.txt`. Their ip addresses will be placed -in a separate ipset "ipban". It can be used to route connections to transparent proxy "redsocks" or VPN. - -IPV6: if ipv6 is enabled, then additional txt's are created with the same name, but with a "6" at the end before the extension. - -`zapret-ip.txt` => `zapret-ip6.txt` - -The ipsets zapret6 and ipban6 are created. - -IP EXCLUSION SYSTEM. All scripts resolve `zapret-hosts-user-exclude.txt` file, creating `zapret-ip-exclude.txt` and `zapret-ip-exclude6.txt`. - -They are the source for ipsets nozapret/nozapret6. All rules created by init scripts are created with these ipsets in mind. -The IPs placed in them are not involved in the process. -zapret-hosts-user-exclude.txt can contain domains, ipv4 and ipv6 addresses or subnets. - -FreeBSD. `ipset/*.sh` scripts also work in FreeBSD. Instead of ipset they create ipfw lookup tables with the same names as in Linux. -ipfw tables can store both ipv4 and ipv6 addresses and subnets. There's no 4 and 6 separation. - -LISTS_RELOAD config parameter defines a custom lists reloading command. -Its useful on BSD systems with PF. -LISTS_RELOAD=- disables reloading ip list backend. - -## Domain name filtering - -An alternative to ipset is to use **tpws** or **nfqws** with a list(s) of domains. -Both **tpws** and **nfqws** take any number of include (`--hostlist`) and exclude (`--hostlist-exclude`) domain lists. -All lists of the same type are combined internally leaving only 2 lists : include and exclude. - -Exclude list is checked first. Fooling is cancelled if domain belongs to exclude list. -If include list is present and domain does not belong to that list fooling is also cancelled. -Empty list means absent list. Otherwise fooling goes on. - -Launch system looks for 2 include lists : - -`ipset/zapret-hosts-users.txt.gz` or `ipset/zapret-hosts-users.txt` - -`ipset/zapret-hosts.txt.gz` or `ipset/zapret-hosts.txt` - -and 1 exclude list - -`ipset/zapret-hosts-users-exclude.txt.gz` or `ipset/zapret-hosts-users-exclude.txt` - -If `MODE_FILTER=hostlist` all present lists are passed to **nfqws** or **tpws**. -If all include lists are empty it works like no include lists exist at all. -If you need "all except" mode you dont have to delete zapret-hosts-users.txt. Just make it empty. - -Subdomains auto apply. For example, "ru" in the list affects "\*.ru" . -`^` prefix symbol disables subdomain match. - -**tpws** and **nfqws** automatically reload lists if their modification time or file size is changed. -HUP signal forcibly reloads all lists. - -When filtering by domain name, daemons should run without filtering by ipset. -When using large regulator lists estimate the amount of RAM on the router ! - -## **autohostlist** mode - -This mode analyzes both client requests and server replies. -If a host is not in any list and a situation similar to block occurs host is automatically added to the special list both in memory and file. -Use exclude hostlist to prevent autohostlist triggering. -If it did happen - delete the undesired record from the file. - -In case of nfqws it's required to redirect both incoming and outgoing traffic to the queue. -It's strongly recommended to use connbytes filter or nfqws will process gigabytes of incoming traffic. -For the same reason it's not recommended to use autohostlist mode in BSDs. BSDs do not support connbytes or similar mechanism. - -**nfqws** и **tpws** detect the folowing situations : -1) [nfqws] Multiple retransmissions of the first request inside a TCP session having host. -2) [nfqws,tpws] RST in response to the first request. -3) [nfqws,tpws] HTTP redirect in response to the first http request with 2nd level domain diferent from the original. -4) [tpws] Client closes connection after first request without having server reply (no reponse from server, timeout). - -To minimize false positives there's fail counter. If in specific time occurs more than specified number of fails -the host is added to the list. Then DPI bypass strategy start to apply immediately. - -For the user autohostlist mode looks like this. -When for the first time user visits a blocked website it sees block page, connection reset -or browser hangs until timeout, then display a error. -User presses multiple times F5 causing browser to retry attempts. -After some retries a website opens and next time works as expected. - -With autohostlist mode it's possible to use bypass strategies that break lots of sites. -If a site does not behave like blocked no fooling applies. -Otherwise it's nothing to lose. - -However false positives still can occur in case target website is behaving abnormally -(may be due to DDoS attack or server malfunction). If it happens bypass strategy -may start to break the website. This situation can only be controlled manually. -Remove undesired domain from the autohostlist file. -Use exclude hostlist to prevent further auto additions. - -It's possible to use one auto hostlist with multiple processes. All processes check for file modification time. -If a process modified autohostlist, all others will reread it automatically. -All processes must run with the same uid. - -If zapret scripts are used then autohostlist is `ipset/zapret-hosts-auto.txt` -and exlude list is `ipset/zapret-hosts-user-exclude.txt`. autohostlist mode -includes hostlist mode. You can use `ipset/zapret-hosts-user.txt`. - - -## 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. - -Which firewall type use on linux systems : `nftables` or `iptables`. -On traditional systems `nftables` is selected by default if `nft` is installed. -On openwrt by default `nftables` is selected on `firewall4` based systems. - -`FWTYPE=iptables` - -With `nftables` post-NAT scheme is used by default. It allows more DPI attacks on forwarded traffic. -It's possible to use `iptables`-like pre-NAT scheme. **nfqws** will see client source IPs and display them in logs. - -`#POSTNAT=0` - -There'are 3 standard options configured separately and independently : `tpws-socks`, **tpws**, **nfqws**. -They can be used alone or combined. Custom scripts in `init.d/{sysv,openwrt,macos}/custom.d` are always applied. - -`tpws-socks` requires daemon parameter configuration but does not require traffic interception. -Other standard options require also traffic interception. -Each standard option launches single daemon instance. Strategy differiences are managed using multi-profile scheme. -Main rule for interception is "intercept required minumum". Everything else only wastes CPU resources and slows down connection. - -`--ipset` option is prohibited intentionally to disallow easy to use but ineffective user-mode filtering. -Use kernel ipsets instead. It may require custom scripts. - -To use standard updatable hostlists from the `ipset` dir use `` placeholder. It's automatically replaced -with hostlist parameters if `MODE_FILTER` variable enables hostlists and is removed otherwise. -Standard hostlists are expected in final (fallback) strategies closing groups of filter parameters. -Don't use `` in highly specialized profiles. Use your own filter or hostlist(s). -`` marker uses standard autohostlist as usual hostlist thus disabling auto additions in this profile. -If any other profile adds something this profile accepts the change automatically. - -Change loop prevention mark bit - -`DESYNC_MARK=0x40000000` - -Change postnat scheme mark bit - -`DESYNC_MARK_POSTNAT=0x20000000` - -If uncommented pass to zapret only packets marked with this bit - -`#FILTER_MARK=0x10000000` - -Bit must be set in your own rules. -* iptables - in mangle PREROUTING and mangle OUTPUT before zapret rules (iptables -I _after_ zapret rules application). -* nftables - in output and prerouting hooks with priority -102 or lower. - -Mark criterias can be any. For example, source IP or source interface name. - -**tpws** socks proxy mode switch - -`TPWS_SOCKS_ENABLE=0` - -Listening tcp port for **tpws** proxy mode. - -`TPPORT_SOCKS=987` - -**tpws** socks mode parameters - -``` -TPWS_SOCKS_OPT=" ---filter-tcp=80 --methodeol --new ---filter-tcp=443 --split-pos=1,midsld --disorder " -" -``` - -**tpws** transparent mode switch - -`TPWS_ENABLE=0` - -**tpws** transparent mode target ports - -`TPWS_PORTS=80,443` - -**tpws** transparent mode parameters - -``` -TPWS_OPT=" ---filter-tcp=80 --methodeol --new ---filter-tcp=443 --split-pos=1,midsld --disorder " -" -``` - -**nfqws** enable switch - -`NFQWS_ENABLE=0` - -**nfqws** port targets for `connbytes`-limited interception. `connbytes` allows to intercept only starting packets from connections. -This is more effective kernel-mode alternative to `nfqws --dpi-desync-cutoff=nX`. - -``` -NFQWS_PORTS_TCP=80,443 -NFQWS_PORTS_UDP=443 -``` - -How many starting packets should be intercepted to nfqws in each direction - -``` -NFQWS_TCP_PKT_OUT=$((6+$AUTOHOSTLIST_RETRANS_THRESHOLD)) -NFQWS_TCP_PKT_IN=3 -NFQWS_UDP_PKT_OUT=$((6+$AUTOHOSTLIST_RETRANS_THRESHOLD)) -NFQWS_UDP_PKT_IN=0 -``` - -There's kind of traffic that requires interception of entire outgoing stream. -Typically it's support for plain http keepalives and stateless DPI. -This mode of interception significantly increases CPU utilization. Use with care and only if required. -Here you specify port numbers for unlimited interception. -It's advised also to remove these ports from `connbytes`-limited interception list. - -``` -#NFQWS_PORTS_TCP_KEEPALIVE=80 -#NFQWS_PORTS_UDP_KEEPALIVE= -``` - -**nfqws** parameters - -``` -NFQWS_OPT=" ---filter-tcp=80 --dpi-desync=fake,multisplit --dpi-desync-split-pos=method+2 --dpi-desync-fooling=md5sig --new ---filter-tcp=443 --dpi-desync=fake,multidisorder --dpi-desync-split-pos=1,midsld --dpi-desync-fooling=badseq,md5sig --new ---filter-udp=443 --dpi-desync=fake --dpi-desync-repeats=6 -" -``` - - -Host filtering mode : -``` -none - apply fooling to all hosts -ipset - limit fooling to hosts from ipset zapret/zapret6 -hostlist - limit fooling to hosts from hostlist -autohostlist - hostlist mode + blocks auto detection -``` - -`MODE_FILTER=none` - - -flow offloading control (if supported) - -``` -donttouch : disable system flow offloading setting if selected mode is incompatible with it, dont touch it otherwise and dont configure selective flow offloading -none : always disable system flow offloading setting and dont configure selective flow offloading -software : always disable system flow offloading setting and configure selective software flow offloading -hardware : always disable system flow offloading setting and configure selective hardware flow offloading -``` - -`FLOWOFFLOAD=donttouch` - -The GETLIST parameter tells the install_easy.sh installer which script to call -to update the list of blocked ip or hosts. -Its called via `get_config.sh` from scheduled tasks (crontab or systemd timer). -Put here the name of the script that you will use to update the lists. -If not, then the parameter should be commented out. - -You can individually disable ipv4 or ipv6. If the parameter is commented out or not equal to "1", -use of the protocol is permitted. - -``` -#DISABLE_IPV4=1 -DISABLE_IPV6=1 -``` - -The number of threads for mdig multithreaded DNS resolver (1..100). -The more of them, the faster, but will your DNS server be offended by hammering ? - -`MDIG_THREADS=30` - -temp directory. Used by ipset/*.sh scripts for large lists processing. -/tmp by default. Can be reassigned if /tmp is tmpfs and RAM is low. -TMPDIR=/opt/zapret/tmp - -ipset and nfset options : - -``` -SET_MAXELEM=262144 -IPSET_OPT="hashsize 262144 maxelem 2097152 -``` - -Kernel automatically increases hashsize if ipset is too large for the current hashsize. -This procedure requires internal reallocation and may require additional memory. -On low RAM systems it can cause errors. -Do not use too high hashsize. This way you waste your RAM. And dont use too low hashsize to avoid reallocs. - -ip2net options. separate for ipv4 and ipv6. - -``` -IP2NET_OPT4="--prefix-length=22-30 --v4-threshold=3/4" -IP2NET_OPT6="--prefix-length=56-64 --v6-threshold=5" -``` - -autohostlist mode tuning. - -``` -AUTOHOSTLIST_RETRANS_THRESHOLD=3 -AUTOHOSTLIST_FAIL_THRESHOLD=2 -AUTOHOSTLIST_FAIL_TIME=60 -AUTOHOSTLIST_DEBUG=0 -``` - -Enable gzip compression for large lists. Used by ipset/*.sh scripts. - -`GZIP_LISTS=1` - -Command to reload ip/host lists after update. -Comment or leave empty for auto backend selection : ipset or ipfw if present. -On BSD systems with PF no auto reloading happens. You must provide your own command. -Newer FreeBSD versions support table only reloading : `pfctl -Tl -f /etc/pf.conf` -Set to "-" to disable reload. - -`LISTS_RELOAD="pfctl -f /etc/pf.conf"` - -In openwrt there's default network `lan`. Only traffic coming from this network is redirected to tpws by default. -To override this behaviour set the following variable : - -`OPENWRT_LAN="lan lan2 lan3"` - -In openwrt wan interfaces are those having default route. Separately for ipv4 and ipv6. -This can be redefined : -``` -OPENWRT_WAN4="wan4 vpn" -OPENWRT_WAN6="wan6 vpn6" -``` - -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. -Not applicable to `OpenWRT` if used with `firewall3+iptables`. - -`FILTER_TTL_EXPIRED_ICMP=1` blocks icmp time exceeded messages in response to connections handled by nfqws. -Linux closes socket if it receives this icmp in response to SYN packet. Similar mechanism exists for datagram sockets. -It's better to disable this if you do not expect problems caused by icmp. - -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 -IFACE_WAN6="henet ipsec0" -``` -Multiple interfaces are space separated. IF IFACE_WAN6 is omitted then IFACE_WAN value is taken. - -IMPORTANT: configuring routing, masquerade, etc. not a zapret task. -Only modes that intercept transit traffic are enabled. -It's possible to specify multiple interfaces like this : `IFACE_LAN="eth0 eth1 eth2"` - - -## 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 - /opt/zapret/init.d/sysv/zapret restart_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 - /opt/zapret/init.d/sysv/zapret restart_daemons -``` - -nftables nearly eliminate conflicts betweeen firewall control systems because they allow -separate tables and netfilter hooks. `zapret` nf table is used for zapret purposes. -If your system does not touch it everything will likely be OK. - -Some additional nftables-only calls exist : - -Lookup `lanif`, `wanif`, `wanif6` and `flow table` interface sets. -``` - /opt/zapret/init.d/sysv/zapret list_ifsets -``` - -Renew `lanif`, `wanif`, `wanif6` and `flow table` interface sets. -Taken from `IFACE_LAN`, `IFACE_WAN` config variables on traditional Linux systems. -Autoselected on `OpenWRT`. `lanif` can be extended using `OPENWRT_LAN` config variable. -``` - /opt/zapret/init.d/sysv/zapret reload_ifsets -``` - -Calls `nft -t list table inet zapret`. -``` - /opt/zapret/init.d/sysv/zapret list_table -``` - -It's also possible to hook with your script to any stage of zapret firewall processing. -The following settings are available in the zapret config file : - -``` -INIT_FW_PRE_UP_HOOK="/etc/firewall.zapret.hook.pre_up" -INIT_FW_POST_UP_HOOK="/etc/firewall.zapret.hook.post_up" -INIT_FW_PRE_DOWN_HOOK="/etc/firewall.zapret.hook.pre_down" -INIT_FW_POST_DOWN_HOOK="/etc/firewall.zapret.hook.post_down" -``` - -Hooks are extremely useful if you need nftables sets populated by zapret scripts. -nfsets can only belong to one table. You have to write rule there and synchorize them with zapret scripts. - -## Installation - -### Checking ISP - -Before running zapret you must discover working bypass strategy. -`blockcheck.sh` automates this process. It first checks DNS then tries many strategies finding the working ones. -Note that DNS check is mostly Russia targeted. It checks several pre-defined blocked in Russia domains and -verifies system DNS answers with public DNS answers. Because ISP can block public DNS or redirect any DNS queries -to their servers `blockcheck.sh` also checks that all returned answers are unique. Usually if DNS is blocked -ISP returns single ip for all blocked domains to redirect you to their "access denied" page. -DoH servers are used automatically for checks if DNS spoof is detected. -`blockcheck.sh` works on all systems supported by `zapret`. - -### desktop linux system - -Simple install works on most modern linux distributions with systemd or openrc, OpenWRT and MacOS. -Run `install_easy.sh` and answer its questions. - -### OpenWRT - -`install_easy.sh` works on openwrt but there're additional challenges. -They are mainly about possibly low flash free space. -Simple install will not work if it has no space to install itself and required packages from the repo. - -Another challenge would be to bring zapret to the router. You can download zip from github and use it. -Install openssh-sftp-server and unzip to openwrt and use sftp to transfer the file. -It's also not too hard to use 'nc' (netcat) for file transfer. - -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. - -For low storage openwrt see `init.d/openwrt-minimal`. - -### Android - -Its not possible to use **nfqws** and **tpws** in transparent proxy mode without root privileges. Without root **tpws** can run in `--socks` mode. - -Android has NFQUEUE and **nfqws** should work. - -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. - -Although linux binaries work it's recommended to use Android specific ones. They have no problems with user names, local time, DNS, ... -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`. - -**nfqws** should be executed with `--uid 1`. Otherwise on some devices or firmwares kernel may partially hang. Looks like processes with certain uids can be suspended. With buggy chineese cellular interface driver this can lead to device hang. - -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` - -How to run **tpws** on root-less android. -You can't write to `/system`, `/data`, can't run from sd card. -Selinux prevents running executables in `/data/local/tmp` from apps. -Use adb and adb shell. - -``` -mkdir /data/local/tmp/zapret -adb push tpws /data/local/tmp/zapret -chmod 755 /data/local/tmp/zapret /data/local/tmp/zapret/tpws -chcon u:object_r:system_file:s0 /data/local/tmp/zapret/tpws -``` - -Now its possible to run `/data/local/tmp/zapret/tpws` from any app such as tasker. - -### FreeBSD, OpenBSD, MacOS - -see [BSD documentation](./bsd.en.md) - -### Windows (WSL) - -see [Windows documentation](./windows.en.md) - -### Other devices - -Author's goal does not include easy supporting as much devices as possibles. -Please do not ask for easy supporting firmwares. It requires a lot of work and owning lots of devices. Its counterproductive. -As a devices owner its easier for you and should not be too hard if firmware is open. -Most closed stock firmwares are not designed for custom usage and sometimes actively prevent it. -In the latter case you have to hack into it and reverse engineer. Its not easy. -Binaries are universal. They can run on almost all firmwares. -You will need : - * root shell access. true sh shell, not microtik-like console - * startup hook - * r/w partition to store binaries and startup script with executable permission (+x) - * **tpws** can be run almost anywhere but **nfqws** require kernel support for NFQUEUE. Its missing in most firmwares. - * too old 2.6 kernels are unsupported and can cause errors. newer 2.6 kernels are OK. -If binaries crash with segfault (rare but happens on some kernels) try to unpack upx like this : upx -d tpws. - -First manually debug your scenario. Run iptables + daemon and check if its what you want. -Write your own script with iptables magic and run required daemon from there. Put it to startup. -Dont ask me how to do it. Its different for all firmwares and requires studying. -Find manual or reverse engineer yourself. -Check for race conditions. Firmware can clear or modify iptables after your startup script. -If this is the case then run another script in background and add some delay there. - -## Donations - -Are welcome here : - -USDT `0x3d52Ce15B7Be734c53fc9526ECbAB8267b63d66E` - -BTC `bc1qhqew3mrvp47uk2vevt5sctp7p2x9m7m5kkchve` - -ETH `0x3d52Ce15B7Be734c53fc9526ECbAB8267b63d66E` diff --git a/docs/readme.md b/docs/readme.md deleted file mode 100644 index 932c4939..00000000 --- a/docs/readme.md +++ /dev/null @@ -1,2723 +0,0 @@ -# ВНИМАНИЕ, остерегайтесь мошенников - -zapret является свободным и open source. -Всякий, кто понуждает вас скачивать zapret только с его ресурса, требует удалить ссылки, видео, файлы, обосновывая эти требования авторскими правами, сам нарушает [лицензию](./LICENSE.txt). -Однако, это не исключает [добровольные пожертвования](#поддержать-разработчика). - -# Multilanguage README - -[![en](https://img.shields.io/badge/lang-en-red.svg)](./readme.en.md) -[![ru](https://img.shields.io/badge/lang-ru-green.svg)](./readme.md) - -*** - - - [Зачем это нужно](#зачем-это-нужно) - - [Быстрый старт](#быстрый-старт) - - [Как это работает](#как-это-работает) - - [Что сейчас происходит в России](#что-сейчас-происходит-в-россии) - - [Как это реализовать на практике в системе linux](#как-это-реализовать-на-практике-в-системе-linux) - - [Когда это работать не будет](#когда-это-работать-не-будет) - - [nfqws](#nfqws) - - [АТАКА ДЕСИНХРОНИЗАЦИИ DPI](#атака-десинхронизации-dpi) - - [ФЕЙКИ](#фейки) - - [МОДИФИКАЦИЯ ФЕЙКОВ](#модификация-фейков) - - [TCP СЕГМЕНТАЦИЯ](#tcp-сегментация) - - [ПЕРЕКРЫТИЕ SEQUENCE NUMBERS](#перекрытие-sequence-numbers) - - [НАЗНАЧЕНИЕ IP_ID](#назначение-ip_id) - - [СПЕЦИФИЧЕСКИЕ РЕЖИМЫ IPV6](#специфические-режимы-ipv6) - - [МОДИФИКАЦИЯ ОРИГИНАЛА](#модификация-оригинала) - - [ДУБЛИКАТЫ](#дубликаты) - - [КОМБИНИРОВАНИЕ МЕТОДОВ ДЕСИНХРОНИЗАЦИИ](#комбинирование-методов-десинхронизации) - - [КЭШ IP](#кэш-ip) - - [РЕАКЦИЯ DPI НА ОТВЕТ СЕРВЕРА](#реакция-dpi-на-ответ-сервера) - - [РЕЖИМ SYNACK](#режим-synack) - - [РЕЖИМ SYNDATA](#режим-syndata) - - [ВИРТУАЛЬНЫЕ МАШИНЫ](#виртуальные-машины) - - [CONNTRACK](#conntrack) - - [РЕАССЕМБЛИНГ](#реассемблинг) - - [ПОДДЕРЖКА UDP](#поддержка-udp) - - [IP ФРАГМЕНТАЦИЯ](#ip-фрагментация) - - [МНОЖЕСТВЕННЫЕ СТРАТЕГИИ](#множественные-стратегии) - - [ФИЛЬТРАЦИЯ ПО WIFI](#фильтрация-по-wifi) - - [IPTABLES ДЛЯ NFQWS](#iptables-для-nfqws) - - [NFTABLES ДЛЯ NFQWS](#nftables-для-nfqws) - - [FLOW OFFLOADING](#flow-offloading) - - [ОСОБЕННОСТИ ЖЕЛЕЗОК](#особенности-железок) - - [ДУРЕНИЕ СО СТОРОНЫ СЕРВЕРА](#дурение-со-стороны-сервера) - - [tpws](#tpws) - - [TCP СЕГМЕНТАЦИЯ В TPWS](#tcp-сегментация-в-tpws) - - [TLSREC](#tlsrec) - - [MSS](#mss) - - [ДРУГИЕ ПАРАМЕТРЫ ДУРЕНИЯ](#другие-параметры-дурения) - - [МНОЖЕСТВЕННЫЕ СТРАТЕГИИ](#множественные-стратегии-1) - - [СЛУЖЕБНЫЕ ПАРАМЕТРЫ](#служебные-параметры) - - [IPTABLES ДЛЯ TPWS](#iptables-для-tpws) - - [NFTABLES ДЛЯ TPWS](#nftables-для-tpws) - - [ip2net](#ip2net) - - [mdig](#mdig) - - [Способы получения списка заблокированных IP](#способы-получения-списка-заблокированных-ip) - - [Фильтрация по именам доменов](#фильтрация-по-именам-доменов) - - [Режим фильтрации autohostlist](#режим-фильтрации-autohostlist) - - [Проверка провайдера](#проверка-провайдера) - - [Выбор параметров](#выбор-параметров) - - [Прикручивание к системе управления фаерволом или своей системе запуска](#прикручивание-к-системе-управления-фаерволом-или-своей-системе-запуска) - - [Вариант custom](#вариант-custom) - - [Простая установка](#простая-установка) - - [Установка под systemd](#установка-под-systemd) - - [Простая установка на openwrt](#простая-установка-на-openwrt) - - [Установка на openwrt в режиме острой нехватки места на диске](#установка-на-openwrt-в-режиме-острой-нехватки-места-на-диске) - - [Android](#android) - - [Мобильные модемы и роутеры huawei](#мобильные-модемы-и-роутеры-huawei) - - [FreeBSD, OpenBSD, MacOS](#freebsd-openbsd-macos) - - [Windows](#windows) - - [Другие прошивки](#другие-прошивки) - - [Обход блокировки через сторонний хост](#обход-блокировки-через-сторонний-хост) - - [Почему стоит вложиться в покупку VPS](#почему-стоит-вложиться-в-покупку-vps) - - [Поддержать разработчика](#поддержать-разработчика) -*** - -## Зачем это нужно - -Автономное средство противодействия DPI, которое не требует подключения каких-либо сторонних серверов. Может помочь -обойти блокировки или замедление сайтов HTTP(S), сигнатурный анализ TCP и UDP протоколов, например, с целью блокировки -VPN. - -Проект нацелен прежде всего на маломощные embedded устройства - роутеры, работающие под OpenWrt. Поддерживаются -традиционные Linux-системы, FreeBSD, OpenBSD, частично macOS. В некоторых случаях возможна самостоятельная прикрутка -решения к различным прошивкам. - -Большая часть функционала работает на Windows. - -## Быстрый старт - -- [Linux/openWrt](./quick_start.md) -- [Windows](./quick_start_windows.md) - -## Как это работает - -В самом простейшем случае вы имеете дело с пассивным DPI. Пассивный DPI может читать трафик из потока, может инжектить -свои пакеты, но не может блокировать проходящие пакеты. Если запрос "плохой", пассивный DPI инжектит пакет RST, -опционально дополняя его пакетом HTTP redirect. Если фейк пакет инжектится только для клиента, в этом случае можно -обойтись командами iptables для дропа RST и/или редиректа на заглушку по определённым условиям, которые нужно подбирать -для каждого провайдера индивидуально. Так мы обходим последствия срабатывания триггера запрета. Если пассивный DPI -направляет пакет RST в том числе и серверу, то вы ничего с этим не сможете сделать. Ваша задача — не допустить -срабатывания триггера запрета. Одними iptables уже не обойтись. Этот проект нацелен именно на предотвращение -срабатывания запрета, а не ликвидацию его последствий. - -Активный DPI ставится в разрез провода и может дропать пакеты по любым критериям, в том числе распознавать TCP-потоки и -блокировать любые пакеты, принадлежащие потоку. - -Как не допустить срабатывания триггера запрета? Послать то, на что DPI не рассчитывает и что ломает ему алгоритм -распознавания запросов и их блокировки. - -Некоторые DPI не могут распознать HTTP-запрос, если он разделен на TCP-сегменты. Например, запрос -вида `GET / HTTP/1.1\r\nHost: kinozal.tv......` -мы посылаем двумя частями: сначала идет `GET`, затем `/ HTTP/1.1\r\nHost: kinozal.tv.....`. Другие DPI спотыкаются, когда -заголовок `Host:` пишется в другом регистре: например, `host:`. Кое-где работает добавление дополнительного пробела -после метода: `GET /` → `GET /` -или добавление точки в конце имени хоста: `Host: kinozal.tv.` - -Существует и более продвинутая магия, направленная на преодоление DPI на пакетном уровне. - -Подробнее про DPI:\ -https://habr.com/ru/post/335436 или https://web.archive.org/web/20230331233644/https://habr.com/ru/post/335436/ \ -https://geneva.cs.umd.edu/papers/geneva_ccs19.pdf - -## Что сейчас происходит в России - -Раньше, до внедрения повсеместных систем ТСПУ, использовался зоопарк различных DPI у провайдеров. Какие-то были -активными, какие-то пассивными. Сейчас время простых iptables окончательно ушло. Везде активный DPI ТСПУ, но кое-где -могут оставаться невыключенными дополнительные старые DPI из зоопарка. В этом случае приходится обходить сразу несколько -DPI. Все больше становится внереестровых блокировок, о которых вы узнаете только по факту недоступности чего-либо, в -списках этого нет. Применяются блокировки некоторых диапазонов ip адресов (автономный обход невозможен) -и протоколов (VPN). На некоторых диапазонах IP используется более строгий фильтр, распознающий попытки обмана через -сегментацию. Должно быть это связано с некоторыми сервисами, которые пытаются таким образом обмануть DPI. - -## Как это реализовать на практике в системе linux - -Если кратко, то варианты можно классифицировать по следующей схеме : - -1) Пассивный DPI, не отправляющий RST серверу. Помогут индивидуально настраиваемые под провайдера команды iptables. На - rutracker в разделе "обход блокировок - другие способы" по этому вопросу существует отдельная тема. В данном проекте - не рассматривается. Если вы не допустите срабатывание триггера запрета, то и не придется бороться с его - последствиями. -2) Модификация TCP соединения на уровне потока. Реализуется через proxy или transparent proxy. -3) Модификация TCP соединения на уровне пакетов. Реализуется через обработчик очереди NFQUEUE и raw сокеты. - -Для вариантов 2 и 3 реализованы программы tpws и nfqws соответственно. Чтобы они работали, необходимо их запустить с -нужными параметрами и перенаправить на них определенный трафик средствами iptables или nftables. - -## Когда это работать не будет - -* Если подменяется DNS. С этой проблемой легко справиться. -* Если блокировка осуществляется по IP. -* Если соединение проходит через фильтр, способный реконструировать TCP соединение, и который следует всем стандартам. - Например, нас заворачивают на squid. Соединение идет через полноценный стек tcpip операционной системы. - Проект нацелен на обман DPI, который всилу ограниченности ресурсов и большого трафика вынужден интерпретировать его лишь ограниченно. - Обмануть полноценный стек ОС и полноценные серверные приложения не получится. - -## nfqws - -Эта программа - модификатор пакетов и обработчик очереди NFQUEUE. Для BSD систем существует адаптированный вариант - -dvtws, собираемый из тех же исходников (см. [документация BSD](./bsd.md)). - -``` -@|$ ; читать конфигурацию из файла. опция должна быть первой. остальные опции игнорируются. - ---debug=0|1 ; 1=выводить отладочные сообщения ---dry-run ; проверить опции командной строки и выйти. код 0 - успешная проверка. ---version ; вывести версию и выйти ---comment ; любой текст (игнорируется) ---daemon ; демонизировать прогу ---pidfile= ; сохранить PID в файл ---user= ; менять uid процесса ---uid=uid[:gid] ; менять uid процесса ---qnum=N ; номер очереди N ---bind-fix4 ; пытаться решить проблему неверного выбора исходящего интерфейса для сгенерированных ipv4 пакетов ---bind-fix6 ; пытаться решить проблему неверного выбора исходящего интерфейса для сгенерированных ipv6 пакетов ---ctrack-timeouts=S:E:F[:U] ; таймауты внутреннего conntrack в состояниях SYN, ESTABLISHED, FIN, таймаут udp. по умолчанию 60:300:60:60 ---ctrack-disable=[0|1] ; 1 или остутствие аргумента отключает conntrack ---ipcache-lifetime= ; время жизни записей кэша IP в секундах. 0 - без ограничений. ---ipcache-hostname=[0|1] ; 1 или отсутствие аргумента включают кэширование имен хостов для применения в стратегиях нулевой фазы ---wsize=[:] ; менять tcp window size на указанный размер в SYN,ACK. если не задан scale_factor, то он не меняется (устарело !) ---wssize=[:] ; менять tcp window size на указанный размер в исходящих пакетах. scale_factor по умолчанию 0. (см. conntrack !) ---wssize-cutoff=[n|d|s]N ; изменять server window size в исходящих пакетах (n), пакетах данных (d), относительных sequence (s) по номеру меньше N ---wssize-forced-cutoff=0|1 ; 1(default)=автоматически отключать wssize в случае обнаружения известного протокола ---synack-split=[syn|synack|acksyn] ; выполнить tcp split handshake. вместо SYN,ACK отсылать только SYN, SYN+ACK или ACK+SYN ---orig-ttl= ; модифицировать TTL оригинального пакета ---orig-ttl6= ; модифицировать ipv6 hop limit оригинальных пакетов. если не указано, используется значение --orig-ttl ---orig-autottl=[[:[-]]|-] ; режим auto ttl для ipv4 и ipv6. по умолчанию: +5:3-64. "0:0-0" или "-" отключает функцию ---orig-autottl6=[[:[-]]|-] ; переопределение предыдущего параметра для ipv6 ---orig-tcp-flags-set= ; устанавливать указанные tcp флаги (flags |= value). число , либо список через запятую : FIN,SYN,RST,PSH,ACK,URG,ECE,CWR,AE,R1,R2,R3 ---orig-tcp-flags-unset= ; удалять указанные tcp флаги (flags &= ~value) ---orig-mod-start=[n|d|s]N ; применять orig-mod только в исходящих пакетах (n), пакетах данных (d), относительных sequence (s) по номеру больше или равно N ---orig-mod-cutoff=[n|d|s]N ; применять orig-mod только в исходящих пакетах (n), пакетах данных (d), относительных sequence (s) по номеру меньше N ---dup= ; высылать N дубликатов до оригинала ---dup-replace=[0|1] ; 1 или отсутствие аргумента блокирует отправку оригинала. отправляются только дубликаты. ---dup-ttl= ; модифицировать TTL дубликатов ---dup-ttl6= ; модифицировать ipv6 hop limit дубликатов. если не указано, используется значение --dup-ttl ---dup-autottl=[[:[-]]|-] ; режим auto ttl для ipv4 и ipv6. по умолчанию: +1:3-64. "0:0-0" или "-" отключает функцию ---dup-autottl6=[[:[-]]|-] ; переопределение предыдущего параметра для ipv6 ---dup-tcp-flags-set= ; устанавливать указанные tcp флаги (flags |= value). число , либо список через запятую : FIN,SYN,RST,PSH,ACK,URG,ECE,CWR,AE,R1,R2,R3 ---dup-tcp-flags-unset= ; удалять указанные tcp флаги (flags &= ~value) ---dup-fooling= ; дополнительные методики как сделать, чтобы дубликат не дошел до сервера. none md5sig badseq badsum datanoack ts hopbyhop hopbyhop2 ---dup-ts-increment= ; инкремент TSval для ts. по умолчанию -600000 ---dup-badseq-increment= ; инкремент sequence number для badseq. по умолчанию -10000 ---dup-badack-increment= ; инкремент ack sequence number для badseq. по умолчанию -66000 ---dup-ip-id=same|zero|seq|rnd ; режим назначения ip_id для пакетов dup ---dup-start=[n|d|s]N ; применять dup только в исходящих пакетах (n), пакетах данных (d), относительных sequence (s) по номеру больше или равно N ---dup-cutoff=[n|d|s]N ; применять dup только в исходящих пакетах (n), пакетах данных (d), относительных sequence (s) по номеру меньше N ---hostcase ; менять регистр заголовка "Host:" по умолчанию на "host:". ---hostnospace ; убрать пробел после "Host:" и переместить его в конец значения "User-Agent:" для сохранения длины пакета ---methodeol ; добавить перевод строки в unix стиле ('\n') перед методом и убрать пробел из Host: : "GET / ... Host: domain.com" => "\nGET / ... Host:domain.com" ---hostspell=HoST ; точное написание заголовка Host (можно "HOST" или "HoSt"). автоматом включает --hostcase ---domcase ; домен после Host: сделать таким : TeSt.cOm ---ip-id=seq|seqgroup|rnd|zero ; режим назначения ip_id для генерированных пакетов ---dpi-desync=[,][, ; бит fwmark для пометки десинхронизирующих пакетов, чтобы они повторно не падали в очередь. default = 0x40000000 ---dpi-desync-ttl= ; установить ttl для десинхронизирующих пакетов ---dpi-desync-ttl6= ; установить ipv6 hop limit для десинхронизирующих пакетов. если не указано, используется значение --dpi-desync-ttl ---dpi-desync-autottl=[[:[-]]|-] ; режим auto ttl для ipv4 и ipv6. по умолчанию: 1:3-20. "0:0-0" или "-" отключает функцию ---dpi-desync-autottl6=[[:[-]]|-] ; переопределение предыдущего параметра для ipv6 ---dpi-desync-tcp-flags-set= ; устанавливать указанные tcp флаги (flags |= value). число , либо список через запятую : FIN,SYN,RST,PSH,ACK,URG,ECE,CWR,AE,R1,R2,R3 ---dpi-desync-tcp-flags-unset= ; удалять указанные tcp флаги (flags &= ~value) ---dpi-desync-fooling= ; дополнительные методики как сделать, чтобы фейковый пакет не дошел до сервера. none md5sig badseq badsum datanoack ts hopbyhop hopbyhop2 ---dpi-desync-repeats= ; посылать каждый генерируемый в nfqws пакет N раз (не влияет на остальные пакеты) ---dpi-desync-skip-nosni=0|1 ; 1(default)=не применять dpi desync для запросов без hostname в SNI, в частности для ESNI ---dpi-desync-split-pos=N|-N|marker+N|marker-N ; список через запятую маркеров для tcp сегментации в режимах split и disorder ---dpi-desync-split-seqovl=N|-N|marker+N|marker-N ; единичный маркер, определяющий величину перекрытия sequence в режимах split и disorder. для split поддерживается только положительное число. ---dpi-desync-split-seqovl-pattern=[+ofs]@|0xHEX ; чем заполнять фейковую часть overlap ---dpi-desync-fakedsplit-pattern=[+ofs]@|0xHEX ; чем заполнять фейки в fakedsplit/fakeddisorder ---dpi-desync-fakedsplit-mod=mod[,mod] ; может быть none, altorder=0|1|2|3 + 0|8|16 ---dpi-desync-hostfakesplit-midhost=marker+N|marker-N ; маркер дополнительного разреза сегмента с оригинальным хостом. должен попадать в пределы хоста. ---dpi-desync-hostfakesplit-mod=mod[,mod] ; может быть none, host=, altorder=0|1 ---dpi-desync-ts-increment= ; инкремент TSval для ts. по умолчанию -600000 ---dpi-desync-badseq-increment= ; инкремент sequence number для badseq. по умолчанию -10000 ---dpi-desync-badack-increment= ; инкремент ack sequence number для badseq. по умолчанию -66000 ---dpi-desync-any-protocol=0|1 ; 0(default)=работать только по http request и tls clienthello 1=по всем непустым пакетам данных ---dpi-desync-fake-tcp-mod=mod[,mod] ; список через запятую режимов runtime модификации tcp фейков (любых) : none, seq ---dpi-desync-fake-http=[+ofs]@|0xHEX ; файл, содержащий фейковый http запрос для dpi-desync=fake, на замену стандартному www.iana.org ---dpi-desync-fake-tls=[+ofs]@|0xHEX|![+offset] ; файл, содержащий фейковый tls clienthello для dpi-desync=fake, на замену стандартному. '!' = стандартный фейк ---dpi-desync-fake-tls-mod=mod[,mod] ; список через запятую режимов runtime модификации фейков : none,rnd,rndsni,sni=,dupsid,padencap ---dpi-desync-fake-unknown=[+ofs]@|0xHEX ; файл, содержащий фейковый пейлоад неизвестного протокола для dpi-desync=fake, на замену стандартным нулям 256 байт ---dpi-desync-fake-syndata=[+ofs]@|0xHEX ; файл, содержащий фейковый пейлоад пакета SYN для режима десинхронизации syndata ---dpi-desync-fake-quic=[+ofs]@|0xHEX ; файл, содержащий фейковый QUIC Initial ---dpi-desync-fake-wireguard=[+ofs]@|0xHEX ; файл, содержащий фейковый wireguard handshake initiation ---dpi-desync-fake-dht=[+ofs]@|0xHEX ; файл, содержащий фейковый пейлоад DHT протокола для dpi-desync=fake, на замену стандартным нулям 64 байт ---dpi-desync-fake-discord=[+ofs]@|0xHEX ; файл, содержащий фейковый пейлоад Discord протокола нахождения IP адреса для голосовых чатов для dpi-desync=fake, на замену стандартным нулям 64 байт ---dpi-desync-fake-stun=[+ofs]@|0xHEX ; файл, содержащий фейковый пейлоад STUN протокола для dpi-desync=fake, на замену стандартным нулям 64 байт ---dpi-desync-fake-unknown-udp=[+ofs]@|0xHEX ; файл, содержащий фейковый пейлоад неизвестного udp протокола для dpi-desync=fake, на замену стандартным нулям 64 байт ---dpi-desync-udplen-increment= ; на сколько увеличивать длину udp пейлоада в режиме udplen ---dpi-desync-udplen-pattern=[+ofs]@|0xHEX ; чем добивать udp пакет в режиме udplen. по умолчанию - нули ---dpi-desync-start=[n|d|s]N ; применять dpi desync только в исходящих пакетах (n), пакетах данных (d), относительных sequence (s) по номеру больше или равно N ---dpi-desync-cutoff=[n|d|s]N ; применять dpi desync только в исходящих пакетах (n), пакетах данных (d), относительных sequence (s) по номеру меньше N ---hostlist= ; действовать только над доменами, входящими в список из filename. поддомены автоматически учитываются, если хост не начинается с '^'. - ; в файле должен быть хост на каждой строке. - ; список читается при старте и хранится в памяти в виде иерархической структуры для быстрого поиска. - ; при изменении времени модификации файла он перечитывается автоматически по необходимости - ; список может быть запакован в gzip. формат автоматически распознается и разжимается - ; списков может быть множество. пустой общий лист = его отсутствие - ; хосты извлекаются из Host: хедера обычных http запросов и из SNI в TLS ClientHello. ---hostlist-domains= ; фиксированный список доменов через зяпятую. можно использовать # в начале для комментирования отдельных доменов. ---hostlist-exclude= ; не применять дурение к доменам из листа. может быть множество листов. схема аналогична include листам. ---hostlist-exclude-domains= ; фиксированный список доменов через зяпятую. можно использовать # в начале для комментирования отдельных доменов. ---hostlist-auto= ; обнаруживать автоматически блокировки и заполнять автоматический hostlist (требует перенаправления входящего трафика) ---hostlist-auto-fail-threshold= ; сколько раз нужно обнаружить ситуацию, похожую на блокировку, чтобы добавить хост в лист (по умолчанию: 3) ---hostlist-auto-fail-time= ; все эти ситуации должны быть в пределах указанного количества секунд (по умолчанию: 60) ---hostlist-auto-retrans-threshold= ; сколько ретрансмиссий запроса считать блокировкой (по умолчанию: 3) ---hostlist-auto-debug= ; лог положительных решений по autohostlist. позволяет разобраться почему там появляются хосты. ---new ; начало новой стратегии (новый профиль) ---skip ; не использовать этот профиль . полезно для временной деактивации профиля без удаления параметров. ---filter-l3=ipv4|ipv6 ; фильтр версии ip для текущей стратегии ---filter-tcp=[~]port1[-port2]|* ; фильтр портов tcp для текущей стратегии. ~ означает инверсию. установка фильтра tcp и неустановка фильтра udp запрещает udp. поддерживается список через запятую. ---filter-udp=[~]port1[-port2]|* ; фильтр портов udp для текущей стратегии. ~ означает инверсию. установка фильтра udp и неустановка фильтра tcp запрещает tcp. поддерживается список через запятую. ---filter-l7= ; фильтр протокола L6-L7. поддерживается несколько значений через запятую. proto : http tls quic wireguard dht discord stun unknown ---filter-ssid=ssid1[,ssid2,ssid3,...] ; фильтр по имени wifi сети (только для linux) ---ipset= ; включающий ip list. на каждой строчке ip или cidr ipv4 или ipv6. поддерживается множество листов и gzip. перечитка автоматическая. ---ipset-ip= ; фиксированный список подсетей через запятую. можно использовать # в начале для комментирования отдельных подсетей. ---ipset-exclude= ; исключающий ip list. на каждой строчке ip или cidr ipv4 или ipv6. поддерживается множество листов и gzip. перечитка автоматическая. ---ipset-exclude-ip= ; фиксированный список подсетей через запятую. можно использовать # в начале для комментирования отдельных подсетей. -``` - -`--debug` позволяет выводить подробный лог действий на консоль, в syslog или в файл. Может быть важен порядок следования -опций. `--debug` лучше всего указывать в самом начале. Опции анализируются последовательно. Если ошибка будет при -проверке опции, а до анализа `--debug` еще дело не дошло, то сообщения не будут выведены в файл или syslog. При -логировании в файл процесс не держит файл открытым. Ради каждой записи файл открывается и потом закрывается. Так что -файл можно удалить в любой момент, и он будет создан заново при первом же сообщении в лог. Но имейте в виду, что если вы -запускаете процесс под root, то будет сменен UID на не-root. В начале на лог файл меняется owner, иначе запись будет -невозможна. Если вы потом удалите файл, и у процесса не будет прав на создание файла в его директории, лог больше не -будет вестись. Вместо удаления лучше использовать truncate. В шелле это можно сделать через команду ": >filename" - -Многие параметры, загружающие двоичные данные из файлов, поддерживают загрузку из hex-строки или из файла. -hex строка начинается с "0x". Имя файла можно писать как есть или использовать префикс "@". -Если перед префиксом "@" указано "+<число>", то это означает смещение полезных данных внутри файла. -Файл может загружаться целиком с нулевой позиции, к нему могут применяться модификации, требующие полного файла (TLS), -но передача пойдет с позиции offset. offset должен быть меньше длины файла. Если к блоку данных применяется мод, -который уменьшает размер данных, и offset окажется не меньше новой длины данных, будет ошибка. - -### АТАКА ДЕСИНХРОНИЗАЦИИ DPI - -Суть ее в следующем. Берется оригинальный запрос, модифицируется, добавляется поддельная информация (фейки) -таким образом, чтобы ОС сервера передала серверному процессу оригинальный запрос в неизменном виде, а DPI увидел другое. -То, что он блокировать не станет. Сервер видит одно, DPI - другое. DPI не понимает, что передается запрещенный запрос и не блокирует его. - -Есть арсенал возможностей, чтобы достичь такого результата. -Это может быть передача фейк пакетов, чтобы они дошли до DPI, но не дошли до сервера. Может использоваться фрагментация на уровне TCP (сегментация) или на уровне IP. -Есть атаки, основанные на игре с tcp sequence numbers или с перепутыванием порядка следования tcp сегментов. -Методы могут сочетаться в различных вариантах. - -### ФЕЙКИ - -Фейки - это отдельные сгенерированные nfqws пакеты, несущие ложную информацию для DPI. -Они либо не должны дойти до сервера, либо могут дойти, но должны быть им отброшены. -Иначе получается слом tcp соединения или нарушение целостности передаваемого потока, что гарантированно приводит к поломке ресурса. -Есть ряд методов для решения этой задачи. - -* `md5sig` добавляет TCP опцию **MD5 signature**. Работает не на всех серверах. Пакеты с md5 обычно отбрасывают только linux. - Требуется значительное увеличение длины tcp пакета, чтобы вместить tcp option. При обработке многосегментных запросов (TLS Kyber) - первый пакет идет полный под MTU. При fakedsplit/fakeddisorder на небольших позициях отдельные tcp сегменты достаточно велики, чтобы внедрение - md5 tcp option вызвало переполнение MTU и ошибку отправки "message too long". `nfqws` не умеет перераспределять данные между tcp сегментами, - поэтому надо или отказываться от kyber, или увеличивать сплит-позицию, или отказываться от fakedsplit/fakeddisorder. -* `badsum` портит контрольную сумму TCP. Не сработает, если ваше устройство за NAT, который не пропускает пакеты с инвалидной суммой. Наиболее - распространенная настройка NAT роутера в Linux их не пропускает. На Linux построено большинство домашних роутеров. - Непропускание обеспечивается так : настройка ядра sysctl по умолчанию - `net.netfilter.nf_conntrack_checksum=1` заставляет conntrack проверять tcp и udp чексуммы входящих пакетов и - выставлять state INVALID для пакетов с инвалидной суммой. Обычно в правилах iptables вставляется правило для дропа - пакетов с состоянием INVALID в цепочке FORWARD. Совместное сочетание этих факторов приводит к непрохождению badsum - через такой роутер. В OpenWrt из коробки `net.netfilter.nf_conntrack_checksum=0`, в других роутерах часто нет, и не - всегда это можно изменить. Чтобы nfqws мог работать через роутер, нужно на нем выставить указанное значение sysctl в 0. - nfqws на самом роутере будет работать и без этой настройки, потому что чексумма локально созданных пакетов не - проверяется никогда. Если роутер за другим NAT, например провайдерским, и он не пропускает invalid packets вы ничего - не сможете с этим сделать. Но обычно провайдеры все же пропускают badsum. На некоторых адаптерах/свитчах/драйверах - принудительно включен rx-checksum offload, badsum пакеты отсекаются еще до получения в ОС. В этом случае если что-то и - можно сделать, то только модифицировать драйвер, что представляется задачей крайне нетривиальной. Установлено, что так - себя ведут некоторые роутеры на базе mediatek. badsum пакеты уходят с клиентской ОС, но роутером не видятся в br-lan - через tcpdump. При этом если nfqws выполняется на самом роутере, обход может работать. badsum нормально уходят с - внешнего интерфейса. -* `badseq` увеличивает TCP sequence number на определенное значение, выводя его тем самым из TCP window. - Такие пакеты будут наверняка отброшены принимающим узлом, но так же и DPI, если он ориентируется на sequence - numbers. По умолчанию смещение seq выбирается -10000. Практика показала, что некоторые DPI не пропускают seq вне - определенного окна. Однако, такое небольшое смещение может вызвать проблемы при существенной потоковой передаче и - потере пакетов. Если вы используете `--dpi-desync-any-protocol`, может понадобиться установить badseq increment - 0x80000000. Это обеспечит надежную гарантию, что поддельный пакет не вклинится в tcp window на сервере. Так же было - замечено, что badseq ломает логику некоторых DPI при анализе http, вызывая зависание соединения. Причем на тех же DPI - TLS с badseq работает нормально. -* `TTL` казалось бы - лучший вариант, но он требует индивидуальной настройки под каждого провайдера. Если DPI находится - дальше локальных сайтов провайдера, то вы можете отрезать себе доступ к ним. Ситуация усугубляется наличием ТСПУ на - магистралах, что вынуждает делать TTL достаточно высоким, увеличивая риск пробоя фейка до сервера. Необходим ip - exclude list, заполняемый вручную. Вместе с ttl можно применять md5sig. Это ничего не испортит, зато дает неплохой - шанс работы сайтов, до которых "плохой" пакет дойдет по TTL. Если не удается найти автоматическое решение, - воспользуйтесь файлом `zapret-hosts-user-exclude.txt`. Некоторые стоковые прошивки роутеров фиксируют исходящий TTL, - без отключения этой опции через них работать не будет. КАКИМ СТОИТ ВЫБИРАТЬ TTL : найдите минимальное значение, при - котором обход еще работает. Это и будет номер хопа вашего DPI. -* `hopbyhop` относится только к ipv6. Добавляется ipv6 extenstion header `hop-by-hop options`. В варианте `hopbyhop2` - добавляются 2 хедера, что является нарушением стандарта и гарантированно отбрасывается стеком протоколов во всех ОС. - Один хедер hop-by-hop принимается всеми ОС, однако на некоторых каналах/провайдерах такие пакеты могут фильтроваться и - не доходить. Расчет идет на то, что DPI проанализирует пакет с hop-by-hop, но он либо не дойдет до адресата в силу - фильтров провайдера, либо будет отброшен сервером, потому что хедера два. -* `datanoack` высылает фейки со снятым tcp флагом ACK. Сервера такое не принимают, а DPI может принять. Эта техника - может ломать NAT и не всегда работает с iptables, если используется masquerade, даже с локальной системы (почти всегда - на роутерах ipv4). На системах c iptables без masquerade и на nftables работает без ограничений. Экспериментально - выяснено, что многие провайдерские NAT не отбрасывают эти пакеты, потому работает даже с внутренним провайдерским IP. - Но linux NAT оно не пройдет, так что за домашним роутером эта техника скорее всего не сработает, но может сработать с него. - Может сработать и через роутер, если подключение по проводу, и на роутере включено аппаратное ускорение. -* Манипуляция tcp флагами с помощью `--dpi-desync-tcp-flags-set` и `--dpi-desync-tcp-flags-unset`. Можно сделать инвалидное - сочетание флагов, которое сервер не примет, а DPI - примет. Например, установить SYN в фейках. Но это может работать не на всех серверах. - `datanoack` может быть заменен `--dpi-desync-tcp-flags-unset=ACK`. - Пакеты с инвалидными флагами могут отбрасываться, проходя через NAT. -* `ts` прибавляет к значению TSval таймштампа tcp значение ts increment (по умолчанию -600000). Сервера отбрасывают пакеты - с TSval в определенных пределах. По практическим тестам инкремент должен быть где-то от -100 до -0x80000000. - timestamps генерирует клиентская ОС. В linux таймштампы включены по умолчанию, в windows выключены по умолчанию. - Можно включить через команду `netsh interface tcp set global timestamps=enabled`. - ts fooling требует, чтобы таймштампы были включены, иначе работать не будет. Включать надо на каждом клиентском устройстве. - TSecr оставляется без изменений. Так же требуется, чтобы сервер понимал timestamps, но это в большинстве случаев так. -* `autottl`. Суть режима в автоматическом определении TTL, чтобы пакет почти наверняка прошел DPI и немного не дошел до - сервера (`--dpi-desync-autottl`). Или наоборот - TTL едва хватило, чтобы он все-таки дошел до сервера (см `--dup-autottl`, `--orig-autottl`). - Берутся базовые значения TTL 64,128,255, смотрится входящий пакет (да, требуется направить первый входящий пакет на nfqws !). - Вычисляется длина пути, прибавляется `delta`. delta может быть положительной или отрицательной. - Чтобы задать положительную дельту, нужно указать унарный знак **+** перед числом. - В случае его отсутствия или при наличии унарного знака **-** дельта считается отрицательной. - Если TTL вне диапазона min,max, то берутся значения min,max, чтобы вписаться в - диапазон. Если при этом дельта отрицательная и полученный TTL больше длины пути или дельта положительная и полученный TTL меньше длины пути, - то автоматизм не сработал и берутся фиксированные значения : `--dpi-desync-ttl`, `--orig-ttl`, `--dup-ttl`. - Техника позволяет решить вопрос, когда вся сеть перегорожена шлагбаумами (DPI, ТСПУ) везде где только - можно, включая магистралов. Но потенциально может давать сбои. Например, при асимметрии входящего и исходящего канала - до конкретного сервера. Некоторые сервера выдают нестандартный TTL (google), потому на них получается полная ерунда. - Если не учитывать подобные исключения, то на каких-то провайдерах эта техника будет работать неплохо, на других доставит больше проблем, - чем пользы. Где-то может потребоваться тюнинг параметров. Лучше использовать с дополнительным ограничителем. - -Режимы дурения могут сочетаться в любых комбинациях. `--dpi-desync-fooling` берет множество значений через запятую. - -Возможно задание множества фейков через повторение параметров `--dpi-desync-fake-???`, кроме `--dpi-desync-fake-syndata`. -Фейки будут отосланы в указанном порядке. `--dpi-desync-repeats` повторяет каждый отосланный фейк. -Итоговый порядок будет такой : `fake1 fake1 fake1 fake2 fake2 fake2 fake3 fake3 fake3 .....` - -### МОДИФИКАЦИЯ ФЕЙКОВ - -Любые tcp фейки отправляются с исходным sequence по умолчанию, даже если их несколько. -Если задать `--dpi-desync-fake-tcp-mod=seq`, то несколько фейков будут отправлены с увеличением sequence number таким образом, -как будто они являются tcp сегментами одного фейка. - -В nfqws зашит базовый вариант фейка для TLS. Его можно переопределить опцией `--dpi-desync-fake-tls`. -Переопределение фейков дает возможность использовать любые данные в качестве фейка для TLS. -Можно использовать фейковый Client Hello с любым фингерпринтом и с любым SNI. - -Некоторые модификации можно делать в процессе выполнения с помощью `--dpi-desync-fake-tls-mod`. -Часть из них работает при обработке каждого TLS Client Hello и может подстраиваться под отправляемые данные. -Модификации требуют наличия полного валидного TLS Client Hello в качестве фейка, они не работают с произвольными данными. - - * `none`. Не применять никакие модификации. - * `rnd`. Рандомизировать поля `random` и `session id`. Выполняется на каждый запрос. - * `dupsid`. Копировать `session ID` из передаваемого TLS Client Hello. Имеет приоритет над `rnd`. Выполняется на каждый запрос. - * `rndsni`. Рандомизировать SNI. Если SNI >=7 символов, применяется случайный домен 2 уровня с известным TLD, иначе заполняется случайными символами без точки. Выполняется один раз при старте. - * `sni=`. Заменить sni на указанное значение. Макс длина SNI - 63 байта. Общая длина TLS фейка и длины в структуре TLS Client Hello меняются. Выполняется один раз при старте. Если сочетается с `rndsni`, выполняется до него. - * `padencap`. Расширяется padding extension на размер передаваемого TLS Client Hello (включая многопакетный вариант с kyber). Если padding отсутствует, он добавляется в конец. Если присутствует - требуется, чтобы padding шел последним extension. Правятся все длины, чтобы создать видимость включения передаваемого TLS Client Hello в padding extension. Размер фейка не изменяется. Расчет идет на DPI, который не анализирует sequence numbers должным образом. Выполняется на каждый запрос. - -По умолчанию если не задан собственный фейк для TLS используются модификации `rnd,rndsni,dupsid`. Если фейк задан, используется `none`. -Это соответствует поведению программы более старых версий с добавлением функции `dupsid`. - -Если задан режим модификации и имеется множество TLS фейков, к каждому из них применяется последний режим модификации. -Если режим модификации задан после фейка, то он замещает предыдущий режим. -Таким образом можно использовать разные режимы модификации для разных фейков. -При невозможности модифицировать фейк на этапе запуска программа завершается с ошибкой. - -Если сначала идет TLS фейк, для него задан режим однократной модификации, затем идет не TLS фейк, то будет ошибка. -Нужно использовать `--dpi-desync-fake-tls-mod=none'. - -Пример : `--dpi-desync-fake-tls=iana_org.bin --dpi-desync-fake-tls-mod=rndsni --dpi-desync-fake-tls=0xaabbccdd --dpi-desync-fake-tls-mod=none' - -### TCP СЕГМЕНТАЦИЯ - - * `multisplit`. нарезаем запрос на указанных в `--dpi-desync-split-pos` позициях. - * `multidisorder`. нарезаем запрос на указанных в `--dpi-desync-split-pos` позициях и отправляем в обратном порядке. - * `fakedsplit`. различные варианты замешивания фейков и оригиналов в прямом порядке - * `fakeddisorder`. различные варианты замешивания фейков и оригиналов в обратном порядке - * `hostfakesplit` (altorder=0). фейкование части запроса с хостом : оригинал до хоста, фейк хоста, оригинал хоста (+ опционально нарезка маркером midhost), фейк хоста, оригинал после хоста - * `hostfakesplit` (altorder=1). фейкование части запроса с хостом : оригинал до хоста, фейк хоста, оригинал после хоста, оригинал хоста (+опционально нарезка маркером midhost) - * `fakeddisorder`. аналогично `fakedsplit`, только в обратном порядке : фейк 2-й части, 2 часть, фейк 2-й части, фейк 1-й части, 1 часть, фейк 1 части. - -Для `fakedsplit` и `fakeddisorder` предусмотрены вариации порядка следования сегментов. -Параметр `--dpi-desync-fakedsplit-mod=altorder=N` задает число, влияющее на наличие отдельных фейков : - -Режимы altorder для `fakedsplit` для части многопакетного запроса, где есть сплит-позиция : - * `altorder=0`. фейк 1-й части, 1 часть, фейк 1-й части, фейк 2-й части, 2 часть, фейк 2-й части - * `altorder=1`. 1 часть, фейк 1-й части, фейк 2-й части, 2 часть, фейк 2-й части - * `altorder=2`. 1 часть, фейк 2-й части, 2 часть, фейк 2-й части - * `altorder=3`. 1 часть, фейк 2-й части, 2 часть - -Режимы altorder для `fakeddisorder` для части многопакетного запроса, где есть сплит-позиция : - * `altorder=0`. фейк 2-й части, 2 часть, фейк 2-й части, фейк 1-й части, 1 часть, фейк 1-й части - * `altorder=1`. 2 часть, фейк 2-й части, фейк 1-й части, 1 часть, фейк 1-й части - * `altorder=2`. 2 часть, фейк 1-й части, 1 часть, фейк 1-й части - * `altorder=3`. 2 часть, фейк 1-й части, 1 часть - -Режимы altorder для `fakedsplit` и `fakeddisorder` для части многопакетного запроса, где нет сплит-позиции : - * `altorder=0`. фейк, оригинал, фейк - * `altorder=8`. оригинал, фейк - * `altorder=16`. оригинал - -Итоговое число `altorder=N` вычисляется как сумма чисел из этих двух групп. По умолчанию `altorder=0`. - -Содержимое фейков в `fakedsplit`/`fakeddisorder` определяется параметром `--dpi-desync-fakedsplit-pattern` (по умолчанию 0x00). -Данные фейков берутся из паттерна со смещением, соответствующим смещению отсылаемых частей, учитывая смещения пакетов в многопакетных запросах. -Размеры фейков соответствуют длинам отсылаемых частей. -Цель этих режимов - максимально усложнить выявление оригинальных данных среди фейков. - -Использование `fakedsplit` или `fakeddisorder` на TLS kyber с md5sig fooling может привести к ошибкам "message too long", если позиция сплита мала, -поскольку будет превышение MTU из-за md5 tcp option. - -Режим 'hostfakesplit' имеет задачу минимального вмешательства фейком - как раз по той части запроса, на основании которой DPI принимает решение о блокировке. Конкретно - имени хоста. -По умолчанию фейк хоста генерируется каждый раз случайно из набора `[a-z0-9]`. При длине более 7 символов за 3 символа до конца ставится точка, имитируя TLD, а последние 3 символа заполняются одним из нескольких известных TLD. - -Можно переопределить шаблон генерации с помощью `--dpi-desync-hostfakesplit-mod=host=`. В последнем случае справа всегда будет указанный hostname. -Слева он будет дополнен до размера оригинального хоста как поддомен со случайными символами. Пример : "www.networksolutions.com" -> "h8xmdba4tv7a8.google.com". -Если размер оригинального хоста меньше шаблона, шаблон будет порезан : "habr.com" -> "ogle.com". -Если размер оригинального хоста больше шаблона на 1, получится инвалидный пустой поддомен : "www.xxx.com" => ".google.com". -Поэтому стоит использовать максимально короткие хосты из разрешенных : "ya.ru", "vk.com". - -`--dpi-desync-hostfakesplit-mod=altorder=1` позволяет сменить порядок следования частей на альтернативный вариант. -`altorder=1` шлет фрагменты в таком порядке, чтобы при последовательной сборке сегментов на DPI он получил полностью собранный оригинал запроса с подмененным хостом. -Реальный хост идет отдельным сегментом уже после. То есть в этом варианте применяется разновидность disorder. Сервер принимает фрагменты с нарушенным порядком sequence. - -Опционально можно разрезать оригинальный хост. Например, `--dpi-desync-hostfakesplit-midhost=midsld`. Позиция нарезки должна попадать внутрь хоста. -Многопакетные запросы поддерживаются только, если исходная нарезка пакетов не включает позиции имени хоста. В последнем случае дурение отменяется. - -Вариант `fakedsplit` имеет несколько альтернативных порядков нарезки - от 0 до 3. Режим задается в параметре `--dpi-desync-fakedsplit-mod=altorder=N`. -Каждый следующий altorder убирает часть фейков. - -Для определения позиций нарезки используются маркеры. - -* **Абсолютный положительный маркер** - числовое смещение внутри пакета или группы пакетов от начала. -* **Абсолютный отрицательный маркер** - числовое смещение внутри пакета или группы пакетов от следующего за концом байта. -1 указывает на последний байт. -* **Относительный маркер** - положительное или отрицательное смещение относительно логической позиции внутри пакета или группы пакетов. - -Относительные позиции : - -* **method** - начало метода HTTP ('GET', 'POST', 'HEAD', ...). Метод обычно всегда находится на позиции 0, но может сместиться из-за `--methodeol`. Тогда позиция может стать 1 или 2. -* **host** - начало имени хоста в известном протоколе (http, TLS) -* **endhost** - байт, следующий за последним байтом имени хоста -* **sld** - начало домена 2 уровня в имени хоста -* **endsld** - байт, следующий за последним байтом домена 2 уровня в имени хоста -* **midsld** - середина домена 2 уровня в имени хоста -* **sniext** - начало поля данных SNI extension в TLS. Любой extension состоит из 2-байтовых полей type и length, за ними идет поле данных. - -Пример списка маркеров : `100,midsld,sniext+1,endhost-2,-10`. - -При разбиении пакета первым делом происходит ресолвинг маркеров - нахождение всех указанных относительных позиций и применение смещений. -Если относительная позиция отсутствует в текущем протоколе, такие позиции не применяются и отбрасываются. -Дальше происходит нормализация позиций относительно смещения текущего пакета в группе пакетов (многопакетные запросы TLS с kyber, например). -Выкидываются все позиции, выходящие за пределы текущего пакета. Оставшиеся сортируются в порядке возрастания и удаляются дубли. -В вариантах `multisplit` и `multidisorder` если не осталось ни одной позиции, разбиение не происходит. - -Варианты `fakedsplit` и `fakeddisorder` применяют только одну позицию сплита. Ее поиск среди списка `--dpi-desync-split-pos` осуществляется особым образом. -Сначала сверяются все относительные маркеры. Если среди них найден подходящий, применяется он. В противном случае сверяются все абсолютные маркеры. -Если и среди них ничего не найдено, применяется позиция 1. - -Например, можно написать `--dpi-desync-split-pos=method+2,midsld,5`. Если протокол http, разбиение будет на позиции `method+2`. -Если протокол TLS - на позиции `midsld`. Если протокол неизвестен и включено `--dpi-desync-any-protocol`, разбиение будет на позиции 5. -Чтобы все было однозначнее, можно использовать разные профили для разных протоколов и указывать только одну позицию, которая точно есть в этом протоколе. - -### ПЕРЕКРЫТИЕ SEQUENCE NUMBERS - -`seqovl` добавляет в начало одного из TCP сегментов `seqovl` байт со смещенным в минус sequence number на величину `seqovl`. -Для `split` - в начало первого сегмента, для `disorder` - в начало предпоследнего отсылаемого сегмента (второго в оригинальном порядке следования). - -В случае `split` расчет идет на то, что предыдущий отсыл, если он был, уже попал в сокет серверного приложения, поэтому новая пришедшая часть лишь частично находится в -пределах текущего окна (in-window). Спереди фейковая часть отбрасывается, а оставшаяся часть содержит оригинал и -начинается с начала window, поэтому попадает в сокет. Серверное приложение получает все, что реально отсылает клиент, -отбрасывая фейковую out-of-window часть. Но DPI не может этого понять, поэтому у него происходит sequence десинхронизация. -Обязательно, чтобы первый сегмент вместе с `seqovl` не превысили длину MTU. Эта ситуация распознается автоматически в Linux, и `seqovl` отменяется. -В остальных системах ситуация не распознается, и это приведет к поломке соединения. Поэтому выбирайте первую позицию сплита и `seqovl` таким образом, чтобы MTU не был превышен в любом случае. -Иначе дурение может не работать или работать хаотично. - -Для `disorder` overlap идет на предпоследнюю отсылаемую часть пакета. -Для простоты будем считать, что разбиение идет на 2 части, шлются они в порядке "2 1" при оригинальном порядке "1 2". -Обязательно, чтобы `seqovl` был меньше позиции первого сплита, иначе все отосланное будет передано в сокет сразу же, включая фейк, ломая протокол прикладного уровня. -Такая ситуация легко обнаруживается программой, и `seqovl` отменяется. Увеличение размера пакета невозможно в принципе. -При соблюдении условия 2-я часть пакета является полностью in-window, поэтому серверная ОС принимает ее целиком, включая фейк. -Но поскольку начальная часть данных из 1 пакета еще не принята, то фейк и реальные данные остаются в памяти ядра, не отправляясь в серверное приложение. -Как только приходит 1-я часть пакета, она переписывает фейковую часть в памяти ядра. -Ядро получает данные из 1 и 2 части, поэтому далее идет отправка в сокет приложения. -Таково поведение всех unix ОС, кроме solaris - оставлять последние принятые данные. -Windows оставляет старые данные, поэтому disorder с seqovl будет приводить к зависаниям соединения -при работе с Windows серверами. Solaris практически мертв, windows серверов очень немного. -Можно использовать листы при необходимости. -Метод позволяет обойтись без fooling и TTL. Фейки перемешаны с реальным данными. -`fakedsplit/fakeddisorder` по-прежнему добавляют дополнительные отдельные фейки. - -`seqovl` в варианте `split` может быть только абсолютным положительным значением, поскольку применяется только в первому пакету. -В варианте `disorder` допустимо применение всех вариантов маркеров. -Они автоматически нормализуются к текущему пакету в серии. Можно сплитать на `midsld` и делать seqovl на `midsld-1`. - -### НАЗНАЧЕНИЕ IP_ID - -Некоторые DPI секут поле ipv4 заголовка ip_id. Защита заключается в распознавании нехарактерного для разных ОС порядка назначения ip_id, -но характерного для некоторого anti-DPI софта. Обычно ОС инкрементируют ip_id для каждого следующего пакета. -Например, на ТСПУ повторение ненулевых ip_id фейка и не фейка вызывает триггер блока на диапазонах IP `googlevideo.com`. - -Если отсылаются фейки или дополнительные tcp сегменты, то в любом случае последовательность будет нарушена, поскольку ОС ничего не будет знать о всунутых фейках -и не увеличит свой счетчик ip_id на количество фейков или дополнительных tcp сегментов. -Чтобы сохранить последовательность, потребовалось бы перехватывать все соединение до конца, что очень затратно по ресурсам. -Поэтому после отработки серии генерированных пакетов ip_id возвращается к тому значению, о котором знает ОС. - -Параметр `ip-id` относится к профилю и задает режим назначения ip_id при отсылке генерированных в nfqws пакетов. - - * `seq` (по умолчанию) : взять последний ip_id реального пакета. последующие генерированыне пакеты получают увеличенные на 1 ip_id, кроме случая `multidisorder`. - для `multidisorder` в пределах сегментов, где есть сплит-позиции, значение ip_id увеличивается на количество частей, затем уменьшается на 1 с каждой отосланной частью. - * `seqgroup` : то же, что и `seq`, но фейки того же размера, что и оригинальные сегменты, маскирующиеся под оригинал получают те же ip_id. - * `rnd` : всем генерированным пакетам назначать случайный ip_id - * `zero` : всем генерированным пакетам назначать ip_id=0 . в этом случае Linux и BSD отошлют 0, Windows назначит последовательные ip_id всем пакетам (тем самым автоматически решается проблема сбоя счетчика пакетов). - -В заголовках ipv6 поле ip_id отсутствует, параметр игнорируется для ipv6. - -### СПЕЦИФИЧЕСКИЕ РЕЖИМЫ IPV6 - -Режимы десинхронизации `hopbyhop`, `destopt` и `ipfrag1` (не путать с fooling !) относятся только к ipv6 и заключается -в добавлении хедера `hop-by-hop options`, `destination options` или `fragment` во все пакеты, попадающие под десинхронизацию. -Здесь надо обязательно понимать, что добавление хедера увеличивает размер пакета, потому не может быть применено -к пакетам максимального размера. Это имеет место при передаче больших сообщений. -В случае невозможности отослать пакет дурение будет отменено, пакет будет выслан в оригинале. -Расчет идет на то, что DPI увидит 0 в поле next header основного заголовка `ipv6` и не будет скакать по -extension хедерам в поисках транспортного хедера. Таким образом не поймет, что это tcp или udp, и пропустит пакет -без анализа. Возможно, какие-то DPI на это купятся. -Может сочетаться с любыми режимами 2-й фазы, кроме варианта `ipfrag1+ipfrag2`. -Например, `hopbyhop,multisplit` означает разбить tcp пакет на несколько сегментов, в каждый из них добавить hop-by-hop. -При `hopbyhop,ipfrag2` последовательность хедеров будет : `ipv6,hop-by-hop`,`fragment`,`tcp/udp`. -Режим `ipfrag1` может срабатывать не всегда без специальной подготовки. См. раздел `IP фрагментация`. - -### МОДИФИКАЦИЯ ОРИГИНАЛА - -Параметры `--orig-ttl` и `--orig-ttl6` позволяют изменить TTL оригинальных пакетов. -Если дальнейшие манипуляции связаны с оригиналом, например, идет TCP сегментация, то исходными -данными являются измененные оригинальные пакеты. То есть в данном примере TCP сегменты пойдут с измененным TTL. - -Вариант `--orig-autottl` и `--orig-autottl6` работает аналогично `dpi-desync-autottl`, но по оригинальным пакетам. -Дельту стоит указывать положительную с унарным знаком `+`, иначе оригинал не дойдет до сервера, и вы вообще ничего не получите. -Пример : `--orig-autottl=+5:3-64`. - -`--orig-mod-start` и `--orig-mod-cutoff` задают ограничитель по началу и концу модификации оригинала. -Схема аналогична `--dpi-desync-start` и `--dpi-desync-cutoff`. - -Функция может быть полезна, когда DPI охотится за фейками и блокирует соединение при наличии подозрительных признаков, -в частности, измененный TTL у фейка относительно оригинала. - -### ДУБЛИКАТЫ - -Дубликаты - это копии оригинальных пакетов, высылаемые перед ними. Включаются параметром `--dup=N`, где N - количество дублей, -не включающее оригинал. `--dup-replace` отключает отсылку оригинала. - -Отсылка дублей имеет место только в тех случаях, когда высылается и оригинал без реконструкции. -Например, если случилась TCP сегментация, то оригинал фактически дропается и заменяется искусственно сконструированными сегментами. -Дубли высланы не будут. Это же касается изменения состава хедеров ipv6, режима tamper для DHT и других. - -Возможно применение всех вариантов дурения, как и для desync : `--dup-ttl`. `--dup-ttl6`, `--dup-fooling`. Нужно ли, чтобы эти пакеты доходили до сервера и в каком виде, решаете вы согласно задуманной стратегии. - -Вариант `--dup-autottl` и `--dup-autottl6` работает аналогично `dpi-desync-autottl`, но по дублям. -Дельту можно указывать положительную с унарным знаком `+`, а можно и отрицательную. Зависит от вашей задумки. -Пример : `--dup-autottl=-2:3-64`. - -`--dup-start` и `--dup-cutoff` задают ограничитель по началу и концу применения стратегии дубликатов. -Схема аналогична `--dpi-desync-start` и `--dpi-desync-cutoff`. - -Функция может помочь, когда DPI сечет разницу в характеристиках фейков и оригинала. -Дубликатами можно попытаться заставить DPI принять , что весь сеанс идет аномальным. -Например, у нас имеется TCP сеанс с MD5 сразу с первого SYN пакета. Значит последующие MD5 будут восприниматься нормально. - -### КОМБИНИРОВАНИЕ МЕТОДОВ ДЕСИНХРОНИЗАЦИИ - -В параметре dpi-desync можно указать до 3 режимов через запятую. - -* 0 фаза - предполагает работу на этапе установления соединения : `synack`, `syndata`, `--wsize`, `--wssize`. На эту фазу не действуют фильтры по [hostlist](#множественные-стратегии), кроме случая, описанного [далее](#кэш-ip). -* 1 фаза - отсылка чего-либо до оригинального пакета данных : `fake`, `rst`, `rstack`. -* 2 фаза - отсылка в модифицированном виде оригинального пакета данных (например, `fakedsplit` или `ipfrag2`). - -Режимы требуют указания в порядке возрастания номеров фаз. - -### КЭШ IP - -ipcache представляет собой структуру в памяти процесса, позволяющую по ключу IP адреса и имени интерфейса запоминать некоторую информацию, -которую впоследствии можно извлечь и использовать как недостающие данные. На текущий момент это применяются в следующих ситуациях : - -1. IP,interface => hop count . Кэшируется количество хопов до сервера для последующего применения в autottl прямо с первого пакета, когда еще ответа не было. Пока записи в кэше нет, autottl не будет применен сразу. При повторном запросе до истечения времени жизни записи autottl будет применение сразу. - -2. IP => hostname . Кэшируется имя хоста, вне привязки к интерфейсу, для последующего применения в стратегиях нулевой фазы. Режим отключен по умолчанию и включается через параметры `ipcache-hostname`. -Данная техника является экспериментальной. Ее проблема в том, что как такового нет однозначного соответствия между доменом и IP. Множество доменов могут ссылаться на тот же IP адрес. -При коллизии происходит замещение имени хоста на последний вариант. -Домен может скакать по разным IP на CDN. Сейчас один адрес, через час - другой. Эта проблема решается через время жизни записей кэша : `--ipcache-lifetime`. По умолчанию 2 часа. -Однако, может случиться и так, что в вашем случае применение техники несет больше пользы, чем проблем. Будьте готовы к непонятному на первый взгляд поведению, которое может быть исследовано только через `--debug` лог. - -При подаче сигнала SIGUSR2 процесс выводит содержимое ipcache на консоль. - -### РЕАКЦИЯ DPI НА ОТВЕТ СЕРВЕРА - -Есть DPI, которые анализируют ответы от сервера, в частности сертификат из ServerHello, где прописаны домены. -Подтверждением доставки ClientHello является ACK пакет от сервера с номером ACK sequence, соответствующим длине ClientHello+1. -В варианте disorder обычно приходит сперва частичное подтверждение (SACK), потом полный ACK. -Если вместо ACK или SACK идет RST пакет с минимальной задержкой, то DPI вас отсекает еще на этапе вашего запроса. -Если RST идет после полного ACK спустя задержку, равную примерно пингу до сервера, -тогда вероятно DPI реагирует на ответ сервера. -DPI может отстать от потока, если ClientHello его удовлетворил и не проверять ServerHello. -Тогда вам повезло. Вариант fake может сработать. -Если же он не отстает и упорно проверяет ServerHello, то можно попробовать заставить сервер высылать ServerHello частями -через параметр `--wssize` (см. conntrack). -Если и это не помогает, то сделать с этим что-либо вряд ли возможно без помощи со стороны сервера. -Лучшее решение - включить на сервере поддержку TLS 1.3. В нем сертификат сервера передается в зашифрованном виде. -Это рекомендация ко всем админам блокируемых сайтов. Включайте TLS 1.3. Так вы дадите больше возможностей преодолеть DPI. - -### РЕЖИМ SYNACK - -В документации по geneva это называется "TCB turnaround". Попытка ввести DPI в заблуждение относительно -ролей клиента и сервера. - -Поскольку режим нарушает работу NAT, техника может сработать только если между атакующим устройством -и DPI нет NAT. Атака не сработает через NAT роутер, но может сработать с него. -Для реализации атаки на проходящий трафик требуются nftables и схема [POSTNAT](#nftables-для-nfqws). - -### РЕЖИМ SYNDATA - -Тут все просто. Добавляются данные в пакет SYN. Все ОС их игнорируют, если не используется TCP fast open (TFO), -а DPI может воспринять, не разобравшись есть там TFO или нет. -Оригинальные соединения с TFO не трогаются, поскольку это их точно сломает. -Без уточняющего параметра добавляются 16 нулевых байтов. - -### ВИРТУАЛЬНЫЕ МАШИНЫ - -Изнутри VM от virtualbox и vmware в режиме NAT не работают многие техники пакетной магии nfqws. -Принудительно заменяется ttl, не проходят фейк пакеты. Необходимо настроить сеть в режиме bridge. - -### CONNTRACK - -nfqws оснащен ограниченной реализацией слежения за состоянием tcp соединений (conntrack). -Он включается для реализации некоторых методов противодействия DPI. -conntrack способен следить за фазой соединения : SYN,ESTABLISHED,FIN, количеством пакетов в каждую сторону, -sequence numbers. conntrack способен "кормиться" пакетами в обе или только в одну сторону. -Соединение попадает в таблицу при обнаружении пакетов с выставленными флагами SYN или SYN,ACK. -Поэтому если необходим conntrack, в правилах перенаправления iptables соединение должно идти на nfqws с самого первого -пакета, хотя затем может обрываться по фильтру connbytes. -Для UDP инициатором попадания в таблицу является первый UDP пакет. Он же и определяет направление потока. -Считается, что первый UDP пакет исходит от клиента к серверу. Далее все пакеты с совпадающими -`src_ip,src_port,dst_ip,dst_port` считаются принадлежащими этому потоку до истечения времени неактивности. -conntrack - простенький, он не писался с учетом всевозможных атак на соединение, он не проверяет -пакеты на валидность sequence numbers или чексумму. Его задача - лишь обслуживание нужд nfqws, он обычно -кормится только исходящим трафиком, потому нечувствителен к подменам со стороны внешней сети. -Соединение удаляется из таблицы, как только отпадает нужда в слежении за ним или по таймауту неактивности. -Существуют отдельные таймауты на каждую фазу соединения. Они могут быть изменены параметром `--ctrack-timeouts`. - -`--wssize` позволяет изменить с клиента размер tcp window для сервера, чтобы он послал следующие ответы разбитыми на части. -Чтобы это подействовало на все серверные ОС, необходимо менять window size в каждом исходящем с клиента пакете до отсылки сообщения, -ответ на которое должен быть разбит (например, TLS ClientHello). Именно поэтому и необходим conntrack, чтобы -знать когда надо остановиться. Если не остановиться и все время устанавливать низкий wssize, скорость упадет катастрофически. -В linux это может быть купировано через connbytes, но в BSD системах такой возможности нет. -В случае http(s) останавливаемся сразу после отсылки первого http запроса или TLS ClientHello. -Если вы имеете дело с не http(s), то вам потребуется параметр `--wssize-cutoff`. Он устанавливает предел, с которого действие -wssize прекращается. Префикс d перед номером означает учитывать только пакеты с data payload, префикс s - relative sequence number, -проще говоря количество переданных клиентом байтов + 1. -Если проскочит пакет с http request или TLS ClientHello, действие wssize прекращается сразу же, не дожидаясь wssize-cutoff, -если не указан параметр `--wssize-forced-cutoff=0`. -Если ваш протокол склонен к долгому бездействию, следует увеличить таймаут фазы ESTABLISHED через параметр `--ctrack-timeouts`. -Таймаут по умолчанию низкий - всего 5 минут. -Не забывайте, что nfqws кормится приходящими на него пакетами. Если вы ограничили поступление пакетов через connbytes, -то в таблице могут остаться повисшие соединения в фазе ESTABLISHED, которые отвалятся только по таймауту. -Для диагностики состояния conntrack пошлите сигнал SIGUSR1 процессу nfqws : `killall -SIGUSR1 nfqws`. -Текущая таблица будет выведена nfqws в stdout. - -Обычно в SYN пакете клиент отсылает кроме window size еще и TCP extension `scaling factor`. -**scaling factor** представляет из себя степень двойки, на которую умножается window size : 0=>1, 1=>2, 2=>4, ..., 8=>256, ... -В параметре wssize scaling factor указывается через двоеточие. -Scaling factor может только снижаться, увеличение заблокировано, чтобы не допустить превышение размера окна со стороны сервера. -Для принуждения сервера к фрагментации ServerHello, чтобы избежать просекание имени сервера из сертификата сервера на DPI, -лучше всего использовать `--wssize=1:6`. Основное правило - делать `scale_factor` как можно больше, чтобы после восстановления -window size итоговый размер окна стал максимально возможным. Если вы сделаете 64:0, будет очень медленно. -С другой стороны нельзя допустить, чтобы ответ сервера стал достаточно большим, чтобы DPI нашел там искомое. - -`--wssize` не работает в профилях с хостлистами, поскольку он действует с самого начала соединения, когда еще нельзя -принять решение о попадании в лист. Однако, профиль с auto hostlist может содержать --wssize. -`--wssize` может замедлять скорость и/или увеличивать время ответа сайтов, поэтому если есть другие работающие способы -обхода DPI, лучше применять их. - -`--dpi-desync-cutoff` позволяет задать предел, при достижении которого прекращается применение dpi-desync. -Доступны префиксы n,d,s по аналогии с `--wssize-cutoff`. -Полезно совместно с `--dpi-desync-any-protocol=1`. -На склонных к бездействию соединениях следует изменить таймауты conntrack. -Если соединение выпало из conntrack и задана опция `--dpi-desync-cutoff`, `dpi desync` применяться не будет. - -### РЕАССЕМБЛИНГ - -nfqws поддерживает реассемблинг некоторых видов запросов. -На текущий момент это TLS и QUIC ClientHello. Они бывают длинными, если в chrome включить пост-квантовую -криптографию tls-kyber, и занимают, как правило, 2 или 3 пакета. kyber включен по умолчанию, начиная с chromium 124. -chrome рандомизирует фингерпринт TLS. SNI может оказаться как в начале, так и в конце, то есть -попасть в любой пакет. stateful DPI обычно реассемблирует запрос целиком, и только потом -принимает решение о блокировке. -В случае получения TLS или QUIC пакета с частичным ClientHello начинается процесс сборки, а пакеты -задерживаются и не отсылаются до ее окончания. По окончании сборки пакеты проходит через десинхронизацию -на основании полностью собранного ClientHello. -При любой ошибке в процессе сборки задержанные пакеты немедленно отсылаются в сеть, а десинхронизация отменяется. - -Есть специальная поддержка всех вариантов tcp сплита для многосегментного TLS. -Если указать позицию сплита больше длины первого пакета, то разбивка происходит не обязательно первого пакета, а того, -на который пришлась итоговая позиция. -Если, допустим, клиент послал TLS ClientHello длиной 2000, SNI начинается с 1700, -и заданы опции `fake,multisplit`, то перед первым пакетом идет fake, затем первый пакет в оригинале, -а последний пакет разбивается на 2 сегмента. В итоге имеем фейк в начале и 3 реальных сегмента. - -### ПОДДЕРЖКА UDP - -Атаки на udp более ограничены в возможностях. udp нельзя фрагментировать иначе, чем на уровне ip. - -Для UDP действуют только режимы десинхронизации `fake`, `fakeknown`, `hopbyhop`, `destopt`, `ipfrag1`, `ipfrag2`, `udplen`, `tamper`. -Режимами первой фазы являются `fake`, `fakeknown`, `hopbyhop`, `destopt`, `ipfrag1`. Второй фазы - `ipfrag2`, `udplen`, `tamper`. -Как обычно, возможно сочетание режимов первой и второй фазы, но не двух режимов одной фазы. - -`udplen` увеличивает размер udp пакета на указанное в `--dpi-desync-udplen-increment` количество байтов. -Паддинг заполняется нулями по умолчанию, но можно задать свой паттерн. -Предназначено для обмана DPI, ориентирующегося на размеры пакетов. -Может сработать, если пользовательский протокол не привязан жестко к размеру udp пейлоада. -Режим tamper означает модификацию пакетов известных протоколов особенным для протокола образом. -На текущий момент работает только с DHT. -Поддерживается определение пакетов QUIC Initial с расшифровкой содержимого и имени хоста, то есть параметр -`--hostlist` будет работать. -Определяются пакеты wireguard handshake initiation, DHT (начинается с 'd1', кончается 'e'), STUN и -[Discord Voice IP Discovery](https://discord.com/developers/docs/topics/voice-connections#ip-discovery). -Для десинхронизации других протоколов обязательно указывать `--dpi-desync-any-protocol`. -Реализован conntrack для udp. Можно пользоваться --dpi-desync-cutoff. Таймаут conntrack для udp -можно изменить 4-м параметром в `--ctrack-timeouts`. -Атака fake полезна только для stateful DPI, она бесполезна для анализа на уровне отдельных пакетов. -По умолчанию fake наполнение - 64 нуля. Можно указать файл в `--dpi-desync-fake-unknown-udp`. - -### IP ФРАГМЕНТАЦИЯ - -Современная сеть практически не пропускает фрагментированные tcp на уровне ip. -На udp с этим дело получше, поскольку некоторые udp протоколы могут опираться на этот механизм (IKE старых версий). -Однако, кое-где бывает, что режут и фрагментированный udp. -Роутеры на базе linux могут самопроизвольно собирать или перефрагментировать пакеты. -Позиция фрагментации задается отдельно для tcp и udp. По умолчанию 24 и 8 соответственно, должна быть кратна 8. -Смещение считается с транспортного заголовка. - -Существует ряд моментов вокруг работы с фрагментами на Linux, без понимания которых может ничего не получиться. - -ipv4 : Linux дает отсылать ipv4 фрагменты, но стандартные настройки iptables в цепочке OUTPUT могут вызывать ошибки отправки. - -ipv6 : Нет способа для приложения гарантированно отослать фрагменты без дефрагментации в conntrack. -На разных системах получается по-разному. Где-то нормально уходят, где-то пакеты дефрагментируются. -Для ядер <4.16 похоже, что нет иного способа решить эту проблему, кроме как выгрузить модуль `nf_conntrack`, -который подтягивает зависимость `nf_defrag_ipv6`. Он то как раз и выполняет дефрагментацию. -Для ядер 4.16+ ситуация чуть лучше. Из дефрагментации исключаются пакеты в состоянии NOTRACK. -Чтобы не загромождать описание, смотрите пример решения этой проблемы в `blockcheck.sh`. - -Иногда требуется подгружать модуль `ip6table_raw` с параметром `raw_before_defrag=1`. -В OpenWrt параметры модулей указываются через пробел после их названий в файлах `/etc/modules.d`. -В традиционных системах посмотрите используется ли `iptables-legacy` или `iptables-nft`. Если legacy, то нужно создать файл -`/etc/modprobe.d/ip6table_raw.conf` с содержимым : -``` -options ip6table_raw raw_before_defrag=1 -``` -В некоторых традиционных дистрибутивах можно изменить текущий ip6tables через : update-alternatives --config ip6tables -Если вы хотите оставаться на iptables-nft, вам придется пересобрать патченную версию. Патч совсем небольшой. -В `nft.c` найдите фрагмент: -``` - { - .name = "PREROUTING", - .type = "filter", - .prio = -300, /* NF_IP_PRI_RAW */ - .hook = NF_INET_PRE_ROUTING, - }, - { - .name = "OUTPUT", - .type = "filter", - .prio = -300, /* NF_IP_PRI_RAW */ - .hook = NF_INET_LOCAL_OUT, - }, -``` -и замените везде -300 на -450. - -Это нужно сделать вручную, никакой автоматики в `blockcheck.sh` нет. - -Либо можно раз и навсегда избавиться от этой проблемы, используя `nftables`. Там можно создать `netfilter hook` -с любым приоритетом. Используйте приоритет -401 и ниже. - -При использовании iptables и NAT, похоже, что нет способа прицепить обработчик очереди после NAT. -Пакет попадает в nfqws с source адресом внутренней сети, затем фрагментируется и уже не обрабатывается NAT. -Так и уходит во внешнюю сеть с src ip 192.168.x.x. Следовательно, метод не срабатывает. -Видимо единственный рабочий метод - отказаться от iptables и использовать nftables. -Хук должен быть с приоритетом 101 или выше. - -### МНОЖЕСТВЕННЫЕ СТРАТЕГИИ - -**nfqws** способен по-разному реагировать на различные запросы и применять разные стратегии дурения. -Это реализовано посредством поддержки множества профилей дурения. -Профили разделяются в командной строке параметром `--new`. Первый профиль создается автоматически. -Для него не нужно `--new`. Каждый профиль имеет фильтр. По умолчанию он пуст, то есть профиль удовлетворяет -любым условиям. -Фильтр может содержать жесткие параметры: версия ip протокола, ipset и порты tcp/udp. -Они всегда однозначно идентифицируются даже на нулевой фазе десинхронизации, когда еще хост и L7 неизвестны. -В качестве мягкого фильтра могут выступать хост-листы и протокол прикладного уровня (l7). -L7 протокол становится известен обычно после первого пакета с данными. -При поступлении запроса идет проверка профилей в порядке от первого до последнего до -достижения первого совпадения с фильтром. -Жесткие параметры фильтра сверяются первыми. При несовпадении идет сразу же переход к следующему профилю. -Если какой-то профиль удовлетворяет жесткому фильтру и L7 фильтру и содержит авто-хостлист, он выбирается сразу. -Если профиль удовлетворяет жесткому фильтру и L7 фильтру, для него задан хостлист, и у нас еще нет имени хоста, -идет переход к следующему профилю. В противном случае идет проверка по хостлистам этого профиля. -Если имя хоста удовлетворяет листам, выбирается этот профиль. Иначе идет переход к следующему. -Может так случиться, что до получения имени хоста или узнавания L7 протокола соединение идет по одному профилю, -а при выяснении этих параметров профиль меняется на лету. Это может произойти даже дважды - при выяснении L7 -и имени хоста. Чаще всего это выяснение совмещается в одно действие, поскольку по одному пакету, как правило, узнается и L7, и хост. -Поэтому если у вас есть параметры дурения нулевой фазы, тщательно продумывайте что может произойти при переключении стратегии. -Смотрите debug log, чтобы лучше понять что делает nfqws. -Нумерация профилей идет с 1 до N. Последним в цепочке создается пустой профиль с номером 0. -Он используется, когда никакие условия фильтров не совпали. - -> [!IMPORTANT] -> Множественные стратегии создавались только для случаев, когда невозможно обьединить -> имеющиеся стратегии для разных ресурсов. Копирование стратегий из blockcheck для разных сайтов -> во множество профилей без понимания как они работают приведет к нагромождению параметров, которые все равно -> не покроют все возможные заблокированные ресурсы. Вы только увязните в этой каше. - -> [!IMPORTANT] -> user-mode реализация ipset создавалась не как удобная замена *nix версии, реализованной в ядре. -> Вариант в ядре работает гораздо эффективнее. Это создавалось для систем без поддержки ipset в ядре. -> Конкретно - Windows и ядра Linux, собранные без nftables и ipset модулей ядра. Например, в android нет ipset. - -### ФИЛЬТРАЦИЯ ПО WIFI - -Имя wifi сети никак не связано с сетевым интерфейсом адаптера wifi. -Интерфейс один, подключиться можно к любой сети. Для разных сетей разные стратегии. -Стратегия от сети A не работает или ломает сеть B. Что делать ? - -Можно вручную запускать и снимать инстансы nfqws. Но можно поступить иначе. -В windows версии winws есть глобальный фильтр `--ssid-filter`. -Он включает или отключает инстанс winws в зависимости от подключенности любого адаптера к конкретной wifi сети. -При этом не учитывается маршрутизация. Такой подход возможен потому, что к windivert можно прицепить несколько инстансов winws на пересекающихся фильтрах. -При смене wifi сети одни будут включаться, другие выключаться. - -Для linux применяется иное решение. Фильтр `--filter-ssid` относится к конкретному профилю. -Невозможно повесить несколько инстансов nfqws на одну и ту же очередь или направить один и тот же трафик на несколько очередей. -Подключение и отключение от очереди разных инстансов сопряжено со сложностями синхронизации между ними. -Поэтому обрабатывать трафик должен один инстанс, и он должен уметь работать с разными wifi сетями. -Это и реализовано в параметре `--filter-ssid`. Он берет список имен wifi сетей (SSID) через запятую аналогично `--ssid-filter` для winws. -При выборе профиля имеет значение куда идет конкретный обрабатываемый пакет. На какой интерфейс. Или с какого интерфейса пакет пришел, если он считается входящим. -Поэтому даже если у вас часть трафика идет на одну сеть, часть на другую, а часть вообще не идет по wifi, то все это можно настроить. - -Сканируются все wifi интерфейсы, составляется список interface->SSID. Он обновляется по мере поступления -пакетов, но не чаще 1 раза в секунду. - -### IPTABLES ДЛЯ NFQWS - -> [!CAUTION] -> Начиная с ядер Linux 6.17 присутствует параметр конфигурации ядра CONFIG_NETFILTER_XTABLES_LEGACY, который по умолчанию в дистрибутиве может быть "not set". Отсутствие этой настройки выключает iptables-legacy. Это часть процесса депрекации iptables. Тем не менее iptables-nft будут работать, поскольку используют backend nftables. - -iptables для задействования атаки на первые пакеты данных в tcp соединении : - -``` -iptables -t mangle -I POSTROUTING -o <внешний_интерфейс> -p tcp -m multiport --dports 80,443 -m connbytes --connbytes-dir=original --connbytes-mode=packets --connbytes 1:6 -m mark ! --mark 0x40000000/0x40000000 -j NFQUEUE --queue-num 200 --queue-bypass -``` - -Этот вариант применяем, когда DPI не следит за всеми запросами http внутри keep-alive сессии. -Если следит, направляем только первый пакет от https и все пакеты от http : - -``` -iptables -t mangle -I POSTROUTING -o <внешний_интерфейс> -p tcp --dport 443 -m connbytes --connbytes-dir=original --connbytes-mode=packets --connbytes 1:6 -m mark ! --mark 0x40000000/0x40000000 -j NFQUEUE --queue-num 200 --queue-bypass -iptables -t mangle -I POSTROUTING -o <внешний_интерфейс> -p tcp --dport 80 -m mark ! --mark 0x40000000/0x40000000 -j NFQUEUE --queue-num 200 --queue-bypass -``` - -mark нужен, чтобы сгенерированный поддельный пакет не попал опять к нам на обработку. nfqws выставляет fwmark при его отсылке. -Хотя nfqws способен самостоятельно различать помеченные пакеты, фильтр в iptables по mark нужен при использовании connbytes, -чтобы не допустить изменения порядка следования пакетов. Процессинг очереди - процесс отложенный. -Если ядро имеет пакеты на отсылку вне очереди - оно их отправляет незамедлительно. -Изменение правильного порядка следования пакетов при десинхронизации ломает всю идею. -Так же были замечены дедлоки при достаточно большой отсылке пакетов из nfqws и отсутствии mark фильтра. -Процесс может зависнуть. Поэтому наличие фильтра по mark в ip/nf tables можно считать обязательным. - -Почему `--connbytes 1:6` : -* 1 - для работы методов десинхронизации 0-й фазы и корректной работы conntrack -* 2 - иногда данные идут в 3-м пакете 3-way handshake -* 3 - стандартная ситуация приема одного пакета запроса -* 4-6 - на случай ретрансмиссии или запроса длиной в несколько пакетов (TLSClientHello с kyber, например) - -Для режима autottl необходимо перенаправление входящего `SYN,ACK` пакета или первого пакета соединения (что обычно есть то же самое). -Для режима autohostlist необходимы входящие RST и http redirect. -Можно построить фильтр на tcp flags для выделения `SYN,ACK` и модуле u32 для поиска характерных паттернов http redirect, -но проще использовать connbytes для выделения нескольких начальных входящих пакетов. - -` -iptables -t mangle -I PREROUTING -i <внешний интерфейс> -p tcp -m multiport --sports 80,443 -m connbytes --connbytes-dir=reply --connbytes-mode=packets --connbytes 1:3 -m mark ! --mark 0x40000000/0x40000000 -j NFQUEUE --queue-num 200 --queue-bypass -` - -Для quic : - -``` -iptables -t mangle -I POSTROUTING -o <внешний_интерфейс> -p udp --dport 443 -m connbytes --connbytes-dir=original --connbytes-mode=packets --connbytes 1:6 -m mark ! --mark 0x40000000/0x40000000 -j NFQUEUE --queue-num 200 --queue-bypass -``` - -6 пакетов берется, чтобы покрыть случаи возможных ретрансмиссий quic initial в случае плохой связи или если сервер плохо себя чувствует, а приложение настаивает именно на quic, не переходя на tcp. -А так же для работы autohostlist по quic. Однако, autohostlist для quic не рекомендуется. - -### NFTABLES ДЛЯ NFQWS - -Можно начать с базовой конфигурации. - -``` -IFACE_WAN=wan - -nft create table inet ztest - -nft add chain inet ztest post "{type filter hook postrouting priority mangle;}" -nft add rule inet ztest post oifname $IFACE_WAN meta mark and 0x40000000 == 0 tcp dport "{80,443}" ct original packets 1-6 queue num 200 bypass -nft add rule inet ztest post oifname $IFACE_WAN meta mark and 0x40000000 == 0 udp dport 443 ct original packets 1-6 queue num 200 bypass - -# auto hostlist with avoiding wrong ACK numbers in RST,ACK packets sent by russian DPI -sysctl net.netfilter.nf_conntrack_tcp_be_liberal=1 -nft add chain inet ztest pre "{type filter hook prerouting priority filter;}" -nft add rule inet ztest pre iifname $IFACE_WAN tcp sport "{80,443}" ct reply packets 1-3 queue num 200 bypass -``` - -Для задействования IP фрагментации и `datanoack` на проходящие пакеты требуется особая конфигурация цепочек, перенаправляющая пакеты после NAT. -В скриптах zapret эта схема называется `POSTNAT`, и она возможна только на nftables. -Сгенерированные nfqws пакеты требуется на раннем этапе помечать как **notrack**, чтобы они не были испорчены NAT. - -``` -IFACE_WAN=wan - -nft create table inet ztest - -nft add chain inet ztest postnat "{type filter hook postrouting priority srcnat+1;}" -nft add rule inet ztest postnat oifname $IFACE_WAN meta mark and 0x40000000 == 0 tcp dport "{80,443}" ct original packets 1-6 queue num 200 bypass -nft add rule inet ztest postnat oifname $IFACE_WAN meta mark and 0x40000000 == 0 udp dport 443 ct original packets 1-6 queue num 200 bypass - -nft add chain inet ztest predefrag "{type filter hook output priority -401;}" -nft add rule inet ztest predefrag "mark & 0x40000000 != 0x00000000 notrack" -``` - -Удаление тестовой таблицы : - -``` -nft delete table inet ztest -``` - -### FLOW OFFLOADING - -Если ваше устройство поддерживает аппаратное ускорение (flow offloading, hardware nat, hardware acceleration), то -iptables могут не работать. При включенном offloading пакет не проходит по обычному пути netfilter. Необходимо или его -отключить, или выборочно им управлять. - -В новых ядрах присутствует software flow offloading (SFO). -Пакеты, проходящие через SFO, так же проходят мимо большей части механизмов iptables. При включенном SFO работает -DNAT/REDIRECT (tpws). Эти соединения исключаются из offloading. Однако, остальные соединения идут через SFO, потому -NFQUEUE будет срабатывать только до помещения соединения в flowtable. Практически это означает, что почти весь функционал nfqws работать не будет. -Offload включается через специальный target в iptables `FLOWOFFLOAD` или через flowtable в nftables. - -Не обязательно пропускать весь трафик через offload. -tpws и так обходит offload "by design", а для отработки nfqws достаточно первых нескольких пакетов в tcp соединении или udp сеансе. -Пока сеанс не направлен на offload, он процессится обычным образом через полноценный netfilter. -Как только срабатывает правило offload по любому входящему или исходящему пакету, весь сеанс окончательно уходит из netfilter в offload. -Поэтому скрипты zapret берут правила для NFQUEUE, что они создали, и из них создают exemption правила, которые не дают раньше времени попасть сеансу в offload, а потом его "отпускают". -При этом входящим пакетам не дают начать offload, триггером выступают только исходящие пакеты. -Эта схема обеспечивает практически нулевой негативный эффект на скорость, одновременно покрывая нужды nfqws и упрощая правила таблиц. - -OpenWrt не предусматривает выборочного управления offload, поэтому скрипты zapret поддерживают свою систему выборочного управления. - -iptables target `FLOWOFFLOAD` - это проприетарное изобретение OpenWrt. -Управление offload в nftables реализовано в базовом ядре linux без патчей. -nftables - единственный способ включения offload на классическом Linux. - -### ОСОБЕННОСТИ ЖЕЛЕЗОК - -На устройствах mediatek замечены 2 проблемы. - -Драйвер mediatek ethernet отбрасывает tcp и udp пакеты с неверной чексуммой на аппаратном уровне, это не отключается. -Как следствие не будет работать fooling badsum через роутер, но будет с него. - -Другая проблема mediatek, затрагивающая как ethernet, так и wireless, проявляется на udp, когда включен offload rx-gro-list. -Пока отсутствует nfqueue, все хорошо. Как только nfqueue появляется, часть пакетов выпадает. -Особенно заметно это проявляется на дурении QUIC с kyber. - -
- shell код лечения - -``` -append_separator_list() -{ - # $1 - var name to receive result - # $2 - separator - # $3 - quoter - # $4,$5,... - elements - local _var="$1" sep="$2" quo="$3" i - - eval i="\$$_var" - shift; shift; shift - while [ -n "$1" ]; do - if [ -n "$i" ] ; then - i="$i$sep$quo$1$quo" - else - i="$quo$1$quo" - fi - shift - done - eval $_var="\$i" -} -resolve_lower_devices() -{ - # $1 - bridge interface name - [ -d "/sys/class/net/$1" ] && { - find "/sys/class/net/$1" -follow -maxdepth 1 -name "lower_*" | - { - local l lower lowers - while read lower; do - lower="$(basename "$lower")" - l="${lower#lower_*}" - [ "$l" != "$lower" ] && append_separator_list lowers ' ' '' "$l" - done - printf "$lowers" - } - } -} - -# it breaks nfqueue -lans=$(resolve_lower_devices br-lan) -for int in $lans; do - ethtool -K $int rx-gro-list off -done -``` -
- -Этот код нужно вызывать после вставания интерфейса LAN, когда все bridge members уже занесены в bridge. -Можно использовать хук в `/etc/hotplug.d/iface`. Должен быть установлен `ethtool`. - -Проблемы mediatek были подтверждены на MT7621 (TP-Link Archer C6U v1) и MT7981 (Xiaomi AX3000T). -Другие чипсеты могут быть так же подвержены проблеме, а могут и не быть. Более широкой статистики нет. - - -### ДУРЕНИЕ СО СТОРОНЫ СЕРВЕРА - -Это тоже возможно. -nfqws рассчитан на атаку со стороны клиента, поэтому он распознает прямой и обратный трафик на основании роли в установлении tcp соединения. -Если проходит SYN, то source IP - это клиент. Если проходит SYN,ACK , то source IP - это сервер. -Для UDP клиентом считается source IP первого прошедшего пакета по двум связкам ip-port. -На сервере трафиком клиента будет считаться принятый трафик, а трафиком сервера - исходящий. - -`--wsize` работает в любом случае, он может использоваться как на клиенте, так и на сервере. -Остальные техники работают только если nfqws считает трафик трафиком клиента. -Поэтому для их применения по исходящему с сервера трафику conntrack нужно выключить параметром `--ctrack-disable`. -Если пакет не найден в conntrack, по нему идет работа как по пакету клиента. - -Большинство протоколов опознаваться не будет, потому что система их опознавания рассчитана на содержание пакетов от клиента. -Чтобы задействовать техники типа `fake` или `multisplit` нужно использовать `--dpi-desync-any-protocol` с ограничителем connbytes или -с ограничителем на основании содержания пакета или его заголовков. -start/cutoff недоступны, поскольку завязаны на conntrack. - -Техника `synack-split` позволяет разбить tcp сегмент SYN,ACK на отдельные части с SYN и с ACK. -В ответ на это клиент шлет SYN,ACK , что обычно характеризует сервер. -У некоторых DPI от этого может ломаться алгоритм, и они перестают блокировать запрещенный контент. -Здесь [подробное описание](https://nmap.org/misc/split-handshake.pdf) что есть split handshake. - -Перенаправление трафика обычно идет по номеру source портов и направлению original. -original - это исходящий с системы трафик, reply - входящий. - - -## tpws - -tpws - это transparent proxy. -``` -@|$ ; читать конфигурацию из файла. опция должна быть первой. остальные опции игнорируются. - ---debug=0|1|2|syslog|@ ; 0,1,2 = логирование на косоль : 0=тихо, 1(default)=подробно, 2=отладка. ---debug-level=0|1|2 ; указать уровень логирования для syslog и @ ---dry-run ; проверить опции командной строки и выйти. код 0 - успешная проверка. ---version ; вывести версию и выйти - ---daemon ; демонизировать прогу ---pidfile= ; сохранить PID в файл ---user= ; менять uid процесса ---uid=uid[:gid] ; менять uid процесса ---bind-addr ; на каком адресе слушать. может быть ipv4 или ipv6 адрес - ; если указан ipv6 link local, то требуется указать с какого он интерфейса : fe80::1%br-lan ---bind-linklocal=no|unwanted|prefer|force ; no : биндаться только на global ipv6 - ; unwanted (default) : предпочтительно global, если нет - LL - ; prefer : предпочтительно LL, если нет - global - ; force : биндаться только на LL ---bind-iface4= ; слушать на первом ipv4 интерфейса iface ---bind-iface6= ; слушать на первом ipv6 интерфейса iface ---bind-wait-ifup= ; ждать до N секунд появления и поднятия интерфейса ---bind-wait-ip= ; ждать до N секунд получения IP адреса (если задан --bind-wait-ifup - время идет после поднятия интерфейса) ---bind-wait-ip-linklocal= - ; имеет смысл только при задании --bind-wait-ip - ; --bind-linklocal=unwanted : согласиться на LL после N секунд - ; --bind-linklocal=prefer : согласиться на global address после N секунд ---bind-wait-only ; подождать все бинды и выйти. результат 0 в случае успеха, иначе не 0. ---connect-bind-addr ; с какого адреса подключаться во внешнюю сеть. может быть ipv4 или ipv6 адрес - ; если указан ipv6 link local, то требуется указать с какого он интерфейса : fe80::1%br-lan - ; опция может повторяться для v4 и v6 адресов - ; опция не отменяет правил маршрутизации ! выбор интерфейса определяется лишь правилами маршрутизации, кроме случая v6 link local. ---socks ; вместо прозрачного прокси реализовать socks4/5 proxy ---no-resolve ; запретить ресолвинг имен через socks5 ---resolve-threads ; количество потоков ресолвера ---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 ---nosplice ; не использовать splice на linux системах ---skip-nodelay ; не устанавливать в исходящих соединения TCP_NODELAY. несовместимо со split. ---local-tcp-user-timeout= ; таймаут соединений client-proxy (по умолчанию : 10 сек, 0 = оставить системное значение) ---remote-tcp-user-timeout= ; таймаут соединений proxy-target (по умолчанию : 20 сек, 0 = оставить системное значение) ---fix-seg= ; исправлять неудачи tcp сегментации ценой задержек для всех клиентов и замедления. ждать до N мс. по умолчанию 30 мс. ---ipcache-lifetime= ; время жизни записей кэша IP в секундах. 0 - без ограничений. ---ipcache-hostname=[0|1] ; 1 или отсутствие аргумента включают кэширование имен хостов для применения в стратегиях нулевой фазы - ---split-pos=N|-N|marker+N|marker-N ; список через запятую маркеров для tcp сегментации ---split-any-protocol ; применять сегментацию к любым пакетам. по умолчанию - только к известным протоколам (http, TLS) ---disorder[=http|tls] ; путем манипуляций с сокетом вынуждает отправлять первым второй сегмент разделенного запроса ---oob[=http|tls] ; отправить байт out-of-band data (OOB) в конце первой части сплита ---oob-data=|0xHEX ; переопределить байт OOB. по умолчанию 0x00. ---hostcase ; менять регистр заголовка "Host:". по умолчанию на "host:". ---hostspell=HoST ; точное написание заголовка Host (можно "HOST" или "HoSt"). автоматом включает --hostcase ---hostdot ; добавление точки после имени хоста : "Host: kinozal.tv." ---hosttab ; добавление табуляции после имени хоста : "Host: kinozal.tv\t" ---hostnospace ; убрать пробел после "Host:" ---hostpad= ; добавить паддинг-хедеров общей длиной перед Host: ---domcase ; домен после Host: сделать таким : TeSt.cOm ---methodspace ; добавить пробел после метода : "GET /" => "GET /" ---methodeol ; добавить перевод строки перед методом : "GET /" => "\r\nGET /" ---unixeol ; конвертировать 0D0A в 0A и использовать везде 0A ---tlsrec=N|-N|marker+N|marker-N ; разбивка TLS ClientHello на 2 TLS records на указанной позиции. Минимальное смещение - 6. ---mss= ; установить MSS для клиента. может заставить сервер разбивать ответы, но существенно снижает скорость ---tamper-start=[n] ; начинать дурение только с указанной байтовой позиции или номера блока исходяшего потока (считается позиция начала принятого блока) ---tamper-cutoff=[n] ; закончить дурение на указанной байтовой позиции или номере блока исходящего потока (считается позиция начала принятого блока) ---hostlist= ; действовать только над доменами, входящими в список из filename. поддомены автоматически учитываются, если хост не начинается с '^'. - ; в файле должен быть хост на каждой строке. - ; список читается при старте и хранится в памяти в виде иерархической структуры для быстрого поиска. - ; при изменении времени модификации файла он перечитывается автоматически по необходимости - ; список может быть запакован в gzip. формат автоматически распознается и разжимается - ; списков может быть множество. пустой общий лист = его отсутствие - ; хосты извлекаются из Host: хедера обычных http запросов и из SNI в TLS ClientHello. ---hostlist-domains= ; фиксированный список доменов через зяпятую. можно использовать # в начале для комментирования отдельных доменов. ---hostlist-exclude= ; не применять дурение к доменам из листа. может быть множество листов. схема аналогична include листам. ---hostlist-exclude-domains= ; фиксированный список доменов через зяпятую. можно использовать # в начале для комментирования отдельных доменов. ---hostlist-auto= ; обнаруживать автоматически блокировки и заполнять автоматический hostlist (требует перенаправления входящего трафика) ---hostlist-auto-fail-threshold= ; сколько раз нужно обнаружить ситуацию, похожую на блокировку, чтобы добавить хост в лист (по умолчанию: 3) ---hostlist-auto-fail-time= ; все эти ситуации должны быть в пределах указанного количества секунд (по умолчанию: 60) ---hostlist-auto-debug= ; лог положительных решений по autohostlist. позволяет разобраться почему там появляются хосты. ---new ; начало новой стратегии (новый профиль) ---skip ; не использовать этот профиль . полезно для временной деактивации профиля без удаления параметров. ---filter-l3=ipv4|ipv6 ; фильтр версии ip для текущей стратегии ---filter-tcp=[~]port1[-port2]|* ; фильтр портов tcp для текущей стратегии. ~ означает инверсию. поддерживается список через запятую. ---filter-l7=[http|tls|quic|wireguard|dht|unknown] ; фильтр протокола L6-L7. поддерживается несколько значений через запятую. ---ipset= ; включающий ip list. на каждой строчке ip или cidr ipv4 или ipv6. поддерживается множество листов и gzip. перечитка автоматическая. ---ipset-ip= ; фиксированный список подсетей через запятую. можно использовать # в начале для комментирования отдельных подсетей. ---ipset-exclude= ; исключающий ip list. на каждой строчке ip или cidr ipv4 или ipv6. поддерживается множество листов и gzip. перечитка автоматическая. ---ipset-exclude-ip= ; фиксированный список подсетей через запятую. можно использовать # в начале для комментирования отдельных подсетей. -``` - -### TCP СЕГМЕНТАЦИЯ В TPWS - -tpws, как и nfqws, поддерживает множественную сегментацию запросов. Сплит позиции задаются в `--split-pos`. -Указываются маркеры через запятую. Описание маркеров см в разделе [nfqws](#tcp-сегментация). - -На прикладном уровне в общем случае нет гарантированного средства заставить ядро выплюнуть -блок данных, порезанным в определенном месте. ОС держит буфер отсылки (SNDBUF) у каждого сокета. -Если у сокета включена опция TCP_NODELAY и буфер пуст, то каждый send приводит к отсылке -отдельного ip пакета или группы пакетов, если блок не вмещается в один ip пакет. -Однако, если в момент send уже имеется неотосланный буфер, то ОС присоединит данные к нему, -никакой отсылки отдельным пакетом не будет. Но в этом случае и так нет никакой гарантии, -что какой-то блок сообщения пойдет в начале пакета, на что собственно и заточены DPI. -Разбиение будет производиться согласно MSS, который зависит от MTU исходящего интерфейса. -Таким образом DPI, смотрящие в начало поля данных TCP пакета, будут поломаны в любом случае. -Протокол http относится к запрос-ответным протоколам. Новое сообщение посылается только тогда, -когда сервер получил запрос и полностью вернул ответ. Значит запрос фактически был не только отослан, -но и принят другой стороной, а следовательно буфер отсылки пуст, и следующие 2 send приведут -к отсылке сегментов данных разными ip пакетами. - -Таким образом tpws обеспечивает сплит только за счет раздельных вызовов send, и это обычно работает надежно, -если разбивать не на слишком много частей и не на слишком мелкие подряд следующие части. -В последнем случае Linux все же может обьединить некоторые части, что приведет к несоответствию реальной сегментации -указанным сплит позициям. Другие ОС в этом вопросе ведут себя более предсказуемо. Спонтанного обьединения замечено не было. -Поэтому не стоит злоупотреблять сплитами и в особенности мелкими соседними пакетами. - -Как показывается практика, проблемы могут начаться , если количество сплитов более одного. -На каких-то системах наблюдался стабильный результат до 8 сплитов, на других проблемы уже начинались после 2 сплитов. -Один сплит работает стабильно, если не является частью массивной потоковой передачи. -При неудаче сегментации будет выводиться сообщение `WARNING ! segmentation failed`. -Если вы его видите, это повод снизить количество сплит позиций. -Если это не вариант, для ядер Linux >=4.6 есть параметр `--fix-seg`. Он позволяет подождать завершение отсылки перед отправкой следующей части. -Но этот вариант ломает модель асинхронной обработки событий. Пока идет ожидание, все остальные соединения не обрабатываются -и кратковременно подвисают. На практике это может быть совсем небольшое ожидание - менее 10 мс. -Выполняется оно только , если происходит split, и в ожидании есть реальная необходимость. -В высоконагруженных системах данный вариант не рекомендуется. Но для домашнего использования может подойти, и вы эти задержки даже не заметите. - -Если вы пытаетесь сплитнуть массивную передачу с `--split-any-protocol`, когда информация поступает быстрее отсылки, -то без `--fix-seg` ошибки сегментации будут сыпаться сплошным потоком. -Работа по массивному потоку без ограничителей `--tamper-start` и `--tamper-cutoff` обычно лишена смысла. - -tpws работает на уровне сокетов, поэтому длинный запрос, не вмещающийся в 1 пакет (TLS с kyber), он получает целым блоком. -На каждую сплит часть он делает отдельный вызов `send()`. Но ОС не сможет отослать данные в одном пакете, если размер превысит MTU. -В случае слишком большого сегмента ОС дополнительно его порежет на более мелкие. Результат должен быть аналогичен nfqws. - -`--disorder` заставляет слать каждый 2-й пакет с TTL=1, начиная с первого. -К серверу приходят все четные пакеты сразу. На остальные ОС делает ретрансмиссию, и они приходят потом. -Это само по себе создает дополнительную задержку (200 мс в linux для первой ретрансмиссии). -Иным способом сделать disorder в сокет варианте не представляется возможным. -Итоговый порядок для 6 сегментов получается `2 4 6 1 3 5`. - -`--oob` высылает 1 байт out-of-band data после первого сплит сегмента. `oob` в каждом сегменте сплита показал себя ненадежным. -Сервер получает oob в сокет. - -Сочетание `oob` и `disorder` возможно только в Linux. Остальные ОС не умеют с таким справляться. Флаг URG теряется при ретрансмиссиях. -Сервер получает oob в сокет. Сочетание этих параметров в ос, кроме Linux, вызывает ошибку на этапе запуска. - -### TLSREC - -`--tlsrec` позволяют внутри одного tcp сегмента разрезать TLS ClientHello на 2 TLS records. Можно использовать стандартный -механизм маркеров для задания относительных позиций. - -`--tlsrec` ломает значительное количество сайтов. Криптобиблиотеки (openssl, ...) на оконечных http серверах -без проблем принимают разделенные tls сегменты, но мидлбоксы - не всегда. К мидлбоксам можно отнести CDN -или системы ddos-защиты. Поэтому применение `--tlsrec` без ограничителей вряд ли целесообразно. -В РФ `--tlsrec` обычно не работает с TLS 1.2, потому что цензор парсит сертификат сервера из ServerHello. -Работает только с TLS 1.3, поскольку там эта информация шифруется. -Впрочем, сейчас сайтов, не поддерживающих TLS 1.3, осталось немного. - -### MSS - -`--mss` устанавливает опцию сокета TCP_MAXSEG. Клиент выдает это значение в tcp опциях SYN пакета. -Сервер в ответ в SYN,ACK выдает свой MSS. На практике сервера обычно снижают размеры отсылаемых ими пакетов, но они -все равно не вписываются в низкий MSS, указанный клиентом. Обычно чем больше указал клиент, тем больше -шлет сервер. На TLS 1.2 если сервер разбил заброс так, чтобы домен из сертификата не попал в первый пакет, -это может обмануть DPI, секущий ответ сервера. -Схема может значительно снизить скорость и сработать не на всех сайтах. - -С фильтром по hostlist совместимо только в [некоторых случаях](#множественные-стратегии-1), когда возможно узнать имя хоста на момент применения дурения. - -Применяя данную опцию к сайтам TLS1.3, если броузер тоже поддерживает TLS1.3, то вы делаете только хуже. -Но нет способа автоматически узнать когда надо применять, когда нет, поскольку MSS идет только в -3-way handshake еще до обмена данными, а версию TLS можно узнать только по ответу сервера, который -может привести к реакции DPI. -Использовать только когда нет ничего лучше или для отдельных ресурсов. -Для http использовать смысла нет, поэтому заводите отдельный desync profile с фильтром по порту 443. -Работает только на Linux, не работает на BSD и MacOS. - -### ДРУГИЕ ПАРАМЕТРЫ ДУРЕНИЯ - -Параметр `--hostpad=` добавляет паддинг-хедеров перед `Host:` на указанное количество байтов. -Если размер `` слишком большой, то идет разбивка на разные хедеры по 2K. -Общий буфер приема http запроса - 64K, больший паддинг не поддерживается, да и http сервера -такое уже не принимают. -Полезно против DPI, выполняющих реассемблинг TCP с ограниченным буфером. -Если техника работает, то после некоторого количества bytes http запрос начнет проходить до сайта. -Если при этом критический размер padding около MTU, значит скорее всего DPI не выполняет реассемблинг пакетов, и лучше будет использовать обычные опции TCP сегментации. -Если все же реассемблинг выполняется, то критический размер будет около размера буфера DPI. Он может быть 4K или 8K, возможны и другие значения. - -### МНОЖЕСТВЕННЫЕ СТРАТЕГИИ - -Работают аналогично **nfqws**, кроме некоторых моментов. -Нет параметра `--filter-udp`, поскольку **tpws** udp не поддерживает. -Методы нулевой фазы (`--mss`) могут работать по хостлисту только в двух случаях: -если используется режим socks и удаленный ресолвинг хостов через прокси, либо используется система [кэша IP](#кэш-ip) для запоминания соответствия IP->hostname. -Работоспособность вашей настройки в одном и том же режиме может зависеть от того, -применяет ли клиент удаленный ресолвинг. Это может быть неочевидно. В одной программе работает, в другой - нет. - -Если вы используете профиль с хостлистом , и вам нужен mss всегда, укажите mss в профиле с хостлистом, -создайте еще один профиль без хостлиста, если его еще нет, и в нем еще раз укажите mss. -Тогда при любом раскладе будет выполняться mss. - -Если вам нужен mss по хостлисту, указывайте `--mss` только в профиле с хостлистом и убедитесь в наличии любого из необходимых условий работы в таком режиме. - -Используйте `curl --socks5` и `curl --socks5-hostname` для проверки вашей стратегии. -Смотрите вывод `--debug`, чтобы убедиться в правильности настроек. - -### СЛУЖЕБНЫЕ ПАРАМЕТРЫ - -`--debug` позволяет выводить подробный лог действий на консоль, в syslog или в файл. -Может быть важен порядок следования опций. `--debug` лучше всего указывать в самом начале. -Опции анализируются последовательно. Если ошибка будет при проверке опции, а до анализа `--debug` еще дело не дошло, -то сообщения не будут выведены в файл или syslog. -`--debug=0|1|2` позволяют сразу в одном параметре включить логирование на консоль и указать уровень. -Сохранено для совместимости с более старыми версиями. Для выбора уровня в режиме syslog или file используйте -отдельный параметр `--debug-level`. Если в этих режимах `--debug` не указывать уровень через `--debug-level`, то -автоматически назначается уровень 1. -При логировании в файл процесс не держит файл открытым. Ради каждой записи файл открывается и потом закрывается. -Так что файл можно удалить в любой момент, и он будет создан заново при первом же сообщении в лог. -Но имейте в виду, что если вы запускаете процесс под root, то будет сменен UID на не-root. -В начале на лог файл меняется owner, иначе запись будет невозможна. Если вы потом удалите файл, -и у процесса не будет прав на создание файла в его директории, лог больше не будет вестись. -Вместо удаления лучше использовать truncate. -В шелле это можно сделать через команду ": >filename" - -tpws может биндаться на множество интерфейсов и IP адресов (до 32 шт). -Порт всегда только один. -Параметры `--bind-iface*` и `--bind-addr` создают новый бинд. -Остальные параметры `--bind-*` относятся к последнему бинду. -Для бинда на все ipv4 укажите `--bind-addr "0.0.0.0"`, на все ipv6 - `"::"`. `--bind-addr=""` - биндаемся на все ipv4 и ipv6. -Выбор режима использования link local ipv6 адресов (`fe80::/8`) : -``` ---bind-iface6 --bind-linklocal=no : сначала приватный адрес fc00::/7, затем глобальный адрес ---bind-iface6 --bind-linklocal=unwanted : сначала приватный адрес fc00::/7, затем глобальный адрес, затем link local. ---bind-iface6 --bind-linklocal=prefer : сначала link local, затем приватный адрес fc00::/7, затем глобальный адрес. ---bind-iface6 --bind-linklocal=force : только link local -``` -Если не указано ни одного бинда, то создается бинд по умолчанию на все адреса всех интерфейсов. -Для бинда на конкретный link-local address делаем так : `--bind-iface6=fe80::aaaa:bbbb:cccc:dddd%iface-name` -Параметры `--bind-wait*` могут помочь в ситуациях, когда нужно взять IP с интерфейса, но его еще нет, он не поднят -или не сконфигурирован. -В разных системах события ifup ловятся по-разному и не гарантируют, что интерфейс уже получил IP адрес определенного типа. -В общем случае не существует единого механизма повеситься на событие типа "на интерфейсе X появился link local address". -Для бинда на известный ip, когда еще интерфейс не сконфигурирован, нужно делать так: `--bind-addr=192.168.5.3 --bind-wait-ip=20` -В режиме transparent бинд возможен на любой несуществующий адрес, в режиме socks - только на существующий. - -Параметры rcvbuf и sndbuf позволяют установить setsockopt SO_RCVBUF SO_SNDBUF для локального и удаленного соединения. - -`--skip-nodelay` может быть полезен, когда tpws используется без дурения, чтобы привести MTU к MTU системы, на которой работает tpws. -Это может быть полезно для скрытия факта использования VPN. Пониженный MTU - 1 из способов обнаружения -подозрительного подключения. С tcp proxy ваши соединения неотличимы от тех, что сделал бы сам шлюз. - -`--local-tcp-user-timeout` и `--remote-tcp-user-timeout` устанавливают значение таймаута в секундах -для соединений клиент-прокси и прокси-сервер. Этот таймаут соответствует опции сокета linux -TCP_USER_TIMEOUT. Под таймаутом подразумевается время, в течение которого буферизированные данные -не переданы или на переданные данные не получено подтверждение (ACK) от другой стороны. -Этот таймаут никак не касается времени отсутствия какой-либо передачи через сокет лишь потому, -что данных для передачи нет. Полезно для сокращения время закрытия подвисших соединений. -Поддерживается только на Linux и MacOS. - -Режим `--socks` не требует повышенных привилегий (кроме бинда на привилегированные порты 1..1023). -Поддерживаются версии socks 4 и 5 без авторизации. Версия протокола распознается автоматически. -Подключения к IP того же устройства, на котором работает tpws, включая localhost, запрещены. -socks5 позволяет удаленно ресолвить хосты (curl : --socks5-hostname firefox : socks_remote_dns=true). -tpws поддерживает эту возможность асинхронно, не блокируя процессинг других соединений, используя -многопоточный пул ресолверов. Количество потоков определяется автоматически в зависимости от `--maxconn`, -но можно задать и вручную через параметр `--resolver-threads`. -Запрос к socks выставляется на паузу, пока домен не будет преобразован в ip адрес в одном из потоков -ресолвера. Ожидание может быть более длинным, если все потоки заняты. -Если задан параметр `--no-resolve`, то подключения по именам хостов запрещаются, а пул ресолверов не создается. -Тем самым экономятся ресурсы. - -### IPTABLES ДЛЯ TPWS - -Для перенаправления tcp соединения на transparent proxy используются команды следующего вида : - -``` -iptables -t nat -I OUTPUT -o <внешний_интерфейс> -p tcp --dport 80 -m owner ! --uid-owner tpws -j DNAT --to 127.0.0.127:988 -iptables -t nat -I PREROUTING -i <внутренний_интерфейс> -p tcp --dport 80 -j DNAT --to 127.0.0.127:988 -``` - -Первая команда для соединений с самой системы, вторая - для проходящих через роутер соединений. - -DNAT на localhost работает в цепочке OUTPUT, но не работает в цепочке PREROUTING без включения параметра -route_localnet : - -`sysctl -w net.ipv4.conf.<внутренний_интерфейс>.route_localnet=1` - -Можно использовать `-j REDIRECT --to-port 988` вместо DNAT, однако в этом случае процесс transparent proxy должен -слушать на ip адресе входящего интерфейса или на всех адресах. Слушать на всех - не есть хорошо с точки зрения -безопасности. Слушать на одном (локальном) можно, но в случае автоматизированного скрипта придется его узнавать, потом -динамически вписывать в команду. В любом случае требуются дополнительные усилия. Использование route_localnet тоже имеет -потенциальные проблемы с безопасностью. Вы делаете доступным все, что висит на `127.0.0.0/8` для локальной подсети < -внутренний_интерфейс>. Службы обычно привязываются к `127.0.0.1`, поэтому можно средствами iptables запретить входящие -на `127.0.0.1` не с интерфейса lo, либо повесить tpws на любой другой IP из `127.0.0.0/8`, например на `127.0.0.127`, -и разрешить входящие не с lo только на этот IP. - -``` -iptables -A INPUT ! -i lo -d 127.0.0.127 -j ACCEPT -iptables -A INPUT ! -i lo -d 127.0.0.0/8 -j DROP -``` - -Фильтр по owner необходим для исключения рекурсивного перенаправления соединений от самого tpws. tpws запускается под -пользователем **tpws**, для него задается исключающее правило. - -ip6tables работают почти точно так же, как и ipv4, но есть ряд важных нюансов. В DNAT следует брать адрес --to в -квадратные скобки. Например : - -`ip6tables -t nat -I OUTPUT -o <внешний_интерфейс> -p tcp --dport 80 -m owner ! --uid-owner tpws -j DNAT --to [::1]:988` - -Параметра route_localnet не существует для ipv6. DNAT на localhost (::1) возможен только в цепочке OUTPUT. В цепочке -PREROUTING DNAT возможен на любой global address или на link local address того же интерфейса, откуда пришел пакет. -NFQUEUE работает без изменений. - -### NFTABLES ДЛЯ TPWS - -Базовая конфигурация : - -``` -IFACE_WAN=wan -IFACE_LAN=br-lan - -sysctl -w net.ipv4.conf.$IFACE_LAN.route_localnet=1 - -nft create table inet ztest - -nft create chain inet ztest localnet_protect -nft add rule inet ztest localnet_protect ip daddr 127.0.0.127 return -nft add rule inet ztest localnet_protect ip daddr 127.0.0.0/8 drop -nft create chain inet ztest input "{type filter hook input priority filter - 1;}" -nft add rule inet ztest input iif != "lo" jump localnet_protect - -nft create chain inet ztest dnat_output "{type nat hook output priority dstnat;}" -nft add rule inet ztest dnat_output meta skuid != tpws oifname $IFACE_WAN tcp dport { 80, 443 } dnat ip to 127.0.0.127:988 -nft create chain inet ztest dnat_pre "{type nat hook prerouting priority dstnat;}" -nft add rule inet ztest dnat_pre meta iifname $IFACE_LAN tcp dport { 80, 443 } dnat ip to 127.0.0.127:988 -``` - -Удаление таблицы : -``` -nft delete table inet ztest -``` - -## 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. -ipfw из FreeBSD понимает ip/prefix, но не понимает ip1-ip2. -ip2net фильтрует входные данные, выкидывая неправильные IP адреса. - -Выбирается подсеть, в которой присутствует указанный минимум адресов. -Для 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 - гораздо лучше. - -## mdig - -Программа предназначена для многопоточного ресолвинга больших листов через системный DNS. -Она берет из stdin список доменов и выводит в stdout результат ресолвинга. Ошибки выводятся в stderr. - -``` ---threads= ; количество потоков. по умолчанию 1. ---family=<4|6|46> ; выбор семейства IP адресов : ipv4, ipv6, ipv4+ipv6 ---verbose ; дебаг-лог на консоль ---stats=N ; выводить статистику каждые N доменов ---log-resolved= ; сохранять успешно отресолвленные домены в файл ---log-failed= ; сохранять неудачно отресолвленные домены в файл ---dns-make-query= ; вывести в stdout бинарный DNS запрос по домену. если --family=6, запрос будет AAAA, иначе A. ---dns-parse-query ; распарсить бинарный DNS ответ и выдать все ivp4 и ipv6 адреса из него в stdout -``` - -Параметры `--dns-make-query` и `--dns-parse-query` позволяют провести ресолвинг одного домена через произвольный канал. -Например, следующим образом можно выполнить DoH запрос, используя лишь mdig и curl : -``` -mdig --family=6 --dns-make-query=rutracker.org | curl --data-binary @- -H "Content-Type: application/dns-message" https://cloudflare-dns.com/dns-query | mdig --dns-parse-query -``` - -## Способы получения списка заблокированных IP - -!!! nftables не могут работать с ipset-ами. Собственный аналогичный механизм требует огромного количество RAM -!!! для загрузки больших листов. Например, для загона 100K записей в nfset не хватает даже 256 Mb. -!!! Если вам нужны большие листы на домашних роутерах, откатывайтесь на iptables+ipset. - -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_preresolved.sh`. то же самое, что и 2), только берется уже заресолвленый список - со стороннего ресурса. - -4) `ipset/get_reestr_preresolved_smart.sh`. то же самое, что и 3), с добавлением всего диапазона некоторых - автономных систем (прыгающие IP адреса из cloudflare, facebook, ...) и некоторых поддоменов блокируемых сайтов - -Cкрипты с названием `get_antifilter_*` оперируют списками адресов и масок подсетей с сайтов antifilter.network и antifilter.download : - -5) `ipset/get_antifilter_ip.sh`. получает лист https://antifilter.download/list/ip.lst. - -6) `ipset/get_antifilter_ipsmart.sh`. получает лист https://antifilter.network/download/ipsmart.lst. - умная суммаризация отдельных адресов из ip.lst по маскам от /32 до /22 - -7) `ipset/get_antifilter_ipsum.sh`. получает лист https://antifilter.download/list/ipsum.lst. - суммаризация отдельных адресов из ip.lst по маске /24 - -8) `ipset/get_antifilter_ipresolve.sh`. получает лист https://antifilter.download/list/ipresolve.lst. - пре-ресолвленный список, аналогичный получаемый при помощи get_reestr_resolve. только ipv4. - -9) `ipset/get_antifilter_allyouneed.sh`. получает лист https://antifilter.download/list/allyouneed.lst. - Суммарный список префиксов, созданный из ipsum.lst и subnet.lst. - -10) `ipset/get_refilter_ipsum.sh`. - Список берется отсюда : https://github.com/1andrevich/Re-filter-lists - -Все варианты рассмотренных скриптов автоматически создают и заполняют ipset. -Варианты 2-10 дополнительно вызывают вариант 1. - -11) `ipset/get_config.sh`. этот скрипт вызывает то, что прописано в переменной GETLIST из файла config - Если переменная не определена, то ресолвятся лишь листы для ipset nozapret/nozapret6. - -Листы РКН все время изменяются. Возникают новые тенденции. Требования к RAM могут меняться. -Поэтому необходима нечастая, но все же регулярная ревизия что же вообще у вас происходит на роутере. -Или вы можете узнать о проблеме лишь когда у вас начнет постоянно пропадать wifi, и вам придется -его перезагружать каждые 2 часа (метод кувалды). - -Самые щадящие варианты по RAM - `get_antifilter_allyouneed.sh`, `get_antifilter_ipsum.sh`, `get_refilter_*.sh`. - -Листы `zapret-ip.txt` и `zapret-ipban.txt` сохраняются в сжатом виде в файлы .gz. -Это позволяет снизить их размер во много раз и сэкономить место на роутере. -Отключить сжатие листов можно параметром конфига GZIP_LISTS=0. - -На роутерах не рекомендуется вызывать эти скрипты чаще раза за 2 суток, поскольку сохранение идет -либо во внутреннюю флэш память роутера, либо в случае extroot - на флэшку. -В обоих случаях слишком частая запись может убить флэшку, но если это произойдет с внутренней -флэш памятью, то вы просто убьете роутер. - -Принудительное обновление `ipset` выполняет скрипт `ipset/create_ipset.sh`. -Если передан параметр `no-update`, скрипт не обновляет `ipset`, а только создает его при его отсутствии и заполняет. -Это полезно, когда могут случиться несколько последовательных вызовов скрипта. Нет смысла несколько раз перезаполнять -`ipset`, это длительная операция на больших листах. Листы можно обновлять раз в несколько суток, и только тогда -вызывать `create_ipset` без параметра `no-update`. Во всех остальных случаях стоит применять `no-update`. - -Список РКН уже достиг внушительных размеров в сотни тысяч IP адресов. Поэтому для оптимизации `ipset` -применяется утилита `ip2net`. Она берет список отдельных IP адресов и пытается интеллектуально создать из него подсети для сокращения -количества адресов. `ip2net` отсекает неправильные записи в листах, гарантируя отсутствие ошибок при их загрузке. -`ip2net` написан на языке C, поскольку операция ресурсоемкая. Иные способы роутер может не потянуть. - -Можно внести список доменов в `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 адреса или подсети. - -**FreeBSD**. Скрипты ipset/*.sh работают так же на FreeBSD. Вместо ipset они создают lookup таблицы ipfw с аналогичными именами. -ipfw таблицы в отличие от ipset могут содержать как ipv4, так и ipv6 адреса и подсети в одной таблице, поэтому разделения нет. - -Параметр конфига LISTS_RELOAD задает произвольную команду для перезагрузки листов. -Это особенно полезно на BSD системах с PF. -LISTS_RELOAD=- отключает перезагрузку листов. - -## Фильтрация по именам доменов - -Альтернативой ipset является использование tpws или nfqws со списком доменов. -Оба демона принимают неограниченное количество листов include (`--hostlist`) и exclude (`--hostlist-exclude`). -Прежде всего проверяются exclude листы. При вхождении в них происходит отказ от дурения. -Далее при наличии include листов проверяется домен на вхождение в них. При невхождении в список отказ от дурения. -Если все include листы пустые, это приравнивается к отсутствию include листов. Ограничение перестает работать. -В иных случаях происходит дурение. -Нет ни одного списка - дурение всегда. -Есть только exclude список - дурение всех, кроме. -Есть только include список - дурение только их. -Есть оба - дурение только include, кроме exclude. - -В системе запуска это обыграно следующим образом. -Присутствуют 2 include списка : -`ipset/zapret-hosts-user.txt.gz` или `ipset/zapret-hosts-user.txt`, -`ipset/zapret-hosts.txt.gz` или `ipset/zapret-hosts.txt` -и 1 exclude список -`ipset/zapret-hosts-user-exclude.txt.gz` или `ipset/zapret-hosts-user-exclude.txt` - -При режимах фильтрации `MODE_FILTER=hostlist` или `MODE_FILTER=autohostlist` система запуска передает **nfqws** или **tpws** все листы, файлы которых присутствуют. -Передача происходит через замену маркеров `` и `` на реальные параметры `--hostlist`, `--hostlist-exclude`, `--hostlist-auto`. -Если вдруг листы include присутствуют, но все они пустые, то работа аналогична отсутствию include листа. -Файл есть, но несмотря на это дурится все, кроме exclude. -Если вам нужен именно такой режим - не обязательно удалять `zapret-hosts-users.txt`. Достаточно сделать его пустым. - -Поддомены учитываются автоматически. Например, строчка "ru" вносит в список "\*.ru". Строчка "\*.ru" в списке не сработает. -Можно использовать символ `^` в начале хоста, чтобы отказаться от автоматического учета поддоменов. - -Список доменов РКН может быть получен скриптами -``` -ipset/get_reestr_hostlist.sh -ipset/get_antizapret_domains.sh -ipset/get_reestr_resolvable_domains.sh -ipset/get_refilter_domains.sh -``` -Он кладется в `ipset/zapret-hosts.txt.gz`. - -При изменении времени модификации или размера файлов списки перечитываются автоматически. -После неатомарных операций изменения можно послать tpws/nfqws сигнал HUP для принудительной перечитки всех листов. - -При фильтрации по именам доменов демон должен запускаться без фильтрации по ipset. -tpws и nfqws решают нужно ли применять дурение в зависимости от хоста, полученного из протокола прикладного уровня (http, tls, quic). -При использовании больших списков, в том числе списка РКН, оцените объем RAM на роутере ! -Если после запуска демона RAM под завязку или случаются oom, значит нужно отказаться от таких больших списков. - -## Режим фильтрации autohostlist - -Этот режим позволяет проанализировать как запросы со стороны клиента, так и ответы от сервера. -Если хост еще не находится ни в каких листах и обнаруживается ситуация, похожая на блокировку, -происходит автоматическое добавление хоста в список `autohostlist` как в памяти, так и в файле. -**nfqws** или **tpws** сами ведут этот файл. -Чтобы какой-то хост не смог попась в `autohostlist` используйте `hostlist-exclude`. -Если он все-же туда попал - удалите запись из файла вручную. Процессы автоматически перечитают файл. -**tpws**/**nfqws** сами назначают владельцем файла юзера, под которым они работают после сброса привилегий, -чтобы иметь возможность обновлять лист. - -В случае **nfqws** данный режим требует перенаправления в том числе и входящего трафика. -Крайне рекомендовано использовать ограничитель `connbytes`, чтобы **nfqws** не обрабатывал гигабайты. -По этой же причине не рекомендуется использование режима на BSD системах. Там нет фильтра `connbytes`. - -На linux системах при использовании nfqws и фильтра connbytes может понадобиться : -`sysctl net.netfilter.nf_conntrack_tcp_be_liberal=1` -Было замечено, что некоторые DPI в России возвращают RST с неверным ACK. Это принимается tcp/ip стеком -linux, но через раз приобретает статус INVALID в conntrack. Поэтому правила с `connbytes` срабатывают -через раз, не пересылая RST пакет **nfqws**. - -Как вообще могут вести себя DPI, получив "плохой запрос" и приняв решение о блокировке: - -1) Зависание: просто отмораживается, блокируя прохождение пакетов по TCP каналу. -2) RST: отправляет RST клиенту и/или серверу -3) Редирект: (только для http) отправляет редирект на сайт-заглушку -4) Подмена сертификата: (только для https) полный перехват TLS сеанса с попыткой всунуть что-то - свое клиенту. Применяется нечасто, поскольку броузеры на такое ругаются. - -**nfqws** и **tpws** могут сечь варианты 1-3, 4 они не распознают. -В силу специфики работы с отдельными пакетами или с TCP каналом tpws и nfqws распознают эти ситуации -по-разному. -Что считается ситуацией, похожей на блокировку : -1) **nfqws** Несколько ретрансмиссий первого запроса в TCP сеансе, в котором имеется host. -2) **nfqws,tpws** RST, пришедший в ответ на первый запрос с хостом. -3) **nfqws,tpws** HTTP редирект, пришедший в ответ на первый запрос с хостом, на глобальный адрес - с доменом 2 уровня, не совпадающим с доменом 2 уровня оригинального запроса. -4) **tpws** закрытие соединения клиентом после отправки первого запроса с хостом, если не было на него - ответа со стороны сервера. Это обычно случается по таймауту, когда нет ответа (случай "зависание"). - -Чтобы снизить вероятность ложных срабатываний, имеется счетчик ситуаций, похожих на блокировку. -Если за определенное время произойдет более определенного их количества, хост считается заблокированным -и заносится в `autohostlist`. По нему сразу же начинает работать стратегия по обходу блокировки. -Если в процессе счета вебсайт отвечает без признаков блокировки, счетчик сбрасывается. -Вероятно, это был временный сбой сайта. - -На практике работа с данным режимом выглядит так. -Первый раз пользователь заходит на сайт и получает заглушку, сброс соединения или броузер подвисает, -вываливаясь по таймауту с сообщением о невозможности загрузить страницу. -Надо долбить F5, принуждая броузер повторять попытки. После некоторой попытки сайт -начинает работать, и дальше он будет работать всегда. - -С этим режимом можно использовать техники обхода, ломающие значительное количество сайтов. -Если сайт не ведет себя как заблокированный, значит обход применен не будет. -В противном случае терять все равно нечего. -Однако, могут быть временные сбои сервера, приводящие к ситуации, аналогичной блокировке. -Могут происходить ложные срабатывания. Если такое произошло, стратегия может начать ломать -незаблокированный сайт. Эту ситуацию, увы, придется вам контролировать вручную. -Заносите такие домены в `ipset/zapret-hosts-user-exclude.txt`, чтобы избежать повторения. -Чтобы впоследствии разобраться почему домен был занесен в лист, можно включить `autohostlist debug log`. -Он полезен тем, что работает без постоянного просмотра вывода **nfqws** в режиме debug. -В лог заносятся только основные события, ведущие к занесению хоста в лист. -По логу можно понять как избежать ложных срабатываний и подходит ли вообще вам этот режим. - -Можно использовать один `autohostlist` с множеством процессов. Все процессы проверяют время модификации файла. -Если файл был изменен в другом процессе, происходит его перечитывание. -Все процессы должны работать под одним uid, чтобы были права доступа на файл. - -Скрипты `zapret` ведут `autohostlist` в `ipset/zapret-hosts-auto.txt`. -`install_easy.sh` при апгрейде `zapret` сохраняет этот файл. -Режим `autohostlist` включает в себя режим `hostlist`. -Можно вести `ipset/zapret-hosts-user.txt`, `ipset/zapret-hosts-user-exclude.txt`. - -## Проверка провайдера - -Перед настройкой нужно провести исследование какую бяку устроил вам ваш провайдер. - -Нужно выяснить не подменяет ли он DNS и какой метод обхода DPI работает. -В этом вам поможет скрипт `blockcheck.sh`. - -Если 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`. -Еще один эффективный вариант - использовать ресолвер от yandex 77.88.8.88 на нестандартном порту 1253. -Многие провайдеры не анализируют обращения к DNS на нестандартных портах. -`blockcheck` если видит подмену DNS автоматически переключается на DoH сервера. - -Следует прогнать `blockcheck` по нескольким заблокированным сайтам и выявить общий характер блокировок. -Разные сайты могут быть заблокированы по-разному, нужно искать такую технику, которая работает на большинстве. -Чтобы записать вывод `blockcheck.sh` в файл, выполните: `./blockcheck.sh | tee /tmp/blockcheck.txt`. - -Проанализируйте какие методы дурения DPI работают, в соответствии с ними настройте `/opt/zapret/config`. - -Имейте в виду, что у провайдеров может быть несколько DPI или запросы могут идти через разные каналы -по методу балансировки нагрузки. Балансировка может означать, что на разных ветках разные DPI или -они находятся на разных хопах. Такая ситуация может выражаться в нестабильности работы обхода. -Дернули несколько раз curl. То работает, то connection reset или редирект. `blockcheck.sh` выдает -странноватые результаты. То split работает на 2-м. хопе, то на 4-м. Достоверность результата вызывает сомнения. -В этом случае задайте несколько повторов одного и того же теста. Тест будет считаться успешным только, -если все попытки пройдут успешно. - -При использовании `autottl` следует протестировать как можно больше разных доменов. Эта техника -может на одних провайдерах работать стабильно, на других потребуется выяснить при каких параметрах -она стабильна, на третьих полный хаос, и проще отказаться. - -`Blockcheck` имеет 3 уровня сканирования. -* `quick` - максимально быстро найти хоть что-то работающее. -* `standard` дает возможность провести исследование как и на что реагирует DPI в плане методов обхода. -* `force` дает максимум проверок даже в случаях, когда ресурс работает без обхода или с более простыми стратегиями. - -Есть ряд других параметров, которые не будут спрашиваться в диалоге, но которые можно переопределить через -переменные. - -``` -CURL - замена программы curl -CURL_MAX_TIME - время таймаута curl в секундах -CURL_MAX_TIME_QUIC - время таймаута curl для quic. если не задано, используется значение CURL_MAX_TIME -CURL_MAX_TIME_DOH - время таймаута curl для DoH серверов -CURL_CMD=1 - показывать команды curl -CURL_OPT - дополнительные параметры curl. `-k` - игнор сертификатов. `-v` - подробный вывод протокола -CURL_HTTPS_GET=1 - использовать метод GET вместо HEAD для https -DOMAINS - список тестируемых доменов через пробел -IPVS=4|6|46 - тестируемые версии ip протокола -ENABLE_HTTP=0|1 - включить тест plain http -ENABLE_HTTPS_TLS12=0|1 - включить тест https TLS 1.2 -ENABLE_HTTPS_TLS13=0|1 - включить тест https TLS 1.3 -ENABLE_HTTP3=0|1 - включить тест QUIC -REPEATS - количество попыток тестирования -PARALLEL=0|1 - включить параллельные попытки. может обидеть сайт из-за долбежки и привести к неверному результату -SCANLEVEL=quick|standard|force - уровень сканирования -BATCH=1 - пакетный режим без вопросов и ожидания ввода в консоли -HTTP_PORT, HTTPS_PORT, QUIC_PORT - номера портов для соответствующих протоколов -SKIP_DNSCHECK=1 - отказ от проверки DNS -SKIP_IPBLOCK=1 - отказ от тестов блокировки по порту или IP -SKIP_TPWS=1 - отказ от тестов tpws -SKIP_PKTWS=1 - отказ от тестов nfqws/dvtws/winws -PKTWS_EXTRA, TPWS_EXTRA - дополнительные параметры nfqws/dvtws/winws и tpws, указываемые после основной стратегии -PKTWS_EXTRA_1 .. PKTWS_EXTRA_9, TPWS_EXTRA_1 .. TPWS_EXTRA_9 - отдельно дополнительные параметры, содержащие пробелы -PKTWS_EXTRA_PRE - дополнительные параметры для nfqws/dvtws/winws, указываемые перед основной стратегией -PKTWS_EXTRA_PRE_1 .. PKTWS_EXTRA_PRE_9 - отдельно дополнительные параметры, содержащие пробелы -SECURE_DNS=0|1 - принудительно выключить или включить DoH -DOH_SERVERS - список URL DoH через пробел для автоматического выбора работающего сервера -DOH_SERVER - конкретный DoH URL, отказ от поиска -UNBLOCKED_DOM - незаблокированный домен, который используется для тестов IP block -MIN_TTL,MAX_TTL - пределы тестов с TTL. MAX_TTL=0 отключает тесты. -MIN_AUTOTTL_DELTA,MAX_AUTOTTL_DELTA - пределы тестов с autottl по дельте. MAX_AUTOTTL_DELTA=0 отключает тесты. -SIMULATE=1 - включить режим симуляции для отладки логики скрипта. отключаются реальные запросы через curl, заменяются рандомным результатом. -SIM_SUCCESS_RATE= - вероятность успеха симуляции в процентах -``` - -Пример запуска с переменными:\ -`SECURE_DNS=1 SKIP_TPWS=1 CURL_MAX_TIME=1 CURL=/tmp/curl ./blockcheck.sh` - -**СКАН ПОРТОВ**\ -Если в системе присутствует совместимый `netcat` (ncat от nmap или openbsd ncat. в OpenWrt по умолчанию нет), -то выполняется сканирование портов http или https всех IP адресов домена. -Если ни один IP не отвечает, то результат очевиден. Можно останавливать сканирование. -Автоматически оно не остановится, потому что netcat-ы недостаточно подробно информируют о причинах ошибки. -Если доступна только часть IP, то можно ожидать хаотичных сбоев, т.к. подключение идет к случайному адресу -из списка. - -**ПРОВЕРКА НА ЧАСТИЧНЫЙ IP block**\ -Под частичным блоком подразумевается ситуация, когда коннект на порты есть, но по определенному транспортному -или прикладному протоколу всегда идет реакция DPI вне зависимости от запрашиваемого домена. -Эта проверка так же не выдаст автоматического вердикта/решения, потому что может быть очень много вариаций. -Вместо этого анализ происходящего возложен на самого пользователя или тех, кто будет читать лог. -Суть этой проверки в попытке дернуть неблокированный IP с блокированным доменом и наоборот, анализируя -при этом реакцию DPI. Реакция DPI обычно проявляется в виде таймаута (зависание запроса), connection reset -или http redirect на заглушку. Любой другой вариант скорее всего говорит об отсутствии реакции DPI. -В частности, любые http коды, кроме редиректа, ведущего именно на заглушку, а не куда-то еще. -На TLS - ошибки handshake без задержек. -Ошибка сертификата может говорить как о реакции DPI с MiTM атакой (подмена сертификата), так и -о том, что принимающий сервер неблокированного домена все равно принимает ваш TLS `handshake` с чужим доменом, -пытаясь при этом выдать сертификат без запрошенного домена. Требуется дополнительный анализ. -Если на заблокированный домен есть реакция на всех IP адресах, значит есть блокировка по домену. -Если на неблокированный домен есть реакция на IP адресах блокированного домена, значит имеет место блок по IP. -Соответственно, если есть и то, и другое, значит есть и блок по IP, и блок по домену. -Неблокированный домен первым делом проверяется на доступность на оригинальном адресе. -При недоступности тест отменяется, поскольку он будет неинформативен. - -Если выяснено, что есть частичный блок по IP на DPI, то скорее всего все остальные тесты будут провалены -вне зависимости от стратегий обхода. Но бывают и некоторые исключения. Например, пробитие через `ipv6 -option headers`. Или сделать так, чтобы он не мог распознать протокол прикладного уровня. -Дальнейшие тесты могут быть не лишены смысла. - -**ПРИМЕРЫ БЛОКИРОВКИ ТОЛЬКО ПО ДОМЕНУ БЕЗ БЛОКА ПО IP** - -``` -> testing iana.org on it's original -!!!!! AVAILABLE !!!!! -> testing rutracker.org on 192.0.43.8 (iana.org) -curl: (28) Operation timed out after 1002 milliseconds with 0 bytes received -> testing iana.org on 172.67.182.196 (rutracker.org) -HTTP/1.1 409 Conflict -> testing iana.org on 104.21.32.39 (rutracker.org) -HTTP/1.1 409 Conflict - -> testing iana.org on it's original ip -!!!!! AVAILABLE !!!!! -> testing rutracker.org on 192.0.43.8 (iana.org) -curl: (28) Connection timed out after 1001 milliseconds -> testing iana.org on 172.67.182.196 (rutracker.org) -curl: (35) OpenSSL/3.2.1: error:0A000410:SSL routines::ssl/tls alert handshake failure -> testing iana.org on 104.21.32.39 (rutracker.org) -curl: (35) OpenSSL/3.2.1: error:0A000410:SSL routines::ssl/tls alert handshake failure - -> testing iana.org on it's original ip -!!!!! AVAILABLE !!!!! -> testing rutracker.org on 192.0.43.8 (iana.org) -HTTP/1.1 307 Temporary Redirect -Location: https://www.gblnet.net/blocked.php -> testing iana.org on 172.67.182.196 (rutracker.org) -HTTP/1.1 409 Conflict -> testing iana.org on 104.21.32.39 (rutracker.org) -HTTP/1.1 409 Conflict - -> testing iana.org on it's original ip -!!!!! AVAILABLE !!!!! -> testing rutracker.org on 192.0.43.8 (iana.org) -curl: (35) Recv failure: Connection reset by peer -> testing iana.org on 172.67.182.196 (rutracker.org) -curl: (35) OpenSSL/3.2.1: error:0A000410:SSL routines::ssl/tls alert handshake failure -> testing iana.org on 104.21.32.39 (rutracker.org) -curl: (35) OpenSSL/3.2.1: error:0A000410:SSL routines::ssl/tls alert handshake failure -``` - - -**ПРИМЕР ПОЛНОГО IP БЛОКА ИЛИ БЛОКА TCP ПОРТА ПРИ ОТСУТСТВИИ БЛОКА ПО ДОМЕНУ** - -``` -* port block tests ipv4 startmail.com:80 - ncat -z -w 1 145.131.90.136 80 - 145.131.90.136 does not connect. netcat code 1 - ncat -z -w 1 145.131.90.152 80 - 145.131.90.152 does not connect. netcat code 1 - -* curl_test_http ipv4 startmail.com -- checking without DPI bypass - curl: (28) Connection timed out after 2002 milliseconds - UNAVAILABLE code=28 - -- IP block tests (requires manual interpretation) - -> testing iana.org on it's original ip -!!!!! AVAILABLE !!!!! -> testing startmail.com on 192.0.43.8 (iana.org) -HTTP/1.1 302 Found -Location: https://www.iana.org/ -> testing iana.org on 145.131.90.136 (startmail.com) -curl: (28) Connection timed out after 2002 milliseconds -> testing iana.org on 145.131.90.152 (startmail.com) -curl: (28) Connection timed out after 2002 milliseconds -``` - -## Выбор параметров - -Файл `/opt/zapret/config` используется различными компонентами системы и содержит основные настройки. -Его нужно просмотреть и при необходимости отредактировать. - -На linux системах можно выбрать использовать `iptables` или `nftables`. -По умолчанию на традиционных linux выбирается `nftables`, если установлен nft. -На OpenWrt по умолчанию выбирается `nftables` на новых версиях с firewall4. - -`FWTYPE=iptables` - -На `nftables` можно отключить стандартную схему перехвата трафика после NAT и перейти на перехват до NAT. -Это сделает невозможным применение некоторых методов дурения на проходящем трафике как в случае с `iptables`. -nfqws начнет получать адреса пакетов из локальной сети и отображать их в логах. - -`POSTNAT=0` - -Существует 3 стандартных опции запуска, настраиваемых раздельно и независимо: `tpws-socks`, **tpws**, **nfqws**. -Их можно использовать как по отдельности, так и вместе. Например, вам надо сделать комбинацию -из методов, доступных только в **tpws** и только в **nfqws**. Их можно задействовать вместе. -**tpws** будет прозрачно локализовывать трафик на системе и применять свое дурение, **nfqws** будет дурить трафик, -исходящий с самой системы после обработки на **tpws**. -А можно на эту же систему повесить без параметров socks proxy, чтобы получать доступ к обходу блокировок через прокси. -Таким образом, все 3 режима вполне могут задействоваться вместе. -Так же безусловно и независимо, в добавок к стандартным опциям, применяются все custom скрипты в `init.d/{sysv,openwrt,macos}/custom.d`. - -Однако, при комбинировании tpws и nfqws с пересечением по L3/L4 протоколам не все так просто , как может показаться на первый взгляд. -Первым всегда работает tpws, за ним - nfqws. На nfqws попадает уже "задуренный" трафик от tpws. -Получается, что дурилка дурит дурилку, и дурилка не срабатывает, потому что ее задурили. -Вот такой веселый момент. nfqws перестает распознавать протоколы и применять методы. -Некоторые методы дурения от tpws nfqws в состоянии распознать и отработать корректно, но большинство - нет. -Решение - использование `--dpi-desync-any-protocol` в nfqws и работа как с неизвестным протоколом. -Комбинирование tpws и nfqws является продвинутым вариантом, требующим глубокого понимания происходящего. -Очень желательно проанализировать действия nfqws по `--debug` логу. Все ли так, как вы задумали. - -Одновременное использование tpws и nfqws без пересечения по L3/L4 (то есть nfqws - udp, tpws - tcp или nfqws - port 443, tpws - port 80 или nfqws - ipv4, tpws - ipv6) проблем не представляет. - -`tpws-socks` требует настройки параметров **tpws**, но не требует перехвата трафика. -Остальные опции требуют раздельно настройки перехвата трафика и опции самих демонов. -Каждая опция предполагает запуск одного инстанса соответствующего демона. Все различия методов дурения -для `http`, `https`, `quic` и т.д. должны быть отражены через схему мультистратегий. -В этом смысле настройка похожа на вариант `winws` на Windows, а перенос конфигов не должен представлять больших сложностей. - -Основное правило настройки перехвата - перехватывайте только необходимый минимум. -Любой перехват лишнего - это бессмысленная нагрузка на вашу систему. -Опции демонов `--ipset` использовать нужно с умом. Не стоит перехватывать весь трафик, чтобы потом по параметру --ipset -выделить лишь горстку IP. Это будет работать, но очень неэффективно с точки зрения нагрузки на систему. -Используйте `ipset`-ы режима ядра. При необходимости пишите и задействуйте `custom scripts`. -Но если у вас и так идет работа по всем IP, и нужно написать небольшую специализацию по IP, то --ipset вполне уместен. - -Настройки демонов можно для удобства писать на нескольких строках, используя двойные или одинарные кавычки. -Чтобы задействовать стандартные обновляемые хост-листы из каталога `ipset`, используйте маркер . -Он будет заменен на параметры, соответствующие режиму MODE_FILTER, и будут подставлены реально существующие файлы. -Если MODE_FILTER не предполагает стандартного хостлиста, будет заменен на пустую строку. -Стандартные хостлисты следует вставлять в финальных стратегиях (стратегиях по умолчанию), закрывающих цепочки по -группе параметров фильтра. Таких мест может быть несколько. -Не нужно использовать в узких специализациях и в тех профилях, по которым точно не будет проходить -трафик с известными протоколами, откуда поддерживается извлечение имени хоста (`http`, `tls`, `quic`). - - это вариация, при которой стандартный автолист используется как обычный. -То есть на этом профиле не происходит автоматическое добавление заблокированных доменов. -Но если на другом профиле что-то будет добавлено, то этот профиль примет изменения автоматически. - -***Изменение бита mark для предотвращения зацикливания***\ -`DESYNC_MARK=0x40000000` - -***Изменение бита mark для пометки пакетов, проходящих по POSTNAT схеме (только nftables)***\ -`DESYNC_MARK_POSTNAT=0x20000000` - -***Если раскоментировано, пометка пакетов, которые должны быть обработаны zapret.***\ -`#FILTER_MARK=0x10000000` - -Бит должен быть установлен вашими собственными правилами. -* Для iptables - в цепочках mangle PREROUTING и mangle OUTPUT перед правилами zapret (iptables -I _после_ применения правил zapret). -* Для nftables - в хуках output и prerouting с приоритетом -102 или ниже. - -Критерии пометки любые. Например, IP адрес или интерфейс источника. Это ответ на вопрос "как мне сделать, чтобы телик не ходил через zapret или чтобы через него ходил только мой комп". - -***Включение стандартной опции tpws в режиме socks***\ -`TPWS_SOCKS_ENABLE=0` - -***На каком порту будет слушать tpws socks. прослушивается только localhost и LAN***\ -`TPPORT_SOCKS=987` - -***Параметры tpws для режима socks*** -``` -TPWS_SOCKS_OPT=" ---filter-tcp=80 --methodeol --new ---filter-tcp=443 --split-pos=1,midsld --disorder " -``` - -***Включение стандартной опции tpws в прозрачном режиме***\ -`TPWS_ENABLE=0` - -***Какие tcp порты следует перенаправлять на tpws***\ -`TPWS_PORTS=80,443` - -***Параметры tpws для прозрачного режима*** -``` -TPWS_OPT=" ---filter-tcp=80 --methodeol --new ---filter-tcp=443 --split-pos=1,midsld --disorder " -``` - -***Включение стандартной опции nfqws***\ -`NFQWS_ENABLE=0` - -***Какие tcp и udp порты следует перенаправлять на nfqws с использованием connbytes ограничителя*** - -connbytes позволяет из каждого соединения перенаправить только заданное количество начальных пакетов по каждому направлению - на вход и на выход. -Это более эффективная kernel-mode замена параметра nfqws `--dpi-desync-cutoff=nX`. -``` -NFQWS_PORTS_TCP=80,443 -NFQWS_PORTS_UDP=443 -``` - -***Сколько начальных входящих и исходящих пакетов нужно перенаправлять на nfqws по каждому направлению*** -``` -NFQWS_TCP_PKT_OUT=$((6+$AUTOHOSTLIST_RETRANS_THRESHOLD)) -NFQWS_TCP_PKT_IN=3 -NFQWS_UDP_PKT_OUT=$((6+$AUTOHOSTLIST_RETRANS_THRESHOLD)) -NFQWS_UDP_PKT_IN=0 -``` - -***Задать порты для перенаправления на nfqws без connbytes ограничителя***\ -Есть трафик, исходящий сеанс для которого необходимо перенаправлять весь без ограничителей. -Типичное применение - поддержка http keepalives на stateless DPI. -Это существенно нагружает процессор. Использовать только если понимаете зачем. Чаще всего это не нужно. -Входящий трафик ограничивается по connbytes через параметры PKT_IN. -Если указываете здесь какие-то порты, желательно их убрать из версии с connbytes ограничителем -``` -NFQWS_PORTS_TCP_KEEPALIVE=80 -NFQWS_PORTS_UDP_KEEPALIVE= -``` - -***Параметры nfqws*** -``` -NFQWS_OPT=" ---filter-tcp=80 --dpi-desync=fake,multisplit --dpi-desync-split-pos=method+2 --dpi-desync-fooling=md5sig --new ---filter-tcp=443 --dpi-desync=fake,multidisorder --dpi-desync-split-pos=1,midsld --dpi-desync-fooling=badseq,md5sig --new ---filter-udp=443 --dpi-desync=fake --dpi-desync-repeats=6 -``` - -***Режим фильтрации хостов:*** -``` -none - применять дурение ко всем хостам -ipset - ограничить дурение ipset-ом zapret/zapret6 -hostlist - ограничить дурение списком хостов из файла -autohostlist - режим hostlist + распознавание блокировок и ведение автоматического листа -``` -`MODE_FILTER=none` - -***Настройка системы управления выборочным traffic offload (только если поддерживается)*** -``` -donttouch: выборочное управление отключено, используется системная настройка, простой инсталлятор выключает системную настройку, если она не совместима с выбранным режимом -none: выборочное управление отключено, простой инсталлятор выключает системную настройку -software: выборочное управление включено в режиме software, простой инсталлятор выключает системную настройку -hardware: выборочное управление включено в режиме hardware, простой инсталлятор выключает системную настройку -``` -`FLOWOFFLOAD=donttouch` - -Параметр `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-ов и nfset-ов*** - -``` -SET_MAXELEM=262144 -IPSET_OPT="hashsize 262144 maxelem 2097152" -``` - -Хук, позволяющий внести ip адреса динамически. $1 = имя таблицы\ -Адреса выводятся в stdout. В случае nfset автоматически решается проблема возможного пересечения интервалов.\ -`IPSET_HOOK="/etc/zapret.ipset.hook"` - -***ПРО РУГАНЬ в 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" -``` - -***Настройка режима autohostlist.*** - -При увеличении AUTOHOSTLIST_RETRANS_THRESHOLD и использовании nfqws следует пересмотреть значения параметров -NFQWS_TCP_PKT_OUT и NFQWS_UDP_PKT_OUT. Все ретрансмиссии должны быть получены nfqws, иначе триггер "зависание запроса" не сработает. - -``` -AUTOHOSTLIST_RETRANS_THRESHOLD=3 -AUTOHOSTLIST_FAIL_THRESHOLD=3 -AUTOHOSTLIST_FAIL_TIME=60 -AUTOHOSTLIST_DEBUG=0 -``` - -***Включить или выключить сжатие больших листов в скриптах ipset/\*.sh.*** - -`GZIP_LISTS=1` - -***Команда для перезагрузки ip таблиц фаервола.*** - -Если не указано или пустое, выбирается автоматически ipset или ipfw при их наличии. -На BSD системах с PF нет автоматической загрузки. Там нужно указать команду явно: `pfctl -f /etc/pf.conf` -На более новых pfctl (есть в новых FreeBSD, нет в OpenBSD 6.8) можно дать команду загрузки только таблиц: `pfctl -Tl -f /etc/pf.conf` -"-" означает отключение загрузки листов даже при наличии поддерживаемого backend. -``` -LISTS_RELOAD="pfctl -f /etc/pf.conf" -LISTS_RELOAD=- -``` - -В OpenWrt существует сеть по умолчанию 'lan'. Только трафик с этой сети будет перенаправлен на tpws. -Но возможно задать другие сети или список сетей:\ -`OPENWRT_LAN="lan lan2 lan3"` - -В OpenWrt в качестве wan берутся интерфейсы, имеющие default route. Отдельно для ipv4 и ipv6. -Это можно переопределить: -``` -OPENWRT_WAN4="wan4 vpn" -OPENWRT_WAN6="wan6 vpn6" -``` - -Параметр `INIT_APPLY_FW=1` разрешает init скрипту самостоятельно применять правила iptables.\ -При иных значениях или если параметр закомментирован, правила применены не будут.\ -Это полезно, если у вас есть система управления фаерволом, в настройки которой и следует прикрутить правила.\ -На OpenWrt неприменимо при использовании firewall3+iptables. - -`FILTER_TTL_EXPIRED_ICMP=1` включает механизмы блокировки пакетов icmp time exceeded, высылаемые роутерами по пути следования пакета в ответ на исчерпание TTL/HL. -В linux соединение обрывается системой, если в ответ на первый пакет (для tcp - SYN) пришел такой icmp. Аналогичная схема имеется и в datagram сокетах. -Блокировка icmp идет исключительно за счет средств iptables/nftables. -Чтобы не трогать весь трафик, в режиме PRENAT используется connmark для пометки сеансов, над которыми поработал nfqws. В режиме POSTNAT так сделать нельзя, -поэтому помечаются все сеансы, заворачиваемые на nfqws. -Настройку лучше отключить, если вы не ожидаете проблем от icmp, тк в этом случае будет меньше ненужных вмешательств в трафик. - -***Следующие настройки не актуальны для openwrt:*** - -Если ваша система работает как роутер, то нужно вписать названия внутренних и внешних интерфейсов: -``` -IFACE_LAN=eth0 -IFACE_WAN=eth1 -IFACE_WAN6="henet ipsec0" -``` -Несколько интерфейсов могут быть вписаны через пробел. -Если IFACE_WAN6 не задан, то берется значение IFACE_WAN. - -> [!IMPORTANT] -> Настройка маршрутизации, маскарада и т.д. не входит в задачу zapret. -> Включаются только режимы, обеспечивающие перехват транзитного трафика. -> Возможно определить несколько интерфейсов следующим образом: - -`IFACE_LAN="eth0 eth1 eth2"` - -## Прикручивание к системе управления фаерволом или своей системе запуска - -Если вы используете какую-то систему управления фаерволом, то она может вступать в конфликт -с имеющимся скриптом запуска. При повторном применении правил она могла бы поломать настройки 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 restart_fw -``` - -_А так можно запустить или остановить демоны отдельно от фаервола:_ - -``` -/opt/zapret/init.d/sysv/zapret start_daemons -/opt/zapret/init.d/sysv/zapret stop_daemons -/opt/zapret/init.d/sysv/zapret restart_daemons -``` - -`nftables` сводят практически на нет конфликты между разными системами управления, поскольку позволяют -использовать независимые таблицы и хуки. Используется отдельная nf-таблица "zapret". -Если ваша система ее не будет трогать, скорее всего все будет нормально. - -_Для `nftables` предусмотрено несколько дополнительных вызовов:_ - -Посмотреть set-ы интерфейсов, относящихся к lan, wan и wan6. По ним идет завертывание трафика. -А так же таблицу flow table с именами интерфейсов ingress hook.\ -`/opt/zapret/init.d/sysv/zapret list_ifsets` - -Обновить set-ы интерфейсов, относящихся к lan, wan и wan6. -Для традиционных linux список интерфейсов берется из переменных конфига IFACE_LAN, IFACE_WAN. -Для OpenWrt определяется автоматически. Множество lanif может быть расширено параметром OPENWRT_LAN. -Все интерфейсы lan и wan так же добавляются в ingress hook от flow table.\ -`/opt/zapret/init.d/sysv/zapret reload_ifsets` - -Просмотр таблицы без содержимого set-ов. Вызывает `nft -t list table inet zapret`\ -`/opt/zapret/init.d/sysv/zapret list_table` - -_Так же возможно прицепиться своим скриптом к любой стадии применения и снятия фаервола со стороны zapret скриптов:_ -``` -INIT_FW_PRE_UP_HOOK="/etc/firewall.zapret.hook.pre_up" -INIT_FW_POST_UP_HOOK="/etc/firewall.zapret.hook.post_up" -INIT_FW_PRE_DOWN_HOOK="/etc/firewall.zapret.hook.pre_down" -INIT_FW_POST_DOWN_HOOK="/etc/firewall.zapret.hook.post_down" -``` - -Эти настройки доступны в config. -Может быть полезно, если вам нужно использовать nftables set-ы, например `ipban`/`ipban6`. -nfset-ы принадлежат только одной таблице, следовательно вам придется писать правила для таблицы zapret, -а значит нужно синхронизироваться с применением/снятием правил со стороны zapret скриптов. - -## Вариант custom - -custom скрипты - это маленькие shell программы, управляющие нестандартными режимами применения zapret -или частными случаями, которые не могут быть интегрированы в основную часть без загромождения и замусоривания кода. -Для применения custom следует помещать файлы в следующие директории в зависимости от вашей системы: -``` -/opt/zapret/init.d/sysv/custom.d -/opt/zapret/init.d/openwrt/custom.d -/opt/zapret/init.d/macos/custom.d -``` -Директория будет просканирована в алфавитном порядке, и каждый скрипт будет применен. - -В `init.d` имеется `custom.d.examples.linux`, в `init.d/macos` - `custom.d.examples`. -Это готовые скрипты, которые можно копировать в `custom.d`. Их можно взять за основу для написания собственных. - -***Для linux пишется код в функции*** -``` -zapret_custom_daemons -zapret_custom_firewall -zapret_custom_firewall_nft -zapret_custom_firewall_nft_flush -``` - -***Для macos*** -``` -zapret_custom_daemons -zapret_custom_firewall_v4 -zapret_custom_firewall_v6 -``` - -zapret_custom_daemons поднимает демоны **nfqws**/**tpws** в нужном вам количестве и с нужными вам параметрами. -В первом параметре передается код операции: 1 = запуск, 0 = останов. -Схема запуска демонов в OpenWrt отличается - используется procd. -Поэтому логика останова отсутствует за ненадобностью, останов никогда не вызывается. - -zapret_custom_firewall поднимает и убирает правила `iptables`. -В первом параметре передается код операции: 1 = запуск, 0 = останов. - -zapret_custom_firewall_nft поднимает правила nftables. -Логика останова отсутствует за ненадобностью. Стандартные цепочки zapret удаляются автоматически. -Однако, sets и правила из ваших собственных цепочек не удаляются. -Их нужно подчистить в zapret_custom_firewall_nft_flush. -Если set-ов и собственных цепочек у вас нет, функцию можно не определять или оставить пустой. - -Если вам не нужны iptables или nftables - можете не писать соответствующую функцию. - -В linux можно использовать локальные переменные `FW_EXTRA_PRE` и `FW_EXTRA_POST`.\ -`FW_EXTRA_PRE` добавляет код к правилам ip/nf tables до кода, генерируемого функциями-хелперами.\ -`FW_EXTRA_POST` добавляет код после. - -В linux функции-хелперы добавляют правило в начало цепочек, то есть перед уже имеющимися. -Поэтому специализации должны идти после более общих вариантов. -Для macos правило обратное. Там правила добавляются в конец. -По этой же причине фаервол в Linux сначала применяется в стандартном режиме, потом custom, -а в MacOS сначала custom, потом стандартный режим. - -В macos firewall-функции ничего сами никуда не заносят. Их задача - лишь выдать текст в stdout, -содержащий правила для pf-якоря. Остальное сделает обертка. - -Особо обратите внимание на номер демона в функциях `run_daemon` , `do_daemon`, `do_tpws`, `do_tpws_socks`, `do_nfqws` , -номера портов **tpws** и очередей **nfqueue**. -Они должны быть уникальными во всех скриптах. При накладке будет ошибка. -Поэтому используйте функции динамического получения этих значений из пула. - -`custom` скрипты могут использовать переменные из `config`. Можно помещать в `config` свои переменные -и задействовать их в скриптах. -Можно использовать функции-хелперы. Они являются частью общего пространства функций shell. -Полезные функции можно взять из примеров скриптов. Так же смотрите `common/*.sh`. -Используя хелпер функции, вы избавитесь от необходимости учитывать все возможные случаи -типа наличия/отсутствия ipv6, является ли система роутером, имена интерфейсов, ...Хелперы это учитывают. Вам нужно сосредоточиться лишь на фильтрах `{ip,nf}tables` и параметрах демонов. - -## Простая установка - -`install_easy.sh` автоматизирует ручные варианты процедур установки. -Он поддерживает OpenWrt, linux системы на базе systemd или openrc и MacOS. - -Для более гибкой настройки перед запуском инсталлятора следует выполнить раздел "Выбор параметров". - -Если система запуска поддерживается, но используется не поддерживаемый инсталлятором менеджер пакетов -или названия пакетов не соответствуют прописанным в инсталлятор, пакеты нужно установить вручную. -Всегда требуется curl. `ipset` - только для режима `iptables`, для `nftables` - не нужен. - -Для совсем обрезанных дистрибутивов (alpine) требуется отдельно установить `iptables` и `ip6tables`, либо `nftables`. - -В комплекте идут статические бинарники для большинства архитектур. Какой-то из них подойдет -с вероятностью 99%. Но если у вас экзотическая система, инсталлятор попробует собрать бинарники сам -через make. Для этого нужны gcc, make и необходимые **-dev** пакеты. Можно форсировать режим -компиляции следующим вызовом: - -`install_easy.sh make` - -Под OpenWrt все уже сразу готово для использования системы в качестве роутера. -Имена интерфейсов WAN и LAN известны из настроек системы. -Под другими системами роутер вы настраиваете самостоятельно. Инсталлятор в это не вмешивается. -Инсталлятор в зависимости от выбранного режима может спросить LAN и WAN интерфейсы. -Нужно понимать, что заворот проходящего трафика на **tpws** в прозрачном режиме происходит до выполнения маршрутизации, -следовательно возможна фильтрация по LAN и невозможна по WAN. -Решение о завороте на **tpws** локального исходящего трафика принимается после выполнения маршрутизации, -следовательно ситуация обратная: LAN не имеет смысла, фильтрация по WAN возможна. -Заворот на **nfqws** происходит всегда после маршрутизации, поэтому к нему применима только фильтрация по WAN. -Возможность прохождения трафика в том или ином направлении настраивается вами в процессе конфигурации роутера. - -Деинсталляция выполняется через `uninstall_easy.sh`. После выполнения деинсталляции можно удалить каталог `/opt/zapret`. - -## Установка под systemd - -Если вам нравится systemd и хочется максимально под него заточиться, можно отказаться от скриптов запуска zapret -и поднимать инстансы `tpws` и `nfqws` как отдельные юниты systemd. При этом вам придется вручную написать правила iptables/nftables -и каким-то образом их поднимать. Например, написать дополнительный systemd unit для этого. -Так же требуется собрать бинарники особым образом через `make systemd`. - -В комплекте zapret есть шаблоны `init.d/systemd/{nfqws@.service,tpws@.service}`. -Краткий перечень команд для их использования приведен в комментариях в этих файлах. - -## Простая установка на openwrt - -Работает только если у вас на роутере достаточно места. - -Копируем zapret на роутер в `/tmp`. - -Запускаем установщик:\ -`sh /tmp/zapret/install_easy.sh` - -Он скопирует в `/opt/zapret` только необходимый минимум файлов. - -После успешной установки можно удалить zapret из tmp для освобождения RAM:\ -`rm -r /tmp/zapret` - -Для более гибкой настройки перед запуском инсталлятора следует выполнить раздел "Выбор параметров". - -Система простой инсталяции заточена на любое умышленное или неумышленное изменение прав доступа на файлы. -Устойчива к репаку под windows. После копирования в `/opt` права будут принудительно восстановлены. - - -## Установка на openwrt в режиме острой нехватки места на диске - -Требуется около 120-200 кб на диске. Придется отказаться от всего, кроме **tpws**. - -**Инструкция для openwrt 22 и выше с nftables** - -Никаких зависимостей устанавливать не нужно. - -***Установка:*** - -1) Скопируйте все из `init.d/openwrt-minimal/tpws/*` в корень openwrt. -2) Скопируйте бинарник **tpws** подходящей архитектуры в `/usr/bin/tpws`. -3) Установите права на файлы: `chmod 755 /etc/init.d/tpws /usr/bin/tpws` -4) Отредактируйте `/etc/config/tpws` -* Если не нужен ipv6, отредактируйте `/etc/nftables.d/90-tpws.nft` и закомментируйте строки с редиректом ipv6. -5) `/etc/init.d/tpws enable` -6) `/etc/init.d/tpws start` -7) `fw4 restart` - -***Полное удаление:*** - -1) `/etc/init.d/tpws disable` -2) `/etc/init.d/tpws stop` -3) `rm -f /etc/nftables.d/90-tpws.nft /etc/firewall.user /etc/init.d/tpws /usr/bin/tpws` -4) `fw4 restart` - -**Инструкция для openwrt 21 и ниже с iptables** - -***Установите зависимости:*** -1) `opkg update` -2) `opkg install iptables-mod-extra` -* только для IPV6: `opkg install ip6tables-mod-nat` - -Убедитесь, что в `/etc/firewall.user` нет ничего значимого. -Если есть - не следуйте слепо инструкции. Объедините код или создайте свой `firewall include` в `/etc/config/firewall`. - -***Установка:*** - -1) Скопируйте все из `init.d/openwrt-minimal/tpws/*` в корень openwrt. -2) Скопируйте бинарник **tpws** подходящей архитектуры в `/usr/bin/tpws`. -3) Установите права на файлы: `chmod 755 /etc/init.d/tpws /usr/bin/tpws` -4) Отредактируйте `/etc/config/tpws` -* Если не нужен ipv6, отредактируйте /etc/firewall.user и установите там DISABLE_IPV6=1. -5) `/etc/init.d/tpws enable` -6) `/etc/init.d/tpws start` -7) `fw3 restart` - -***Полное удаление:*** - -1) `/etc/init.d/tpws disable` -2) `/etc/init.d/tpws stop` -3) `rm -f /etc/nftables.d/90-tpws.nft /etc/firewall.user /etc/init.d/tpws` -4) `touch /etc/firewall.user` -5) `fw3 restart` - - -## Android - -Без рута забудьте про nfqws и tpws в режиме transparent proxy. tpws будет работать только в режиме `--socks`. - -Ядра Android имеют поддержку NFQUEUE. nfqws работает. - -В стоковых ядрах нет поддержки ipset. В общем случае сложность задачи по поднятию ipset варьируется от -"не просто" до "почти невозможно". Если только вы не найдете готовое собранное ядро под ваш девайс. - -tpws будет работать в любом случае, он не требует чего-либо особенного. - -Хотя linux варианты под Android работают, рекомендуется использовать специально собранные под bionic бинарники. -У них не будет проблем с DNS, с локальным временем и именами юзеров и групп.\ -Рекомендуется использовать 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 - -**nfqws** может иметь такой глюк. При запуске с uid по умолчанию (0x7FFFFFFF) при условии работы на сотовом интерфейсе -и отключенном кабеле внешнего питания система может частично виснуть. Перестает работать тач и кнопки, -но анимация на экране может продолжаться. Если экран был погашен, то включить его кнопкой power невозможно. -Изменение UID на низкий (--uid 1 подойдет) позволяет решить эту проблему. -Глюк был замечен на android 8.1 на девайсе, основанном на платформе mediatek. - -Ответ на вопрос куда поместить tpws на android без рута, чтобы потом его запускать из приложений. -Файл заливаем через adb shell в /data/local/tmp/, лучше всего в субфолдер. -``` -mkdir /data/local/tmp/zapret -adb push tpws /data/local/tmp/zapret -chmod 755 /data/local/tmp/zapret /data/local/tmp/zapret/tpws -chcon u:object_r:system_file:s0 /data/local/tmp/zapret/tpws -``` -Как найти стратегию обхода сотового оператора: проще всего раздать инет на комп. -Для этого подойдет любая поддерживаемая ОС. Подключите android через USB кабель к компу и включите режим модема. -Прогоните стандартную процедуру blockcheck. При переносе правил на телефон уменьшить TTL на 1, -если правила с TTL присутствуют в стратегии. Если проверялось на windows, убрать параметры `--wf-*`. - -Работа blockcheck в android shell не поддерживается, но имея рута можно развернуть rootfs какого-нибудь дистрибутива linux. -Это лучше всего делать с компа через adb shell. -Если компа нет, то развертка chroot - единственный вариант, хотя и неудобный. -Подойдет что-то легковесное, например, alpine или даже OpenWrt. -Если это не эмулятор android, то универсальная архитектура - arm (любой вариант). -Если вы точно знаете, что ОС у вас 64-разрядная, то лучше вместо arm - arm64. -Выяснить архитектуру можно командой `uname -a`. - -``` -mount --bind /dev /data/linux/dev -mount --bind /proc /data/linux/proc -mount --bind /sys /data/linux/sys -chroot /data/linux -``` - -Первым делом вам нужно будет один раз настроить DNS. Сам он не заведется. - -`echo nameserver 1.1.1.1 >/etc/resolv.conf` - -Далее нужно средствами пакетного менеджера установить iptables-legacy. Обязательно **НЕ** iptables-nft, -который, как правило, присутствует по умолчанию. В ядре android нет nftables.\ -`ls -la $(which iptables)`\ -Линк должен указывать на legacy вариант. -Если нет, значит устанавливайте нужные пакеты вашего дистрибутива, и убеждайтесь в правильности ссылок.\ -`iptables -S`\ -Так можно проверить, что ваш `iptables` увидел то, что туда насовал android. `iptables-nft` выдаст ошибку. -Далее качаем zapret в `/opt/zapret`. Обычные действия с `install_prereq.sh`, `install_bin.sh`, `blockcheck.sh`. - -Учтите, что стратегии обхода сотового оператора и домашнего wifi вероятно будут разные. -Выделить сотового оператора легко через параметр iptables `-o <имя интерфейса>`. Имя может быть, например, `ccmni0`. -Его легко увидеть через `ifconfig`. -Wifi сеть - обычно `wlan0`. - -Переключать blockcheck между оператором и wifi можно вместе со всем инетом - включив или выключив wifi. -Если найдете стратегию для wifi и впишите ее в автостарт, то при подключении к другому wifi -она может не сработать или вовсе что-то поломать, потому подумайте стоит ли. -Может быть лучше сделать скрипты типа "запустить обход домашнего wifi", "снять обход домашнего wifi", -и пользоваться ими по необходимости из терминала. -Но домашний wifi лучше все-же обходить на роутере. - - -## Мобильные модемы и роутеры huawei - -Устройства типа E3372, E8372, E5770 разделяют общую идеологию построения системы. -Имеются 2 вычислительных ядра. Одно ядро выполняет vxworks, другое - linux. -На 4pda имеются модифицированные прошивки с telnet и adb. Их и нужно использовать. - -Дальнейшие утверждения проверены на E8372. На других может быть аналогично или похоже. -Присутствуют дополнительные аппаратные блоки для offload-а сетевых функций. -Не весь трафик идет через linux. Исходящий трафик с самого модема проходит -цепочку OUTPUT нормально, на FORWARD =>wan часть пакетов выпадает из tcpdump. - -tpws работает обычным образом. - -`nfqueue` поломан, можно собрать фиксящий модуль https://github.com/im-0/unfuck-nfqueue-on-e3372h, -используя исходники с huawei open source. Исходники содержат тулчейн и полусобирающееся, -неактуальное ядро. Конфиг можно взять с рабочего модема из `/proc/config.gz`. -С помощью этих исходников умельцы могут собрать модуль `unfuck_nfqueue.ko`. -После его применения NFQUEUE и nfqws для arm работают нормально. - -Чтобы избежать проблемы с offload-ом при использовании nfqws, следует комбинировать tpws в режиме tcp proxy и nfqws. -Правила NFQUEUE пишутся для цепочки OUTPUT. -connbytes придется опускать, поскольку модуля в ядре нет. Но это не смертельно. - -Скрипт автозапуска - `/system/etc/autorun.sh`. Создайте свой скрипт настройки zapret, -запускайте из конца autorun.sh через "&". Скрипт должен в начале делать sleep 5, чтобы дождаться -поднятия сети и iptables от huawei. - -> [!WARNING] -> На этом модеме происходят хаотические сбросы соединений tcp по непонятным причинам. -> Выглядит это так, если запускать curl с самого модема: -``` -curl www.ru -curl: (7) Failed to connect to www.ru port 80: Host is unreachable -``` -Возникает ошибка сокета EHOSTUNREACH (errno -113). То же самое видно в tpws. -В броузере не подгружаются части веб страниц, картинки, стили. -В tcpdump на внешнем интерфейсе eth_x виден только единственный и безответный SYN пакет, без сообщений ICMP. -ОС каким-то образом узнает о невозможности установить TCP соединение и выдает ошибку. -Если выполнять подключение с клиента, то SYN пропадают, соединение не устанавливается. -ОС клиента проводит ретрансмиссию, и с какого-то раза подключение удается. -Поэтому без tcp проксирования в этой ситуации сайты тупят, но загружаются, а с проксированием -подключение выполняется, но вскоре сбрасывается без каких-либо данных, и броузеры не пытаются установить -его заново. Поэтому качество броузинга с tpws может быть хуже, но дело не в tpws. -Частота сбросов заметно возрастает, если запущен торент клиент, имеется много tcp соединений. -Однако, причина не в переполнении таблицы conntrack. Увеличение лимитов и очистка conntrack не помогают. -Предположительно эта особенность связана с обработкой пакетов сброса соединения в hardware offload. -Точного ответа на вопрос у меня нет. Если вы знаете - поделитесь, пожалуйста. -Чтобы не ухудшать качество броузинга, можно фильтровать заворот на tpws по ip фильтру. -Поддержка ipset отсутствует. Значит, все, что можно сделать - создать индивидуальные правила -на небольшое количество хостов. - -Некоторые наброски скриптов присутствуют в [files/huawei](./../files/huawei/). _Не готовое решение!_ Смотрите, изучайте, приспосабливайте.\ -Здесь можно скачать готовые полезные статические бинарники для arm, включая curl : https://github.com/bol-van/bins - - -## FreeBSD, OpenBSD, MacOS - -Описано в [документации BSD](./bsd.md) - -## Windows - -Описано в [документации Windows](./windows.md) - - -## Другие прошивки - -Для статических бинарников не имеет значения на чем они запущены: PC, android, приставка, роутер, любой другой девайс. -Подойдет любая прошивка, дистрибутив linux. Статические бинарники запустятся на всем. -Им нужно только ядро с необходимыми опциями сборки или модулями. -Но кроме бинарников в проекте используются еще и скрипты, в которых задействуются некоторые -стандартные программы. - -Основные причины почему нельзя просто так взять и установить эту систему на что угодно: -* отсутствие доступа к девайсу через shell -* отсутствие рута -* отсутствие раздела r/w для записи и энергонезависимого хранения файлов -* отсутствие возможности поставить что-то в автозапуск -* отсутствие cron -* неотключаемый flow offload или другая проприетарщина в netfilter -* недостаток модулей ядра или опций его сборки -* недостаток модулей 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. -С их помощью можно компенсировать недостаток ПО основной прошивки, за исключением ядра. - -Можно попытаться использовать sysv init script таким образом, как это описано в разделе -"Прикручивание к системе управления фаерволом или своей системе запуска". -В случае ругани на отсутствие каких-то базовых программ, их следует восполнить посредством entware. -Перед запуском скрипта путь к дополнительным программам должен быть помещен в PATH. - -_Подробное описание настроек для других прошивок выходит за рамки данного проекта._ - -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 и репозитория софта -* flow offload предсказуемо, стандартно и выборочно управляем, а так же отключаем -* в репозитории есть все модули ядра, их можно доустановить через opkg. ядро пересобирать не нужно. -* в репозитории есть все модули iptables, их можно доустановить через opkg -* в репозитории есть огромное количество стандартных программ и дополнительного софта -* наличие SDK, позволяющего собрать недостающее - - -## Обход блокировки через сторонний хост - -Если не работает автономный обход, приходится перенаправлять трафик через сторонний хост. -Предлагается использовать прозрачный редирект через socks5 посредством `iptables+redsocks`, либо `iptables+iproute+vpn`. -Настройка варианта с redsocks на OpenWrt описана в [redsocks.txt](./redsocks.txt). -Настройка варианта с `iproute+wireguard` - в [wireguard_iproute_openwrt.txt](./wireguard_iproute_openwrt.txt). - - -## Почему стоит вложиться в покупку 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-прокси и прописать его в браузер. Знания linux не нужны совсем. -Это вариант наименее напряжный для чайников, хотя и не самый удобный в использовании. - -## Поддержать разработчика - -USDT `0x3d52Ce15B7Be734c53fc9526ECbAB8267b63d66E` - -BTC `bc1qhqew3mrvp47uk2vevt5sctp7p2x9m7m5kkchve` - -ETH `0x3d52Ce15B7Be734c53fc9526ECbAB8267b63d66E` diff --git a/docs/redsocks.txt b/docs/redsocks.txt deleted file mode 100644 index 48bfaff3..00000000 --- a/docs/redsocks.txt +++ /dev/null @@ -1,194 +0,0 @@ -Данный мануал пишется не как копипастная инструкция, а как помощь уже соображающему. -Если вы не знаете основ сетей, linux, openwrt, а пытаетесь что-то скопипастить отсюда без малейшего -понимания смысла, то маловероятно, что у вас что-то заработает. Не тратье свое время напрасно. -Цель - донести принципы как это настраивается вообще, а не указать какую буковку где вписать. - - -Прозрачный выборочный заворот tcp соединений на роутере через socks - -Tor поддерживает "из коробки" режим transparent proxy. Это можно использовать в теории, но практически - только на роутерах с 128 мб памяти и выше. И тор еще и тормозной. -Другой вариант напрашивается, если у вас есть доступ к какой-нибудь unix системе с SSH, где сайты не блокируются. Например, у вас есть VPS вне России. -Понятийно требуются следующие шаги : -1) Выделять IP, на которые надо проксировать трафик. У нас уже имеется ipset "zapret", технология создания которого отработана. -2) Сделать так, чтобы все время при загрузке системы на некотором порту возникал socks. -3) Установить transparent соксификатор. Redsocks прекрасно подошел на эту роль. -4) Завернуть через iptables или nftables трафик с порта назначения 443 и на ip адреса из ipset/nfset 'zapret' на соксификатор -Тоже самое сделать с ipset/nfset 'ipban' для всех tcp портов. -Буду рассматривать систему на базе openwrt, где уже установлена система обхода dpi "zapret". -Если вам не нужны функции обхода DPI, его можно не включать. Обновление фильтра от этого не зависит. - - -* Сделать так, чтобы все время при загрузке системы на некотором порту возникал 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 и shadow-su. ------------------- -useradd -s /bin/false -d /home/proxy proxy -mkdir -p /home/proxy -chown proxy:proxy /home/proxy ------------------- -Сгенерируем ключ RSA для доступа к ssh серверу. ------------------- -su -s /bin/ash 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.127; - local_port = 1099; - ip = 127.0.0.1; - port = 1098; - type = socks5; -} ---------------------------- - -После чего перезапускаем : /etc/init.d/redsocks restart -Смотрим появился ли листенер : netstat -tnlp | grep 1099 - -В zapret для перенаправления DNAT на интерфейс lo используется 127.0.0.127. -Ко всем остальным адресам из 127.0.0.0/8 DNAT может быть заблокирован. Читайте readme.txt про route_localnet. - -* Завертывание соединений через iptables - -!! Версии OpenWRT до 21.02 включительно используют iptables + fw3. Более новые перешили на nftables по умолчанию. -!! В новых OpenWRT можно снести firewall4 и nftables, заменив их на firewall3 + iptables -!! Инструкция относится только к openwrt, где используется iptables. - -Будем завертывать любые tcp соединения на ip из ipset "ipban" и https на ip из ipset "zapret", за исключением ip из ipset "nozapret". - ---- /etc/firewall.user ----- -SOXIFIER_PORT=1099 - -. /opt/zapret/init.d/openwrt/functions - -create_ipset no-update - -network_find_wan4_all wan_iface -for ext_iface in $wan_iface; do - network_get_device ext_device $ext_iface - ipt OUTPUT -t nat -o $ext_device -p tcp --dport 443 -m set --match-set zapret dst -m set ! --match-set nozapret dst -j REDIRECT --to-port $SOXIFIER_PORT - ipt OUTPUT -t nat -o $ext_device -p tcp -m set --match-set ipban dst -m set ! --match-set nozapret dst -j REDIRECT --to-port $SOXIFIER_PORT -done - -prepare_route_localnet - -ipt prerouting_lan_rule -t nat -p tcp --dport 443 -m set --match-set zapret dst -m set ! --match-set nozapret -j DNAT --to $TPWS_LOCALHOST4:$SOXIFIER_PORT -ipt prerouting_lan_rule -t nat -p tcp -m set --match-set ipban dst -m set ! --match-set nozapret -j DNAT --to $TPWS_LOCALHOST4:$SOXIFIER_PORT ----------------------------- - -Внести параметр "reload" в указанное место : ---- /etc/config/firewall --- -config include - option path '/etc/firewall.user' - option reload '1' ----------------------------- - -Перезапуск firewall : /etc/init.d/firewall restart - - -* Завертывание соединений через nftables - -!! Только для версий OpenWRT старше 21.02 - -nftables не могут использовать ipset. Вместо ipset существует аналог - nfset. -nfset является частью таблицы nftable и принадлежит только к ней. Адресация nfset из другой nftable невозможна. -Скрипты ipset/* в случае nftables используют nfset-ы в таблице zapret. -Чтобы использовать эти nfset-ы в своих правилах, необходимо синхронизироваться с их созданием и вносить свои цепочки в nftable "zapret". -Для этого существуют хуки - скрипты, вызываемые из zapret на определенных стадиях инициализации фаервола. - -Раскоментируейте в /opt/zapret/config строчку -INIT_FW_POST_UP_HOOK="/etc/firewall.zapret.hook.post_up" - -Создайте файл /etc/firewall.zapret.hook.post_up и присвойте ему chmod 755. - ---- /etc/firewall.zapret.hook.post_up --- -#!/bin/sh - -SOXIFIER_PORT=1099 - -. /opt/zapret/init.d/openwrt/functions - -cat << EOF | nft -f - 2>/dev/null - delete chain inet $ZAPRET_NFT_TABLE my_output - delete chain inet $ZAPRET_NFT_TABLE my_prerouting -EOF - -prepare_route_localnet - -cat << EOF | nft -f - - add chain inet $ZAPRET_NFT_TABLE my_output { type nat hook output priority -102; } - flush chain inet $ZAPRET_NFT_TABLE my_output - add rule inet $ZAPRET_NFT_TABLE my_output oifname @wanif meta l4proto tcp ip daddr @ipban ip daddr != @nozapret dnat to $TPWS_LOCALHOST4:$SOXIFIER_PORT - add rule inet $ZAPRET_NFT_TABLE my_output oifname @wanif tcp dport 443 ip daddr @zapret ip daddr != @nozapret dnat to $TPWS_LOCALHOST4:$SOXIFIER_PORT - - add chain inet $ZAPRET_NFT_TABLE my_prerouting { type nat hook prerouting priority -102; } - flush chain inet $ZAPRET_NFT_TABLE my_prerouting - add rule inet $ZAPRET_NFT_TABLE my_prerouting iifname @lanif meta l4proto tcp ip daddr @ipban ip daddr != @nozapret dnat to $TPWS_LOCALHOST4:$SOXIFIER_PORT - add rule inet $ZAPRET_NFT_TABLE my_prerouting iifname @lanif tcp dport 443 ip daddr @zapret ip daddr != @nozapret dnat to $TPWS_LOCALHOST4:$SOXIFIER_PORT -EOF ----------------------------- - -Перезапуск firewall : /etc/init.d/zapret restart_fw - - -* Проверка - -Все, теперь можно проверять : -/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 -# должно выдать страницу diff --git a/docs/windows.en.md b/docs/windows.en.md deleted file mode 100644 index 0a4784fd..00000000 --- a/docs/windows.en.md +++ /dev/null @@ -1,181 +0,0 @@ -### tpws - -Using `WSL` (Windows subsystem for Linux) it's possible to run `tpws` in socks mode under rather new builds of -windows 10 and windows server. -Its not required to install any linux distributions as suggested in most articles. -tpws is static binary. It doesn't need a distribution. - -Install `WSL` : `dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all` - -From release copy `binaries/linux-x86_64/tpws_wsl.tgz` to the target system. -Run : `wsl --import tpws "%USERPROFILE%\tpws" tpws_wsl.tgz` - -Run tpws : `wsl -d tpws --exec /tpws --uid=1 --no-resolve --socks --bind-addr=127.0.0.1 --port=1080 ` - -Configure socks as `127.0.0.1:1080` in a browser or another program. - -Cleanup : `wsl --unregister tpws` - -Tested in windows 10 build 19041 (20.04) with WSL1. - -`--oob` , `--mss` and `--disorder` do not work. -RST detection in autohostlist scheme may not work. -WSL may glitch with splice. `--nosplice` may be required. - - -### winws - -`winws` is `nfqws` version for windows. It's based on `windivert`. Most functions are working. -Large ip filters (ipsets) are not possible. Forwarded traffic and connection sharing are not supported. -Administrator rights are required. - -Working with packet filter consists of two parts - -1. In-kernel packet selection and passing selected packets to a packet filter in user mode. -In *nix it's done by `iptables`, `nftables`, `pf`, `ipfw`. -2. User mode packet filter processes packets and does DPI bypass magic. - -Windows does not have part 1. No `iptables` exist. That's why 3rd party packet redirector is used. -It's called `windivert`. It works starting from `windows 7`. Kernel driver is signed but it may require to disable secure boot -or update windows 7. Read below for windows 7 windivert signing info. - -Task of `iptables` is done inside `winws` through `windivert` filters. `Windivert` has it's own [filter language](https://reqrypt.org/windivert-doc.html#filter_language). -`winws` can automate filter construction using simple ip version and port filter. Raw filters are also supported. - -``` - --wf-iface=[:] ; numeric network interface and subinterface indexes - --wf-l3=ipv4|ipv6 ; L3 protocol filter. multiple comma separated values allowed. - --wf-tcp=[~]port1[-port2] ; TCP port filter. ~ means negation. multiple comma separated values allowed. - --wf-udp=[~]port1[-port2] ; UDP port filter. ~ means negation. multiple comma separated values allowed. - --wf-raw-part=|@ ; partial raw windivert filter string or filename - --wf-filter-lan=0|1 ; add excluding filter for non-global IP (default : 1) - --wf-raw=|@ ; full raw windivert filter string or filename. replaces --wf-tcp,--wf-udp,--wf-raw-part - --wf-save= ; save windivert filter string to a file and exit - --ssid-filter=ssid1[,ssid2,ssid3,...] ; enable winws only if any of specified wifi SSIDs connected - --nlm-filter=net1[,net2,net3,...] ; enable winws only if any of specified NLM network is connected. names and GUIDs are accepted. - --nlm-list[=all] ; list Network List Manager (NLM) networks. connected only or all. -``` - -`--wf-l3`, `--wf-tcp`, `--wf-udp` can take multiple comma separated arguments. - -Interface indexes can be discovered using this command : `netsh int ip show int` - -If you can't find index this way use `winws --debug` to see index there. Subinterface index is almost always 0 and you can omit it. - -`--wf-raw-part` specifies partial windivert filter. Multiple filter parts are supported. They can also be combined with `--wf-tcp`,`--wf-udp`. - -`--wf-raw` specifies full windivert filter that replaces `--wf-tcp`,`--wf-udp`,`--wf-raw-part`. - -Kernel filtering with windivert language is much more effective than passing massive amount of traffic to winws. Use it if possible to save CPU resources. - -Multiple `winws` processes are allowed. However, it's discouraged to intersect their filters. - -`--ssid-filter` allows to enable `winws` only if specified wifi networks are connected. `winws` auto detects SSID appearance and disappearance. -SSID names must be written in the same case as the system sees them. This option does not analyze routing and does not detect where traffic actually goes. -If multiple connections are available, the only thing that triggers `winws` operation is wifi connection presence. That's why it's a good idea to add also `--wf-iface` filter to not break ethernet, for example. - -`--nlm-filter` is like `--ssid-filter` but works with names or GUIDs from Network List Manager. NLM names are those you see in Control Panel "Network and Sharing Center". -NLM networks are adapter independent. Usually MAC address of the default router is used to distinugish networks. NLM works with any type of adapters : ethernet, wifi, vpn and others. -That's why NLM is more universal than `ssid-filter`. - -`Cygwin` shell does not run binaries if their directory has it's own copy of `cygwin1.dll`. -If you want to run `winws` from `cygwin` delete, rename or move `cygwin1.dll`. -`Cygwin` is required for `blockcheck.sh` support but `winws` itself can be run standalone without cygwin. - -How to get `windows 7` and `winws` compatible `cygwin` : -``` -curl -O https://www.cygwin.com/setup-x86_64.exe -setup-x86_64.exe --allow-unsupported-windows --no-verify --site http://ctm.crouchingtigerhiddenfruitbat.org/pub/cygwin/circa/64bit/2024/01/30/231215 -``` -You must choose to install `curl`. To compile from sources install `gcc-core`,`make`,`zlib-devel`. -Make from directory `nfq` using `make cygwin64` or `make cygwin32` for 64 and 32 bit versions. - -`winws` requires `cygwin1.dll`, `windivert.dll`, `windivert64.sys` or `windivert32.sys`. -You can take them from `binaries/windows-x86_64` or `binaries/windows-x86`. - -There's no `arm64` signed `windivert` driver and no `cygwin`. -But it's possible to use unsigned driver version in test mode and user mode components with x64 emulation. -x64 emulation requires `windows 11` and not supported in `windows 10`. - -### windows 7 windivert signing - -Requirements for windows driver signing have changed in 2021. -Official free updates of windows 7 ended in 2020. -After 2020 for the years paid updates were available (ESU). -One of the updates from ESU enables signatures used in windivert 2.2.2-A. -There are several options : - -1. Take `windivert64.sys` and `windivert.dll` version `2.2.0-C` or `2.2.0-D` from [here](https://reqrypt.org/download). -Replace these 2 files in every location they are present. -In `zapret-win-bundle` they are in `zapret-winws` и `blockcheck/zapret/nfq` folders. -However this option still requires 10+ year old patch that enables SHA256 signatures. -If you're using win bundle you can simply run `win7\install_win7.cmd` - -3. [Hack ESU](https://hackandpwn.com/windows-7-esu-patching) - -4. Use `UpdatePack7R2` from simplix : https://blog.simplix.info -If you are in Russia or Belarus temporary change region in Control Panel. - -### blockcheck - -`blockcheck.sh` is written in posix shell and uses some standard posix utilites. -Windows does not have them. To execute `blockcheck.sh` use `cygwin` command prompt run as administrator. -It's not possible to use `WSL`. It's not the same as `cygwin`. -First run once `install_bin.sh` then `blockcheck.sh`. - -Backslashes in windows paths shoud be doubled. Or use cygwin path notation. -``` -cd "C:\\Users\\vasya" -cd "C:/Users/vasya" -cd "/cygdrive/c/Users/vasya" -``` -`Cygwin` shell does not run binaries if their directory has it's own copy of `cygwin1.dll`. -If you want to run `winws` from `cygwin` delete, rename or move `cygwin1.dll`. - -`Cygwin` is required only for `blockcheck.sh`. Standalone `winws` can be run without it. - -To simplify things it's advised to use `zapret-win-bundle`. - -### zapret-win-bundle - -To make your life easier there's ready to use [bundle](https://github.com/bol-van/zapret-win-bundle) with `cygwin`,`blockcheck` and `winws`. - -* `/zapret-winws` - standalone version of `winws` for everyday use. does not require any other folders. -* `/zapret-winws/_CMD_ADMIN.cmd` - open `cmd` as administrator in the current folder -* `/blockcheck/blockcheck.cmd` - run `blockcheck` with logging to `blockcheck/blockcheck.log` -* `/cygwin/cygwin.cmd` - run `cygwin` shell as current user -* `/cygwin/cygwin-admin.cmd` - run `cygwin` shell as administrator - -There're aliases in cygwin shell for `winws`,`blockcheck`,`ip2net`,`mdig`. No need to mess with paths. -It's possible to send signals to `winws` using standard unix utilites : `pidof,kill,killall,pgrep,pkill`. -`Cygwin` shares common process list per `cygwin1.dll` copy. If you run a `winws` from `zapret-winws` -you won't be able to `kill` it because this folder contain its own copy of `cygwin1.dll`. - -It's possible to use `cygwin` shell to make `winws` debug log. Use `tee` command like this : - -``` -winws --debug --wf-tcp=80,443 | tee winws.log -unix2dos winws.log -``` - -`winws.log` will be in `cygwin/home/`. `unix2dos` helps with `windows 7` notepad. It's not necessary in `Windows 10` and later. - -Because 32-bit systems are rare nowadays `zapret-win-bundle` exists only for `Windows x64/arm64`. - -### auto start - -To start `winws` with windows use windows task scheduler. There are `task_*.cmd` batch files in `binaries/windows-x86-64/zapret-winws`. -They create, remove, start and stop scheduled task `winws1`. They must be run as administrator. - -Edit `task_create.cmd` and write your `winws` parameters to `%WINWS1%` variable. If you need multiple `winws` instances -clone the code in all cmd files to support multiple tasks `winws1,winws2,winws3,...`. - -Tasks can also be controlled from GUI `taskschd.msc`. - -Also you can use windows services the same way with `service_*.cmd`. - -### Windows Server - -winws is linked against wlanapi.dll which is absent by default. -To solve this problem run power shell as administrator and execute command `Install-WindowsFeature -Name Wireless-Networking`. -Then reboot the system. diff --git a/docs/windows.md b/docs/windows.md deleted file mode 100644 index 486aded4..00000000 --- a/docs/windows.md +++ /dev/null @@ -1,281 +0,0 @@ -# Windows - -## tpws - -Запуск tpws возможен только в Linux варианте под **WSL** _(Windows Subsystem for Linux)_. -Нативного варианта под Windows нет, поскольку он использует epoll, которого под windows не существует. - -tpws в режиме socks можно запускать под более-менее современными билдами windows 10 и windows server -с установленным WSL. Совсем не обязательно устанавливать дистрибутив убунту, как вам напишут почти в каждой -статье про WSL, которую вы найдете в сети. tpws - статический бинарик, ему дистрибутив не нужен. - -Установить WSL : - `dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all` - -Из релиза скопировать на целевую систему `binaries/linux-x86_64/tpws_wsl.tgz`. - -Выполнить : - `wsl --import tpws "%USERPROFILE%\tpws" tpws_wsl.tgz` - -Запустить : - `wsl -d tpws --exec /tpws --uid=1 --no-resolve --socks --bind-addr=127.0.0.1 --port=1080 <параметры_дурения>` - -Прописать socks `127.0.0.1:1080` в браузер или другую программу. - -Удаление : `wsl --unregister tpws` - - -> [!NOTE] -> Проверено на windows 10 build 19041 (20.04) под WSL1. На WSL2 эти команды могут не сработать. -Если у вас есть WSL2, значит у вас есть работающая виртуалка с linux. -Если вы умеете с ней обращаться, tpws на ней запустить возможно без всяких проблем. - - -Возможные проблемы: -- Не работают функции `--oob` и `--mss` из-за ограничений реализации WSL. -`--disorder` не работает из-за особенностей tcp/ip стека windows. - -- Может не срабатывать детект RST в autohostlist. - -- WSL может глючить со splice, приводя к зацикливанию процесса. Может потребоваться `--nosplice`. - -- Не поддерживается tcp user timeout. -Чтобы избавиться от сообщений об ошибке добавляйте : - `--local-tcp-user-timeout=0 --remote-tcp-user-timeout=0`. -Эти сообщения только информативные, на работу они не влияют. - -## winws - -Это вариант пакетного фильтра nfqws для Windows, построенный на базе windivert. -Все функции работоспособны, однако функционал ipset в ядре отсутствует. Он реализован в user mode. Фильтры по большому количеству IP адресов невозможны. -Работа с проходящим трафиком, например в случае "расшаривания" соединения, невозможна. -Для работы с windivert требуются права администратора. -Специфические для unix параметры, такие как `--uid`, `--user` и тд, исключены. Все остальные параметры аналогичны nfqws и dvtws. - -Работа с пакетным фильтром основана на двух действиях : -1) Выделение перенаправляемого трафика в режиме ядра и передача его пакетному фильтру в user mode. -2) Собственно обработка перенаправленных пакетов в пакетном фильтре. - -В windows отсутствуют встроенные средства для перенаправления трафика, такие как _iptables_, _nftables_, _pf_ или _ipfw_. -Поэтому используется сторонний драйвер ядра windivert. Он работает, начиная с windows 7. На системах с включенным -secure boot могут быть проблемы из-за подписи драйвера. В этом случае отключите `secureboot` или включите режим `testsigning`. -На windows 7, вероятно, будут проблемы с загрузкой windivert. Читайте ниже соответствующий раздел. - -Задача _iptables_ в **winws** решается внутренними средствами через фильтры windivert. -У windivert существует собственный язык фильтров, похожий на язык фильтров wireshark. -[Документация по фильтрам windivert.](https://reqrypt.org/windivert-doc.html#filter_language) -Чтобы не писать сложные фильтры вручную, предусмотрены различные упрощенные варианты автоматического построения фильтров. - -``` - --wf-iface=[.] ; числовые индексы интерфейса и суб-интерфейса - --wf-l3=ipv4|ipv6 ; фильтр L3 протоколов. по умолчанию включены ipv4 и ipv6. - --wf-tcp=[~]port1[-port2] ; фильтр портов для tcp. ~ означает отрицание - --wf-udp=[~]port1[-port2] ; фильтр портов для udp. ~ означает отрицание - --wf-raw-part=|@ ; частичный windivert фильтр из параметра или из файла. имени файла предшествует символ @. может быть множество частей. сочетается с --wf-tcp,--wf-udp. - --wf-filter-lan=0|1 ; отфильтровывать адреса назначения, не являющиеся глобальными inet адресами ipv4 или ipv6. по умолчанию - 1. - --wf-raw=|@ ; полный windivert фильтр из параметра или из файла. имени файла предшествует символ @. замещает --wf-raw-part,--wf-tcp,--wf-udp. - --wf-save= ; сохранить сконструированный фильтр windivert в файл для последующей правки вручную - --ssid-filter=ssid1[,ssid2,ssid3,...] ; включать winws только когда подключена любая из указанных wifi сетей - --nlm-filter=net1[,net2,net3,...] ; включать winws только когда подключена любая из указанных сетей NLM - --nlm-list[=all] ; вывести список сетей NLM. по умолчанию только подключенных, all - всех. - ``` - -Параметры `--wf-l3`, `--wf-tcp`, `--wf-udp` могут брать несколько значений через запятую. - -Номера интерфейсов можно узнать так : `netsh int ip show int`. -Некоторых типы соединений там не увидеть. В этом случае запускайте **winws** с параметром `--debug` и смотрите IfIdx там. -SubInterface используется windivert, но практически всегда **0**, его можно не указывать. Вероятно, он нужен в редких случаях. - -Конструктор стандартных фильтров автоматически включает входящие tcp пакеты с tcp synack и tcp rst для корректной работы функций -autottl и autohostlist. При включении autohostlist так же перенаправляются пакеты данных с http redirect с кодами 302 и 307. -Если не указаное иное, добавляется фильтр на исключение не-интернет адресов ipv4 и ipv6. -Для сложных нестандартных сценариев могут потребоваться свои фильтры. Полный фильтр --wf-raw замещает все остальное. -Частичные фильтры `--wf-raw-part` совместимы друг с другом и `--wf-tcp` и `--wf-udp`. Они позволяют исключить написание -громоздких полных фильтров, сосредоточившись лишь на добавлении какого-то особенного пейлоада. -`--wf-save` позволяет записать итоговый windivert фильтр в файл. Максимальный размер фильтра - **16 Kb**. - -Фильтрация windivert производится в ядре. Это несравнимо легче по ресурсам, чем перенаправлять пакеты в пространство user mode, -чтобы winws принимал решение. Поэтому пользуйтесь по максимуму возможностями windivert. -Например, если вам нужно дурить wireguard на все порты, вам придется перенаправить все порты на winws. Или же написать windivert фильтр, который отсечет wireguard по содержимому пакета. -Разница в нагрузке на процессор колоссальна. В первом случае - до 100% одного ядра cpu в зависимости от обьема исходящего udp трафика (привет, торрент и uTP), во втором - близко к 0. -Кроме нагрузки на процессор еще можете порезать себе скорость, тк одно ядро не будет справляться с обработкой вашего гигабитного интернета. А на старых ноутах еще и получите самолетный вой системы охлаждения, приводящий к ее износу. - -Можно запускать несколько процессов **winws** с разными стратегиями. Однако, не следует делать пересекающиеся фильтры. - -В `--ssid-filter` можно через запятую задать неограниченное количество имен wifi сетей (**SSID**). Если задана хотя бы одна сеть, -то winws включается только, если подключен указанный **SSID**. Если **SSID** исчезает, winws отключается. Если **SSID** появляется снова, -winws включается. Это нужно, чтобы можно было применять раздельное дурение к каждой отдельной wifi сети. -Названия сетей должны быть написаны в том регистре, в котором их видит система. Сравнение идет с учетом регистра! -При этом нет никаких проверок куда реально идет трафик. Если одновременно подключен, допустим, ethernet, -и трафик идет туда, то дурение включается и выключается просто по факту наличия wifi сети, на которую трафик может и не идти. -И это может сломать дурение на ethernet. Поэтому полезно так же будет добавить фильтр `--wf-iface` на индекс интерфейса wifi адаптера, -чтобы не трогать другой трафик. - -`--nlm-filter` аналогичен `--ssid-filter`, но работает с именами или GUIDами сетей Network List Manager (NLM). -Это те сети, которые вы видите в панели управления в разделе "Центр управления сетями и общим доступом". -Под сетью подразумевается не конкретный адаптер, а именно сетевое окружение конкретного подключения. -Обычно проверяется mac адрес шлюза. К сети можно подключиться через любой адаптер, и она останется той же самой. -Если подключиться, допустим, к разными роутерам по кабелю, то будут разные сети. -А если к одному роутеру через 2 разных сетевых карточки на том же компе - будет одна сеть. -NLM абстрагирует типы сетевых адаптеров. Он работает как с wifi, так и с ethernet и любыми другими. -Поэтому это более универсальный метод, чем **SSID** фильтр. -Однако, есть и неприятная сторона. В windows 7 вы легко могли ткнуть на иконку сети и выбрать тип : private или public. -Там же вы могли посмотреть список сетей и обьединить их. Чтобы, допустим, вы могли подключаться по кабелю и wifi -к одному роутеру, и система эти подключения воспринимала как одну сеть. -В следующих версиях windows они эти возможности сильно порезали. Похоже нет встроенных средств полноценно управлять -network locations в win10/11. Кое-что есть в **powershell**. -Можно поковыряться напрямую в реестре здесь : -`HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\NetworkList` -Нужно менять ProfileGUID в `Signatures\Unmanaged`. Имена можно поменять в Profiles. -Есть кое-какие сторонние утилиты. Кое-что находится, позволяющее посмотреть и удалить network profiles, но не обьединить. -Факт, что в ms они это сильно испортили. Движок network list все тот же, и он способен на все то, что было в win7. -Можно не бороться с этой проблемой, а просто указывать через запятую те названия сетей или GUIDы, которые выбрала система. -Или если у вас только wifi, то использовать `--ssid-filter`. Там хотя бы есть гарантия, что **SSID** соответствуют реальности, -а система их не назвала как-то по-своему. - -Если в путях присутствуют национальные символы, то при вызове winws из `cmd` или `bat` кодировку нужно использовать **OEM**. -Для русского языка это 866. Пути с пробелами нужно брать в кавычки. -При использовании опции @ кодировка в файле должна быть **UTF-8** без **BOM mark**. - -Существует неочевидный момент, каcаемый запуска **winws** из cygwin shell\`а. Если в директории, где находится winws, находится -копия `cygwin1.dll`, **winws** не запустится. -Если нужен запуск под cygwin, то следует удалить или переместить `cygwin1.dll` из `binaries/windows-x86_64`. Это нужно для работы blockcheck. -Из cygwin шелла можно посылать winws сигналы через `kill` точно так же, как в `*nix`. - -Как получить совместимый с windows 7 и winws cygwin : - -`curl -O https://www.cygwin.com/setup-x86_64.exe` - -`setup-x86_64.exe --allow-unsupported-windows --no-verify --site http://ctm.crouchingtigerhiddenfruitbat.org/pub/cygwin/circa/64bit/2024/01/30/231215` - -> [!IMPORTANT] -> Следует выбрать установку curl. - -Для сборки из исходников требуется _gcc-core_,_make_,_zlib-devel_. -Собирать из директории nfq командой `make cygwin64` или `make cygwin32` для 64 и 32 битных версий соответственно. -**winws** требует `cygwin1.dll`, `windivert.dll`, `windivert64.sys` или `windivert32.sys`. -Их можно взять из `binaries/win64` и `binaries/win32`. - -Для _arm64_ windows нет подписанного драйвера windivert и нет cygwin. -Однако, эмуляция x64 windows 11 позволяет использовать все, кроме WinDivert64.sys без изменений. -Но при этом надо заменить WinDivert64.sys на неподписанную _arm64_ версию и установить режим testsigning. - -## Windows 7 и windivert - -Требования к подписи драйверов windows изменились в 2021 году. -Официальные бесплатные обновления windows 7 закончились в 2020. -После этого несколько лет продолжали идти платные обновления по программе **ESU**. -Именно в этих **ESU** обновлениях находится обновление ядра windows 7, позволяющиее загрузить драйвер -_windivert 2.2.2-A_, который идет в поставке zapret. -Поэтому варианты следующие : - -1) Взять `windivert64.sys` и `windivert.dll` версии _2.2.0-C_ или _2.2.0-D_ отсюда : https://reqrypt.org/download -и заменить эти 2 файла. -В [zapret-win-bundle](https://github.com/bol-van/zapret-win-bundle) есть отдельных 2 места, где находится **winws** : [_zapret-winws_](https://github.com/bol-van/zapret-win-bundle/tree/master/zapret-winws) и [_blockcheck/zapret/nfq_](https://github.com/bol-van/zapret-win-bundle/tree/master/blockcheck). -Надо менять в обоих местах. -Альтернативный вариант при использовании win bundle - запустить `win7\install_win7.cmd` - -> [!NOTE] -> Этот вариант проверен и должен работать. Тем не менее патч 10 летней давности, который включает SHA256 сигнатуры, все еще необходим. - -2) Взломать **ESU** : -https://hackandpwn.com/windows-7-esu-patching/ -http://www.bifido.net/tweaks-and-scripts/8-extended-security-updates-installer.html -и обновить систему - -3) Использовать готовый патчер-взламыватель ESU - "BypassESU". Его надо искать по названию. - -4) Использовать UpdatePack7R2 от simplix : https://blog.simplix.info -> [!WARNING] -> Но с этим паком есть проблема. Автор из Украины, он очень обиделся на русских. -> Если в панели управления стоит регион RU или BY, появляется неприятный диалог. -> Чтобы эту проблему обойти, можно поставить временно любой другой регион, потом вернуть. -> Так же нет никаких гарантий, что автор не насовал туда какой-то зловредный код. -> Использовать на свой страх и риск. - -Более безопасный вариант - скачать последнюю нормальную довоенную версию : 22.2.10 -https://nnmclub.to/forum/viewtopic.php?t=1530323 -Ее достаточно, чтобы _windivert 2.2.2-A_ заработал на windows 7. - -## blockcheck - -`blockcheck.sh` написан на _posix shell_ и требует некоторых стандартных утилит _posix_. В windows, естественно, этого нет. -Потому просто так запустить `blockcheck.sh` невозможно. -Для этого требуется скачать и установить _cygwin_ так , как описано в предыдущем разделе. -Следует запустить от имени администратора _cygwin shell_ через `cygwin.bat`. -В нем нужно пройти в директорию с zapret. -Обратные слэши путей windows нужно удваивать, менять на прямые слэши, либо использовать отображение на unix path. -Корректные варианты : -- `cd C:\\Users\\vasya` -- `cd C:/Users/vasya` -- `cd /cygdrive/c/Users/vasya` - -Существует неочевидный момент, каcаемый запуска **winws** из _cygwin_ шелла. Если в директории, где находится **winws**, есть копия `cygwin1.dll`, **winws** не запустится. Нужно переименовать файл `cygwin1.dll`. -Далее все как в _*nix_ : 1 раз `./install_bin.sh` , затем `./blockcheck.sh`. -WSL использовать нельзя, это не то же самое. - -_cygwin_ для обычной работы **winws** не нужен. - -Однако, хотя такой способ и работает, использование **winws** сильно облегчает [zapret-win-bundle](https://github.com/bol-van/zapret-win-bundle). -Там нет проблемы с `cygwin.dll`. - -## Zapret-win-bundle - -Можно не возиться с _cygwin_, а взять готовый пакет, включающий в себя _cygwin_ и _blockcheck_ : https://github.com/bol-van/zapret-win-bundle -Там сделан максимум удобств для сосредоточения на самом zapret, исключая возню с установкой _cygwin_, -заходами в директории, запусками под администратором и прочими сугубо техническими моментами, в которых могут быть -ошибки и непонимания, а новичок без базиса знаний может и вовсе запутаться. - -`/zapret-winws` - здесь все, что нужно для запуска winws в повседневном рабочем режиме. остальное не нужно.\ -`/zapret-winws/_CMD_ADMIN.cmd` - получить командную строку cmd в этой директории от имени администратора для тестирования **winws** -с параметрами, вводимыми вручную\ -`/blockcheck/blockcheck.cmd` - достаточно кликнуть по нему, чтобы пошел _blockcheck_ с записью лога в `blockcheck/blockcheck.log`\ -`/cygwin/cygwin.cmd` - запуск среды _cygwin bash_ под текущим пользователем\ -`/cygwin/cygwin-admin.cmd` - запуск среды _cygwin bash_ под администратором - -В среде _cygwin_ уже настроены alias-ы на winws,blockcheck,ip2net,mdig. С путями возиться не нужно! - -> [!TIP] -> Из cygwin можно не только тестировать winws, но и посылать сигналы. -> Доступны команды: ->- `pidof` ->- `kill` ->- `killall` ->- `pgrep` ->- `pkill` - -Но важно понимать, что таким образом не выйдет посылать сигналы **winws**, запущенному из _zapret-winws_, -поскольку там свой `cygwin1.dll`, и они не разделяют общее пространство процессов unix. -_zapret-winws_ - это отдельный комплект для повседневного использования, не требующий что-то еще, но и не связанный со _средой cygwin_. -Специально для посылки сигналов winws в _zapret-winws_ присутствует killall.exe. - -Среду cygwin можно использовать для записи в файл дебаг-лога winws. Для этого пользуйтесь командой tee. -`winws --debug --wf-tcp=80,443 | tee winws.log` -`winws.log` будет в `cygwin/home/<имя_пользователя>` -Если у вас windows 7, то блокнот не поймет переводы строк в стиле unix. Воспользуйтесь командой -`unix2dos winws.log` - -> [!CAUTION] -> Поскольку 32-битные windows мало востребованы, _zapret-win-bundle_ существует только в варианте для windows _x64/arm64_. - -## Автозапуск winws - -Для запуска **winws** вместе с windows есть 2 варианта. Планировщик задач или службы windows. - -Можно создавать задачи и управлять ими через консольную программу schtasks. -В директории `binaries/windows-x86_64/winws` подготовлены файлы `task_*.cmd` . -В них реализовано создание, удаление, старт и стоп одной копии процесса winws с параметрами из переменной `%WINWS1%`. -Исправьте параметры на нужную вам стратегию. Если для разных фильтров применяется разная стратегия, размножьте код -для задач _winws1_,_winws2_,_winws3_,_..._ - -Аналогично настраивается вариант запуска через службы windows. Смотрите `service_*.cmd`. - -Все батники требуется запускать от имени администратора. - -Управлять задачами можно так же из графической программы управления планировщиком `taskschd.msc` - -## Особенности Windows Server - -winws слинкован с wlanapi.dll, который по умолчанию не установлен в windows server. -Для решения этой проблемы запустите power shell под администратором и выполните команду `Install-WindowsFeature -Name Wireless-Networking`. -После чего перезагрузите систему. diff --git a/docs/wireguard_iproute_openwrt.txt b/docs/wireguard_iproute_openwrt.txt deleted file mode 100644 index f9d610a5..00000000 --- a/docs/wireguard_iproute_openwrt.txt +++ /dev/null @@ -1,653 +0,0 @@ -Данный мануал пишется не как копипастная инструкция, а как помощь уже соображающему. -Если вы не знаете основ сетей, linux, openwrt, а пытаетесь что-то скопипастить отсюда без малейшего -понимания смысла, то маловероятно, что у вас что-то заработает. Не тратье свое время напрасно. -Цель - донести принципы как это настраивается вообще, а не указать какую буковку где вписать. - - -Есть возможность поднять свой 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. - -Понятийно необходимо выполнить следующие шаги : -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. - ---- Если нет своего сервера --- - -Но есть конфиг от VPN провайдера или от друга "Васи", который захотел с вами поделиться. -Тогда вам не надо настраивать сервер, задача упрощается. Делается невозможным вариант настройки -без masquerade (см ниже). -Из конфига вытаскиваете приватный ключ своего пира и публичный ключ сервера, ip/host/port сервера, -используете их в настройках openwrt вместо сгенеренных самостоятельно. - ---- Поднятие сервера --- - -Wireguard был включен в ядро linux с версии 5.6. -Если у вас ядро >=5.6, то достаточно установить пакет wireguard-tools. Он содержит user-mode компоненты wireguard. -Посмотрите, возможно в вашем дистрибутиве ядро по умолчанию более старое, но в репозитории -имеются бэкпорты новых версий. Лучше будет обновить ядро из репозитория. - -В репозитории может быть пакет wireguard-dkms. Это автоматизированное средство сборки -wireguard с исходников, в том числе модуль ядра. Можно пользоваться им. - -Иначе вам придется собрать wireguard самому. Ядро должно быть не ниже 3.10. -На сервере должны быть установлены заголовки ядра (linux-headers-...) и компилятор gcc. - -# git clone --depth 1 https://git.zx2c4.com/wireguard-linux-compat -# cd wireguard-linux-compat/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-tools - -Добавляем записи в конфиги. - ---/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 сервера. - Конечно, оно скорее всего заработает и так, потому что первый пакет пойдет от клиента к серверу и тем самым создаст - запись в conntrack. Все дальнейшие пакеты в обе стороны подпадут под состояние ESTABLISHED и будут пропущены. - Запись будет поддерживаться за счет периодических запросов keep alive. Но если вы вдруг уберете keep alive или - выставите таймаут, превышающий udp таймаут в conntrack, то могут начаться ошибки, висы и переподключения. - Если же в фаерволе проткнута дырка, то пакеты от сервера не будут заблокированы ни при каких обстоятельствах. - -# /etc/init.d/firewall restart -# ifup wgvps -# ifconfig wgvps -# ping 192.168.254.1 - -Если все хорошо, должны ходить пинги. -С сервера не помешает : -# ping 192.168.254.3 - - ---- Подготовка zapret --- - -Выполните install_easy.sh. Он настроит режим обхода DPI. Если обход DPI не нужен - не включайте tpws и nfqws. -Так же инсталятор заресолвит домены из ipset/zapret-hosts-user-ipban.txt и внесет крон-джоб для периодического обновления ip. - -Если вы используете в своих правилах ipset zapret, то он ресолвится и обновляется только, если выбран режим фильтрации обхода DPI по ipset. -По сути он вам нужен исключительно, если обход DPI не помогает. Например, удается как-то пробить http, но не удается пробить https. -И при этом вы хотите, чтобы на VPN направлялись только ip из скачанного ip листа, в добавок к заресолвленному ipset/zapret-hosts-user.txt. -Именно этот случай и рассмотрен в данном примере. Если это не так, то убирайте правила с портом 443 из нижеприведенных правил iptables/nftables. -Если не хотите ограничиваться листом, и хотите направлять все на порт 443, то уберите фильтры из правил iptables/nftables, -связанные с ipset/nfset "zapret". - -Фильтрация по именам доменов (MODE_FILTER=hostlist) невозможна средствами iptables/nftables. Она производится исключительно в tpws и nfqws -по результатам анализа протокола прикладного уровня, иногда достаточно сложного, связанного с дешифровкой пакета (QUIC). -Скачиваются листы с именами доменов, не ip адресами. ipset/zapret-hosts-user.txt не ресолвится, а используется как hostlist. -Потому вам нельзя расчитывать на ipset zapret. -Тем не менее при выборе этого режима фильтрации , либо вовсе при ее отсутствии (MODE_FILTER=none), ipset/zapret-hosts-user-ipban.txt -все равно ресолвится. Вы всегда можете расчитывать на ipset/nfset "ipban", "nozapret". - -"nozapret" - это ipset/nfset, связанный с системой исключения ip. Сюда загоняется все из ipset/zapret-hosts-user-exclude.txt после ресолвинга. -Его учет крайне желателен, чтобы вдруг из скачанного листа не просочились записи, например, 192.168.0.0/16 и не заставили лезть туда через VPN. -Хотя скрипты получения листов и пытаются отсечь IP локалок, но так будет намного надежнее. - ---- Маркировка трафика --- - -Завернем на vpn все из ipset zapret на tcp:443 и все из ipban. -OUTPUT относится к исходящим с роутера пакетам, PREROUTING - ко всем остальным. -Если с самого роутера ничего заруливать не надо, можно опустить часть, отвечающую за OUTPUT. - ---/etc/firewall.user---------------------------- -. /opt/zapret/init.d/openwrt/functions - -create_ipset no-update - -network_find_wan4_all wan_iface -for ext_iface in $wan_iface; do - network_get_device DEVICE $ext_iface - ipt OUTPUT -t mangle -o $DEVICE -p tcp --dport 443 -m set --match-set zapret dst -m set ! --match-set nozapret dst -j MARK --set-mark 0x800/0x800 - ipt OUTPUT -t mangle -o $DEVICE -m set --match-set ipban dst -m set ! --match-set nozapret 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 -m set ! --match-set nozapret dst -j MARK --set-mark 0x800/0x800 -ipt PREROUTING -t mangle -i $DEVICE -m set --match-set ipban dst -m set ! --match-set nozapret dst -j MARK --set-mark 0x800/0x800 ------------------------------------------------- - -# /etc/init.d/firewall restart - -Чтобы правила обновлялись в процессе поднятия интерфейсов в системе, нужно внести параметр "reload" в указанное место : ---- /etc/config/firewall --- -config include - option path '/etc/firewall.user' - option reload '1' ----------------------------- - - ---- Маркировка трафика nftables --- - -В новых openwrt по умолчанию установлен nftables, iptables отсутствует. -Есть вариант снести nftables + fw4 и заменить их на iptables + fw3. -Веб интерфейс luci понимает прозрачно и fw3, и fw4. Однако, при установке iptables и fw3 новые пакеты -будут устанавливаться без сжатия squashfs. Убедитесь, что у вас достаточно места. -Либо сразу настраивайте образ через image builder. - -Фаервол fw4 работает в одноименной nftable - "inet fw4". "inet" означает, что таблица принимает и ipv4, и ipv6. -Поскольку для маркировки трафика используется nfset, принадлежащий таблице zapret, цепочки необходимо помещать в ту же таблицу. -Для синхронизации лучше всего использовать хук -INIT_FW_POST_UP_HOOK="/etc/firewall.zapret.hook.post_up" -Параметр нужно раскоментировать в /opt/zapret/config. Далее надо создать указанный файл и дать ему chmod 755. - ---/etc/firewall.zapret.hook.post_up---------------------------- -#!/bin/sh - -ZAPRET_NFT_TABLE=zapret - -cat << EOF | nft -f - 2>/dev/null - delete chain inet $ZAPRET_NFT_TABLE my_output - delete chain inet $ZAPRET_NFT_TABLE my_prerouting -EOF - -cat << EOF | nft -f - - add chain inet $ZAPRET_NFT_TABLE my_output { type route hook output priority mangle; } - flush chain inet $ZAPRET_NFT_TABLE my_output - add rule inet $ZAPRET_NFT_TABLE my_output oifname @wanif ip daddr @ipban ip daddr != @nozapret meta mark set mark or 0x800 - add rule inet $ZAPRET_NFT_TABLE my_output oifname @wanif tcp dport 443 ip daddr @zapret ip daddr != @nozapret meta mark set mark or 0x800 - - add chain inet $ZAPRET_NFT_TABLE my_prerouting { type filter hook prerouting priority mangle; } - flush chain inet $ZAPRET_NFT_TABLE my_prerouting - add rule inet $ZAPRET_NFT_TABLE my_prerouting iifname @lanif ip daddr @ipban ip daddr != @nozapret meta mark set mark or 0x800 - add rule inet $ZAPRET_NFT_TABLE my_prerouting iifname @lanif tcp dport 443 ip daddr @zapret ip daddr != @nozapret meta mark set mark or 0x800 -EOF ------------------------------------------------- - -# /etc/init.d/zapret restart_fw - -Проверка правил : -# /etc/init.d/zapret list_table -или -# nft -t list table inet zapret - -Должны быть цепочки my_prerouting и my_output. - -Проверка заполнения nfsets : -# nft list set inet zapret zapret -# nft list set inet zapret ipban -# nft list set inet zapret nozapret - -Проверка заполнения множеств lanif, wanif, wanif6, link_local : -# /etc/init.d/zapret list_ifsets - -Должны присутствовать имена интерфейсов во множествах lanif, wanif. -wanif6 заполняется только при включении ipv6. -link_local нужен только для tpws при включении ipv6. - ---- По поводу двойного NAT --- - -В описанной конфигурации nat выполняется дважды : на роутере-клиенте происходит замена адреса источника из LAN -на 192.168.254.3 и на сервере замена 192.168.254.3 на внешний адрес сервера в инете. -Зачем так делать ? Исключительно для простоты настройки. Или на случай, если сервер wireguard не находится под вашим контролем. -Делать для вас нижеописанные настройки никто не будет с вероятностью, близкой к 100%. -Если сервер wireguard - ваш, и вы готовы чуток еще поднапрячься и не хотите двойного 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, что и требовалось. - -Альтернативное решение - использовать на VPSке для проброса портов не только DNAT, но и SNAT/MASQUERADE. Тогда source address -будет заменен на 192.168.254.1. Он по таблице маршрутизации пойдет на wgvps. Но в этом случае клиентские программы, -на которые осуществляется проброс портов, не будут видеть реальный IP подключенца. - ---/etc/firewall.user---------------------------- -. /opt/zapret/init.d/openwrt/functions - -create_ipset no-update - -network_find_wan4_all wan_iface -for ext_iface in $wan_iface; do - network_get_device DEVICE $ext_iface - ipt OUTPUT -t mangle -o $DEVICE -p tcp --dport 443 -m set --match-set zapret dst -m set ! --match-set nozapret dst -j MARK --set-mark 0x800/0x800 - ipt OUTPUT -t mangle -o $DEVICE -m set --match-set ipban dst -m set ! --match-set nozapret 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 -m set ! --match-set nozapret dst -j MARK --set-mark 0x800/0x800 -ipt PREROUTING -t mangle -i $DEVICE -m set --match-set ipban dst -m set ! --match-set nozapret 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-mark 0x800/0x800 ------------------------------------------------- - -# /etc/init.d/firewall restart - -Вариант nftables : - ---/etc/firewall.zapret.hook.post_up---------------------------- -#!/bin/sh - -ZAPRET_NFT_TABLE=zapret -DEVICE=wgvps - -cat << EOF | nft -f - 2>/dev/null - delete chain inet $ZAPRET_NFT_TABLE my_output - delete chain inet $ZAPRET_NFT_TABLE my_prerouting - delete chain inet $ZAPRET_NFT_TABLE my_nat -EOF - -cat << EOF | nft -f - - add chain inet $ZAPRET_NFT_TABLE my_output { type route hook output priority mangle; } - flush chain inet $ZAPRET_NFT_TABLE my_output - add rule inet $ZAPRET_NFT_TABLE my_output oifname @wanif ip daddr @ipban ip daddr != @nozapret meta mark set mark or 0x800 - add rule inet $ZAPRET_NFT_TABLE my_output oifname @wanif tcp dport 443 ip daddr @zapret ip daddr != @nozapret meta mark set mark or 0x800 - add rule inet $ZAPRET_NFT_TABLE my_output oifname @wanif meta mark set mark or 0x1000 - - add chain inet $ZAPRET_NFT_TABLE my_prerouting { type filter hook prerouting priority mangle; } - flush chain inet $ZAPRET_NFT_TABLE my_prerouting - add rule inet $ZAPRET_NFT_TABLE my_prerouting iifname $DEVICE ct state new ct mark set ct mark or 0x800 - add rule inet $ZAPRET_NFT_TABLE my_prerouting iifname != $DEVICE meta mark set ct mark and 0x800 - add rule inet $ZAPRET_NFT_TABLE my_prerouting iifname @lanif ip daddr @ipban ip daddr != @nozapret meta mark set mark or 0x800 - add rule inet $ZAPRET_NFT_TABLE my_prerouting iifname @lanif tcp dport 443 ip daddr @zapret ip daddr != @nozapret meta mark set mark or 0x800 - - add chain inet $ZAPRET_NFT_TABLE my_nat { type nat hook postrouting priority 100 ; } - flush chain inet $ZAPRET_NFT_TABLE my_nat - add rule inet $ZAPRET_NFT_TABLE my_nat oifname $DEVICE mark and 0x1000 == 0x1000 masquerade -EOF ------------------------------------------------- - -# /etc/init.d/zapret restart_fw - -К сожалению, здесь возможности nftables немного хромают. Полноценного эквивалента CONNMARK --restore-mark --nfmask -не существует. Оригинал iptables предполагал копирование одного бита 0x800 из connmark в mark. -Лучшее, что можно сделать в nftables, это копирование одного бита с занулением всех остальных. -Сложные выражения типа "meta mark set mark and ~0x800 or (ct mark and 0x800)" nft не понимает. -Об этом же говорит попытка перевода через iptables-translate. - -Сейчас уже можно с 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' ------------------------------------------------- - -# /etc/init.d/firewall restart -# /etc/init.d/zapret restart_fw - ---/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 PREROUTING -i eth0 -p udp -m multiport --dports 5001,5201 -j DNAT --to-destination 192.168.2.2 - 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 PREROUTING -i eth0 -p udp -m multiport --dports 5001,5201 -j DNAT --to-destination 192.168.2.2 ----------------------------------------------- - -# 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' ------------------------------------------------- - -# /etc/init.d/firewall 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' ------------------------------------------------- - -/etc/init.d/firewall 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 diff --git a/files/huawei/E8372/run-zapret-hostlist b/files/huawei/E8372/run-zapret-hostlist deleted file mode 100755 index 7f37d580..00000000 --- a/files/huawei/E8372/run-zapret-hostlist +++ /dev/null @@ -1,35 +0,0 @@ -#!/system/bin/busybox sh - -# download hostlist from http(s) (need curl, its absent by default), -# feed it to zapret. save flash write cycles - -u="https://your.host.com/censorship/hoslist.txt" - -SCRIPT=$(readlink -f "$0") -EXEDIR=$(dirname "$SCRIPT") - -d=/data/censorship -[ -d $d ] || mkdir $d -f=$d/hostlist.txt -t=/hostlist.txt - -curl -k --fail --max-time 10 -o "$t" "$u" && { - if [ -s "$t" ]; then - m1=$(md5sum "$t" | cut -d ' ' -f 1) - m2=$(md5sum "$f" | cut -d ' ' -f 1) - echo $m1 $m2 - if [ -z "$m2" ] || [ "$m1" != "$m2" ]; then - echo updating hostlist - cp -f "$t" "$f" - else - echo hostlist was not changed. keeping old copy - fi - else - echo downloaded hostlist is empty. disabling zapret - rm "$f" - fi -} - -rm -f "$t" -"$EXEDIR/unzapret" -[ -s "$f" ] && exec "$EXEDIR/zapret" "--hostlist=$f" diff --git a/files/huawei/E8372/run-zapret-ip b/files/huawei/E8372/run-zapret-ip deleted file mode 100755 index 803e9844..00000000 --- a/files/huawei/E8372/run-zapret-ip +++ /dev/null @@ -1,39 +0,0 @@ -#!/system/bin/busybox sh - -# download hostlist from http(s) (need curl, its absent by default), -# resolve to ip list, feed to zapret-ip. save flash write cycles - -u="https://your.host.com/censorship/hoslist.txt" - -SCRIPT=$(readlink -f "$0") -EXEDIR=$(dirname "$SCRIPT") - -d=/data/censorship -[ -d $d ] || mkdir $d -f=$d/hostlist.txt -t=/hostlist.txt -i=/iplist.txt - -curl -k --fail --max-time 10 -o "$t" "$u" && { - if [ -s "$t" ]; then - m1=$(md5sum "$t" | cut -d ' ' -f 1) - m2=$(md5sum "$f" | cut -d ' ' -f 1) - echo $m1 $m2 - if [ -z "$m2" ] || [ "$m1" != "$m2" ]; then - echo updating hostlist - cp -f "$t" "$f" - else - echo hostlist was not changed. keeping old copy - fi - else - echo downloaded hostlist is empty. disabling zapret - rm "$f" - fi -} - -rm -f "$t" -"$EXEDIR/unzapret-ip" -[ -s "$f" ] && { - mdig --threads=10 --family=4 <"$f" >"$i" - [ -s "$i" ] && exec "$EXEDIR/zapret-ip" "$i" -} diff --git a/files/huawei/E8372/unfuck_nfqueue.ko b/files/huawei/E8372/unfuck_nfqueue.ko deleted file mode 100644 index c24ce5eb..00000000 Binary files a/files/huawei/E8372/unfuck_nfqueue.ko and /dev/null differ diff --git a/files/huawei/E8372/unzapret b/files/huawei/E8372/unzapret deleted file mode 100755 index f040dfc5..00000000 --- a/files/huawei/E8372/unzapret +++ /dev/null @@ -1,9 +0,0 @@ -#!/system/bin/busybox sh - -rule="PREROUTING -t nat -i br0 ! -d 192.168.0.0/16 -p tcp -m multiport --dports 80,443 -j REDIRECT --to-port 1" -iptables -C $rule 2>/dev/null && iptables -D $rule -killall tpws - -rule="OUTPUT -t mangle -o wan0 -p tcp -m multiport --dports 80,443 -m mark ! --mark 0x40000000/0x40000000 -j NFQUEUE --queue-num 200 --queue-bypass" -iptables -C $rule 2>/dev/null && iptables -D $rule -killall nfqws diff --git a/files/huawei/E8372/unzapret-ip b/files/huawei/E8372/unzapret-ip deleted file mode 100755 index ccb74258..00000000 --- a/files/huawei/E8372/unzapret-ip +++ /dev/null @@ -1,11 +0,0 @@ -#!/system/bin/busybox sh - -rule="PREROUTING -t nat -i br0 -p tcp -m multiport --dports 80,443 -j tpws" -iptables -C $rule 2>/dev/null && iptables -D $rule -iptables -F tpws -t nat -iptables -X tpws -t nat -killall tpws - -rule="OUTPUT -t mangle -o wan0 -p tcp -m multiport --dports 80,443 -m mark ! --mark 0x40000000/0x40000000 -j NFQUEUE --queue-num 200 --queue-bypass" -iptables -C $rule 2>/dev/null && iptables -D $rule -killall nfqws diff --git a/files/huawei/E8372/zapret b/files/huawei/E8372/zapret deleted file mode 100755 index f19eed35..00000000 --- a/files/huawei/E8372/zapret +++ /dev/null @@ -1,15 +0,0 @@ -#!/system/bin/busybox sh - -# $1 - additional parameters for nfqws - -insmod /online/modules/unfuck_nfqueue.ko 2>/dev/null - -rule="PREROUTING -t nat -i br0 ! -d 192.168.0.0/16 -p tcp -m multiport --dports 80,443 -j REDIRECT --to-port 1" -iptables -C $rule 2>/dev/null || iptables -I $rule - -tpws --uid 1:3003 --port=1 --daemon - -rule="OUTPUT -t mangle -o wan0 -p tcp -m multiport --dports 80,443 -m mark ! --mark 0x40000000/0x40000000 -j NFQUEUE --queue-num 200 --queue-bypass" -iptables -C $rule 2>/dev/null || iptables -I $rule - -nfqws --uid 2 --qnum=200 --dpi-desync=disorder --dpi-desync-ttl=8 --dpi-desync-fooling=md5sig --daemon $1 diff --git a/files/huawei/E8372/zapret-ip b/files/huawei/E8372/zapret-ip deleted file mode 100755 index 9e70fac9..00000000 --- a/files/huawei/E8372/zapret-ip +++ /dev/null @@ -1,34 +0,0 @@ -#!/system/bin/busybox sh - -# $1 - ip list file. create individual rules for tpws redirection. ipset is not available - -[ -z "$1" ] && { - echo need iplist file as parameter - exit 1 -} - -insmod /online/modules/unfuck_nfqueue.ko 2>/dev/null - -tpws --maxconn=1024 --uid 1:3003 --port=1 --daemon - - -REDIR="-j REDIRECT --to-port 1" - -iptables -F tpws -t nat -iptables -X tpws -t nat -iptables -N tpws -t nat -iptables -A tpws -t nat -d 192.168.0.0/16 -j RETURN - -while read ip; do - echo redirecting $ip - iptables -A tpws -t nat -d $ip -p tcp $REDIR -done <"$1" - - -rule="PREROUTING -t nat -i br0 -p tcp -m multiport --dports 80,443 -j tpws" -iptables -C $rule 2>/dev/null || iptables -I $rule - -nfqws --uid 2 --qnum=200 --dpi-desync=disorder --dpi-desync-ttl=8 --dpi-desync-fooling=md5sig --daemon - -rule="OUTPUT -t mangle -o wan0 -p tcp -m multiport --dports 80,443 -m mark ! --mark 0x40000000/0x40000000 -j NFQUEUE --queue-num 200 --queue-bypass" -iptables -C $rule 2>/dev/null || iptables -I $rule diff --git a/init.d/custom.d.examples.linux/50-quic4all b/init.d/custom.d.examples.linux/50-quic4all deleted file mode 100644 index a9b1ef7f..00000000 --- a/init.d/custom.d.examples.linux/50-quic4all +++ /dev/null @@ -1,30 +0,0 @@ -# this custom script runs desync to all IETF QUIC initials -# NOTE: @ih requires nft 1.0.1+ and updated kernel version. it's confirmed to work on 5.15 (openwrt 23) and not work on 5.10 (openwrt 22) - -# can override in config : -NFQWS_OPT_DESYNC_QUIC="${NFQWS_OPT_DESYNC_QUIC:---dpi-desync=fake --dpi-desync-repeats=2}" - -alloc_dnum DNUM_QUIC4ALL -alloc_qnum QNUM_QUIC4ALL - -zapret_custom_daemons() -{ - # $1 - 1 - add, 0 - stop - - local opt="--qnum=$QNUM_QUIC4ALL $NFQWS_OPT_DESYNC_QUIC" - do_nfqws $1 $DNUM_QUIC4ALL "$opt" -} -zapret_custom_firewall() -{ - # $1 - 1 - run, 0 - stop - - local f='-p udp -m u32 --u32' - fw_nfqws_post $1 "$f 0>>22&0x3C@4>>16=264:65535&&0>>22&0x3C@8>>28=0xC&&0>>22&0x3C@9=0x00000001" "$f 44>>16=264:65535&&48>>28=0xC&&49=0x00000001" $QNUM_QUIC4ALL -} -zapret_custom_firewall_nft() -{ - # stop logic is not required - - local f="udp length >= 264 @ih,0,4 0xC @ih,8,32 0x00000001" - nft_fw_nfqws_post "$f" "$f" $QNUM_QUIC4ALL -} diff --git a/init.d/macos/custom.d.examples/50-extra-tpws b/init.d/macos/custom.d.examples/50-extra-tpws deleted file mode 100644 index 5b1ae6d3..00000000 --- a/init.d/macos/custom.d.examples/50-extra-tpws +++ /dev/null @@ -1,29 +0,0 @@ -# this script is an example describing how to run tpws on a custom port - -TPWS_OPT_EXTRA=${TPWS_OPT_EXTRA:---split-pos=2} -DPORTS_EXTRA=${DPORTS_EXTRA:-20443,20444,30000-30009} - -alloc_dnum DNUM_EXTRA_TPWS -alloc_tpws_port TPPORT_EXTRA_TPWS - -zapret_custom_daemons() -{ - # $1 - 1 - run, 0 - stop - local opt="--user=root --port=$TPPORT_EXTRA_TPWS" - tpws_apply_binds opt - opt="$opt $TPWS_OPT_EXTRA" - filter_apply_hostlist_target opt - do_daemon $1 $DNUM_EXTRA_TPWS "$TPWS" "$opt" -} - -# custom firewall functions echo rules for zapret-v4 and zapret-v6 anchors -# they come after automated table definitions. so you can use ... - -zapret_custom_firewall_v4() -{ - pf_anchor_zapret_v4_tpws $TPPORT_EXTRA_TPWS $(replace_char - : $DPORTS_EXTRA) -} -zapret_custom_firewall_v6() -{ - pf_anchor_zapret_v6_tpws $TPPORT_EXTRA_TPWS $(replace_char - : $DPORTS_EXTRA) -} diff --git a/init.d/macos/custom.d/.keep b/init.d/macos/custom.d/.keep deleted file mode 100644 index e69de29b..00000000 diff --git a/init.d/macos/functions b/init.d/macos/functions deleted file mode 100644 index 28af67c9..00000000 --- a/init.d/macos/functions +++ /dev/null @@ -1,195 +0,0 @@ -# init script functions library for macos - -ZAPRET_BASE=${ZAPRET_BASE:-/opt/zapret} -ZAPRET_RW=${ZAPRET_RW:-"$ZAPRET_BASE"} -ZAPRET_CONFIG=${ZAPRET_CONFIG:-"$ZAPRET_RW/config"} -. "$ZAPRET_CONFIG" -. "$ZAPRET_BASE/common/base.sh" -. "$ZAPRET_BASE/common/pf.sh" -. "$ZAPRET_BASE/common/list.sh" -. "$ZAPRET_BASE/common/custom.sh" -CUSTOM_DIR="$ZAPRET_RW/init.d/macos" - -IPSET_DIR=$ZAPRET_BASE/ipset -. "$IPSET_DIR/def.sh" - -PIDDIR=/var/run -[ -n "$TPPORT" ] || TPPORT=988 -[ -n "$TPPORT_SOCKS" ] || TPPORT=987 -[ -n "$WS_USER" ] || WS_USER=daemon -TPWS_WAIT="--bind-wait-ifup=30 --bind-wait-ip=30" -TPWS_WAIT_SOCKS6="$TPWS_WAIT --bind-wait-ip-linklocal=30" -[ -n "$TPWS" ] || TPWS="$ZAPRET_BASE/tpws/tpws" - -CUSTOM_SCRIPT="$ZAPRET_BASE/init.d/macos/custom" -[ -f "$CUSTOM_SCRIPT" ] && . "$CUSTOM_SCRIPT" - -run_daemon() -{ - # $1 - daemon number : 1,2,3,... - # $2 - daemon - # $3 - daemon args - # use $PIDDIR/$DAEMONBASE$1.pid as pidfile - local DAEMONBASE="$(basename "$2")" - local PIDFILE="$PIDDIR/$DAEMONBASE$1.pid" - local ARGS="--daemon --pidfile=$PIDFILE $3" - [ -f "$PIDFILE" ] && pgrep -qF "$PIDFILE" && { - echo Already running $1: $2 - return 0 - } - echo "Starting daemon $1: $2 $ARGS" - "$2" $ARGS -} -stop_daemon() -{ - # $1 - daemon number : 1,2,3,... - # $2 - daemon - # use $PIDDIR/$DAEMONBASE$1.pid as pidfile - - local PID - local DAEMONBASE="$(basename "$2")" - local PIDFILE="$PIDDIR/$DAEMONBASE$1.pid" - [ -f "$PIDFILE" ] && read PID <"$PIDFILE" - [ -n "$PID" ] && { - echo "Stopping daemon $1: $2 (PID=$PID)" - kill $PID - rm -f "$PIDFILE" - } - return 0 -} -do_daemon() -{ - # $1 - 1 - run, 0 - stop - on_off_function run_daemon stop_daemon "$@" -} - -tpws_apply_binds() -{ - local o - [ "$DISABLE_IPV4" = "1" ] || o="--bind-addr=127.0.0.1" - [ "$DISABLE_IPV6" = "1" ] || { - for i in lo0 $IFACE_LAN; do - o="$o --bind-iface6=$i --bind-linklocal=force $TPWS_WAIT" - done - } - eval $1="\"\$$1 $o\"" -} -tpws_apply_socks_binds() -{ - local o - - [ "$DISABLE_IPV4" = "1" ] || o="--bind-addr=127.0.0.1" - [ "$DISABLE_IPV6" = "1" ] || o="$o --bind-addr=::1" - - for lan in $IFACE_LAN; do - [ "$DISABLE_IPV4" = "1" ] || o="$o --bind-iface4=$lan $TPWS_WAIT" - [ "$DISABLE_IPV6" = "1" ] || o="$o --bind-iface6=$lan --bind-linklocal=unwanted $TPWS_WAIT_SOCKS6" - done - eval $1="\"\$$1 $o\"" -} - -wait_interface_ll() -{ - echo waiting for an ipv6 link local address on $1 ... - "$TPWS" --bind-wait-only --bind-iface6=$1 --bind-linklocal=force $TPWS_WAIT -} -wait_lan_ll() -{ - [ "$DISABLE_IPV6" != "1" ] && { - for lan in $IFACE_LAN; do - wait_interface_ll $lan >&2 || { - echo "wait interface failed on $lan" - return 1 - } - done - } - return 0 -} -get_ipv6_linklocal() -{ - ifconfig $1 | sed -nEe 's/^.*inet6 (fe80:[a-f0-9:]+).*/\1/p' -} - - -zapret_do_firewall() -{ - # $1 - 1 - add, 0 - del - - [ "$1" = 1 -a -n "$INIT_FW_PRE_UP_HOOK" ] && $INIT_FW_PRE_UP_HOOK - [ "$1" = 0 -a -n "$INIT_FW_PRE_DOWN_HOOK" ] && $INIT_FW_PRE_DOWN_HOOK - - if [ "$1" = "1" ] ; then - pf_anchor_root || return 1 - pf_anchors_create - pf_anchors_load || return 1 - pf_enable - else - pf_anchors_clear - fi - - [ "$1" = 1 -a -n "$INIT_FW_POST_UP_HOOK" ] && $INIT_FW_POST_UP_HOOK - [ "$1" = 0 -a -n "$INIT_FW_POST_DOWN_HOOK" ] && $INIT_FW_POST_DOWN_HOOK - - return 0 -} -zapret_apply_firewall() -{ - zapret_do_firewall 1 "$@" -} -zapret_unapply_firewall() -{ - zapret_do_firewall 0 "$@" -} -zapret_restart_firewall() -{ - zapret_unapply_firewall "$@" - zapret_apply_firewall "$@" -} - - -standard_mode_daemons() -{ - local opt - - if [ "$1" = "1" ] && [ "$DISABLE_IPV4" = "1" ] && [ "$DISABLE_IPV6" = "1" ] ; then - echo "both ipv4 and ipv6 are disabled. nothing to do" - else - [ "$TPWS_ENABLE" = 1 ] && check_bad_ws_options $1 "$TPWS_OPT" && { - opt="--user=root --port=$TPPORT" - tpws_apply_binds opt - opt="$opt $TPWS_OPT" - filter_apply_hostlist_target opt - do_daemon $1 1 "$TPWS" "$opt" - } - [ "$TPWS_SOCKS_ENABLE" = 1 ] && { - opt="--socks --user=$WS_USER --port=$TPPORT_SOCKS" - tpws_apply_socks_binds opt - opt="$opt $TPWS_SOCKS_OPT" - filter_apply_hostlist_target opt - do_daemon $1 2 "$TPWS" "$opt" - } - fi -} - -zapret_do_daemons() -{ - # $1 - 1 - run, 0 - stop - - standard_mode_daemons $1 - custom_runner zapret_custom_daemons $1 - - return 0 -} -zapret_run_daemons() -{ - zapret_do_daemons 1 "$@" -} -zapret_stop_daemons() -{ - zapret_do_daemons 0 "$@" -} -zapret_restart_daemons() -{ - zapret_stop_daemons "$@" - zapret_run_daemons "$@" -} diff --git a/init.d/macos/zapret b/init.d/macos/zapret deleted file mode 100755 index 17f7897b..00000000 --- a/init.d/macos/zapret +++ /dev/null @@ -1,51 +0,0 @@ -#!/bin/sh - -EXEDIR="$(dirname "$0")" -ZAPRET_BASE="$EXEDIR/../.." -ZAPRET_BASE="$(cd "$ZAPRET_BASE"; pwd)" - -. "$EXEDIR/functions" - -case "$1" in - start) - zapret_run_daemons - [ "$INIT_APPLY_FW" != "1" ] || zapret_apply_firewall - ;; - stop) - [ "$INIT_APPLY_FW" != "1" ] || zapret_unapply_firewall - zapret_stop_daemons - ;; - restart) - "$0" stop - "$0" start - ;; - - start-fw|start_fw) - zapret_apply_firewall - ;; - stop-fw|stop_fw) - zapret_unapply_firewall - ;; - restart-fw|stop_fw) - zapret_restart_firewall - ;; - reload-fw-tables|reload_fw_tables) - pf_table_reload - ;; - - start-daemons|start_daemons) - zapret_run_daemons - ;; - stop-daemons|stop_daemons) - zapret_stop_daemons - ;; - restart-daemons|restart_daemons) - zapret_restart_daemons - ;; - - *) - N="$SCRIPT/$NAME" - echo "Usage: $N {start|stop|start-fw|stop-fw|restart-fw|reload-fw-tables|start-daemons|stop-daemons|restart-daemons}" >&2 - exit 1 - ;; -esac diff --git a/init.d/macos/zapret.plist b/init.d/macos/zapret.plist deleted file mode 100644 index 747d69bc..00000000 --- a/init.d/macos/zapret.plist +++ /dev/null @@ -1,17 +0,0 @@ - - - - - Label - zapret - LaunchOnlyOnce - - ProgramArguments - - /opt/zapret/init.d/macos/zapret - start - - RunAtLoad - - - diff --git a/init.d/openrc/zapret b/init.d/openrc/zapret deleted file mode 100755 index 3a1ca586..00000000 --- a/init.d/openrc/zapret +++ /dev/null @@ -1,69 +0,0 @@ -#!/sbin/openrc-run - -# zapret openrc to sysv adapter -# on some systems (alpine) for unknown reason non-openrc-run scripts are not started from /etc/init.d - -EXEDIR=$(dirname "$RC_SERVICE") -EXEDIR="$(cd "$EXEDIR"; pwd)" -ZAPRET_BASE="$EXEDIR/../.." -ZAPRET_INIT="$ZAPRET_BASE/init.d/sysv/zapret" - -extra_commands="start_fw stop_fw restart_fw start_daemons stop_daemons restart_daemons reload_ifsets list_ifsets list_table" -description="extra commands :" -description_stop_fw="Stop zapret firewall" -description_start_fw="Start zapret firewall" -description_restart_fw="Restart zapret firewall" -description_reload_ifsets="Reload interface lists (nftables only)" -description_list_ifsets="Display interface lists (nftables only)" -description_list_table="Display zapret nftable (nftables only)" -description_stop_daemons="Stop zapret daemons only" -description_start_daemons="Start zapret daemons only" -description_restart_daemons="Restart zapret firewall only" - -depend() { - rc-service -e networking && need networking -} -start() -{ - "$ZAPRET_INIT" start -} -stop() -{ - "$ZAPRET_INIT" stop -} -start_fw() -{ - "$ZAPRET_INIT" start_fw -} -stop_fw() -{ - "$ZAPRET_INIT" stop_fw -} -restart_fw() -{ - "$ZAPRET_INIT" restart_fw -} -start_daemons() -{ - "$ZAPRET_INIT" start_daemons -} -stop_daemons() -{ - "$ZAPRET_INIT" stop_daemons -} -restart_daemons() -{ - "$ZAPRET_INIT" restart_daemons -} -reload_ifsets() -{ - "$ZAPRET_INIT" reload_ifsets -} -list_ifsets() -{ - "$ZAPRET_INIT" list_ifsets -} -list_table() -{ - "$ZAPRET_INIT" list_table -} diff --git a/init.d/openwrt-minimal/readme.txt b/init.d/openwrt-minimal/readme.txt deleted file mode 100644 index 081df694..00000000 --- a/init.d/openwrt-minimal/readme.txt +++ /dev/null @@ -1,54 +0,0 @@ -Minimal tpws startup script for low storage openwrt. - ---- openwrt with NFTABLES (22+) - -Make sure you are running openwrt with nftables, not iptables. -No opkg dependencies required ! - -* install : - -Copy everything from tpws directory to the root of the router. -Copy tpws binary for your architecture to /usr/bin/tpws -Set proper access rights : chmod 755 /etc/init.d/tpws /usr/bin/tpws -EDIT /etc/config/tpws -If you don't want ipv6 : edit /etc/nftables.d and comment lines with ipv6 redirect -/etc/init.d/tpws enable -/etc/init.d/tpws start -fw4 restart - -* full uninstall : - -/etc/init.d/tpws disable -/etc/init.d/tpws stop -rm -f /etc/nftables.d/90-tpws.nft /etc/firewall.user /etc/init.d/tpws -fw4 restart - ---- openwrt with IPTABLES (21-) - -Make sure you are running openwrt with iptables, not nftables. -Make sure you do not have anything valuable in /etc/firewall.user. -If you have - do not blindly follow instruction in firewall.user part. -Merge the code instead or setup your own firewall include in /etc/config/firewall. - -opkg update -opkg install iptables-mod-extra -IPV6 ONLY : opkg install ip6tables-mod-nat - -* install : - -Copy everything from tpws directory to the root of the router. -Copy tpws binary for your architecture to /usr/bin/tpws -Set proper access rights : chmod 755 /etc/init.d/tpws /usr/bin/tpws -EDIT /etc/config/tpws -If you don't want ipv6 : edit /etc/firewall.user and set DISABLE_IPV6=1 -/etc/init.d/tpws enable -/etc/init.d/tpws start -fw3 restart - -* full uninstall : - -/etc/init.d/tpws disable -/etc/init.d/tpws stop -rm -f /etc/nftables.d/90-tpws.nft /etc/firewall.user /etc/init.d/tpws -touch /etc/firewall.user -fw3 restart diff --git a/init.d/openwrt-minimal/tpws/etc/config/tpws b/init.d/openwrt-minimal/tpws/etc/config/tpws deleted file mode 100644 index 55bf66c7..00000000 --- a/init.d/openwrt-minimal/tpws/etc/config/tpws +++ /dev/null @@ -1,12 +0,0 @@ -config global defaults - option user daemon - option tpws /usr/bin/tpws - -config tpws - option port 900 - option opt '--split-pos=2 --oob' - option enabled 1 -config tpws - option port 901 - option opt '--split-tls=sni --disorder' - option enabled 0 diff --git a/init.d/openwrt-minimal/tpws/etc/firewall.user b/init.d/openwrt-minimal/tpws/etc/firewall.user deleted file mode 100644 index 2681cf56..00000000 --- a/init.d/openwrt-minimal/tpws/etc/firewall.user +++ /dev/null @@ -1,49 +0,0 @@ -DISABLE_IPV6=0 -TP_PORT=900 -TP_USER=daemon - -EXCLUDE4="10.0.0.0/8 172.16.0.0/12 192.168.0.0/16 169.254.0.0/16 127.0.0.0/8" -EXCLUDE6="fc00::/7 fe80::/10 ::1" -IPTS="iptables ip6tables" -[ "$DISABLE_IPV6" = 1 ] && IPTS=iptables - -exists() -{ - which "$1" >/dev/null 2>/dev/null -} - -ipt() -{ - $IPTABLES -C "$@" >/dev/null 2>/dev/null || $IPTABLES -I "$@" -} - -redirect_port() -{ - ipt tpws -t nat -p tcp --dport $1 -j REDIRECT --to-port $2 -} - -redirect() -{ - redirect_port 80 $TP_PORT - redirect_port 443 $TP_PORT -} - -for IPTABLES in $IPTS; do - $IPTABLES -t nat -N tpws 2>/dev/null - $IPTABLES -t nat -F tpws - redirect -done - -for net in $EXCLUDE4; do - iptables -t nat -I tpws -d $net -j RETURN -done -[ "$DISABLE_IPV6" = 1 ] || { - for net in $EXCLUDE6; do - ip6tables -t nat -I tpws -d $net -j RETURN - done -} - -for IPTABLES in $IPTS; do - ipt PREROUTING -t nat -j tpws - ipt OUTPUT -t nat -m owner ! --uid-owner $TP_USER -j tpws -done diff --git a/init.d/openwrt-minimal/tpws/etc/init.d/tpws b/init.d/openwrt-minimal/tpws/etc/init.d/tpws deleted file mode 100755 index 65d3f1d1..00000000 --- a/init.d/openwrt-minimal/tpws/etc/init.d/tpws +++ /dev/null @@ -1,34 +0,0 @@ -#!/bin/sh /etc/rc.common - -TPWS_DEFAULT=/usr/bin/tpws -TPWS_USER_DEFAULT=daemon - -START=99 -STOP=01 -USE_PROCD=1 - -tpws_instance() -{ - config_get "$@" - - local enabled port opt - - config_get_bool enabled "$1" enabled 0 - [ "$enabled" -eq 1 ] || return 1 - - config_get port "$1" port - config_get opt "$1" opt - - local COMMAND="$TPWS --user=$TPWS_USER --port=$port $opt" - procd_open_instance - procd_set_param command $COMMAND - procd_close_instance -} - -start_service() -{ - config_load tpws - config_get TPWS_USER defaults user $TPWS_USER_DEFAULT - config_get TPWS defaults tpws $TPWS_DEFAULT - config_foreach tpws_instance tpws -} diff --git a/init.d/openwrt-minimal/tpws/etc/nftables.d/90-tpws.nft b/init.d/openwrt-minimal/tpws/etc/nftables.d/90-tpws.nft deleted file mode 100644 index 819ca4f1..00000000 --- a/init.d/openwrt-minimal/tpws/etc/nftables.d/90-tpws.nft +++ /dev/null @@ -1,18 +0,0 @@ -set tpws_exclude4 { - type ipv4_addr; flags interval; auto-merge; - elements = { 10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,169.254.0.0/16,127.0.0.0/8 } -} -set tpws_exclude6 { - type ipv6_addr; flags interval; auto-merge; - elements = { fc00::/7, fe80::/10, ::1 } -} -chain tpws_pre { - type nat hook prerouting priority dstnat; policy accept; - tcp dport {80,443} ip daddr != @tpws_exclude4 redirect to :900 - tcp dport {80,443} ip6 daddr != @tpws_exclude6 redirect to :900 -} -chain tpws_out { - type nat hook output priority -100; policy accept; - tcp dport {80,443} skuid != daemon ip daddr != @tpws_exclude4 redirect to :900 - tcp dport {80,443} skuid != daemon ip6 daddr != @tpws_exclude6 redirect to :900 -} diff --git a/init.d/pfsense/zapret.sh b/init.d/pfsense/zapret.sh deleted file mode 100755 index d4bc4937..00000000 --- a/init.d/pfsense/zapret.sh +++ /dev/null @@ -1,24 +0,0 @@ -#!/bin/sh - -# this file should be placed to /usr/local/etc/rc.d and chmod 755 - -# prepare system - -kldload ipfw -kldload ipdivert - -# for older pfsense versions. newer do not have these sysctls -sysctl net.inet.ip.pfil.outbound=ipfw,pf -sysctl net.inet.ip.pfil.inbound=ipfw,pf -sysctl net.inet6.ip6.pfil.outbound=ipfw,pf -sysctl net.inet6.ip6.pfil.inbound=ipfw,pf - -# required for newer pfsense versions (2.6.0 tested) to return ipfw to functional state -pfctl -d ; pfctl -e - -# add ipfw rules and start daemon - -ipfw delete 100 -ipfw add 100 divert 989 tcp from any to any 80,443 out not diverted not sockarg -pkill ^dvtws$ -dvtws --daemon --port 989 --dpi-desync=multisplit diff --git a/init.d/runit/zapret/finish b/init.d/runit/zapret/finish deleted file mode 100755 index 2781ad36..00000000 --- a/init.d/runit/zapret/finish +++ /dev/null @@ -1,2 +0,0 @@ -#!/bin/sh -/opt/zapret/init.d/sysv/zapret stop diff --git a/init.d/runit/zapret/run b/init.d/runit/zapret/run deleted file mode 100755 index f216e934..00000000 --- a/init.d/runit/zapret/run +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh -/opt/zapret/init.d/sysv/zapret start -exec chpst -b zapret sleep infinity diff --git a/init.d/s6/zapret/down b/init.d/s6/zapret/down deleted file mode 100644 index aaab9114..00000000 --- a/init.d/s6/zapret/down +++ /dev/null @@ -1,2 +0,0 @@ -#!/bin/execlineb -P -exec /opt/zapret/init.d/sysv/zapret stop diff --git a/init.d/s6/zapret/type b/init.d/s6/zapret/type deleted file mode 100644 index bdd22a18..00000000 --- a/init.d/s6/zapret/type +++ /dev/null @@ -1 +0,0 @@ -oneshot diff --git a/init.d/s6/zapret/up b/init.d/s6/zapret/up deleted file mode 100644 index 42a12102..00000000 --- a/init.d/s6/zapret/up +++ /dev/null @@ -1,2 +0,0 @@ -#!/bin/execlineb -P -exec /opt/zapret/init.d/sysv/zapret start diff --git a/init.d/systemd/nfqws@.service b/init.d/systemd/nfqws@.service deleted file mode 100644 index 5a18c7ef..00000000 --- a/init.d/systemd/nfqws@.service +++ /dev/null @@ -1,62 +0,0 @@ -# Example systemd service unit for nfqws. Adjust for your installation. - -# WARNING ! This unit requires to compile nfqws using `make systemd` -# WARNING ! This makefile target enables special systemd notify support. - -# PREPARE -# install build depends -# make -C /opt/zapret systemd -# cp nfqws@service /lib/systemd/system -# systemctl daemon-reload - -# MANAGE INSTANCE -# prepare /etc/zapret/nfqws1.conf with nfqws parameters -# systemctl start nfqws@nfqws1 -# systemctl status nfqws@nfqws1 -# systemctl restart nfqws@nfqws1 -# systemctl enable nfqws@nfqws1 -# systemctl disable nfqws@nfqws1 -# systemctl stop nfqws@nfqws1 - -# DELETE -# rm /lib/systemd/system/nfqws@.service -# systemctl daemon-reload - - -[Unit] -After=network.target - -[Service] -Type=notify -Restart=on-failure - -ExecSearchPath=/opt/zapret/binaries/my -ExecStart=nfqws @${CONFIG_DIR}/${INSTANCE}.conf -Environment=CONFIG_DIR=/etc/zapret -Environment=INSTANCE=%i - -RestrictAddressFamilies=AF_NETLINK AF_UNIX AF_INET6 AF_INET - -LockPersonality=true -MemoryDenyWriteExecute=true -PrivateDevices=true -PrivateMounts=true -PrivateTmp=true -ProcSubset=pid -ProtectClock=true -ProtectControlGroups=true -ProtectHome=true -ProtectHostname=true -ProtectKernelLogs=true -ProtectKernelModules=true -ProtectKernelTunables=true -ProtectProc=invisible -ProtectSystem=full -RemoveIPC=true -RestrictNamespaces=true -RestrictRealtime=true -RestrictSUIDSGID=true -UMask=0077 - -[Install] -WantedBy=multi-user.target diff --git a/init.d/systemd/tpws@.service b/init.d/systemd/tpws@.service deleted file mode 100644 index c2780089..00000000 --- a/init.d/systemd/tpws@.service +++ /dev/null @@ -1,61 +0,0 @@ -# Example systemd service unit for tpws. Adjust for your installation. - -# WARNING ! This unit requires to compile tpws using `make systemd` -# WARNING ! This makefile target enables special systemd notify support. - -# PREPARE -# install build depends -# make -C /opt/zapret systemd -# cp tpws@service /lib/systemd/system -# systemctl daemon-reload - -# MANAGE INSTANCE -# prepare /etc/zapret/tpws1.conf with tpws parameters -# systemctl start tpws@tpws1 -# systemctl status tpws@tpws1 -# systemctl restart tpws@tpws1 -# systemctl enable tpws@tpws1 -# systemctl disable tpws@tpws1 -# systemctl stop tpws@tpws1 - -# DELETE -# rm /lib/systemd/system/tpws@.service -# systemctl daemon-reload - - -[Unit] -After=network.target - -[Service] -Type=notify -Restart=on-failure - -ExecSearchPath=/opt/zapret/binaries/my -ExecStart=tpws @${CONFIG_DIR}/${INSTANCE}.conf -Environment=CONFIG_DIR=/etc/zapret -Environment=INSTANCE=%i - -RestrictAddressFamilies=AF_NETLINK AF_UNIX AF_INET6 AF_INET - -LockPersonality=true -MemoryDenyWriteExecute=true -PrivateDevices=true -PrivateMounts=true -PrivateTmp=true -ProcSubset=pid -ProtectClock=true -ProtectControlGroups=true -ProtectHome=true -ProtectHostname=true -ProtectKernelLogs=true -ProtectKernelModules=true -ProtectProc=invisible -ProtectSystem=full -RemoveIPC=true -RestrictNamespaces=true -RestrictRealtime=true -RestrictSUIDSGID=true -UMask=0077 - -[Install] -WantedBy=multi-user.target diff --git a/init.d/systemd/zapret-list-update.service b/init.d/systemd/zapret-list-update.service deleted file mode 100644 index eeee1b06..00000000 --- a/init.d/systemd/zapret-list-update.service +++ /dev/null @@ -1,13 +0,0 @@ -[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 deleted file mode 100644 index 29379bde..00000000 --- a/init.d/systemd/zapret-list-update.timer +++ /dev/null @@ -1,11 +0,0 @@ -[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 deleted file mode 100644 index 9d3bf415..00000000 --- a/init.d/systemd/zapret.service +++ /dev/null @@ -1,17 +0,0 @@ -[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.d/.keep b/init.d/sysv/custom.d/.keep deleted file mode 100644 index e69de29b..00000000 diff --git a/init.d/sysv/functions b/init.d/sysv/functions deleted file mode 100644 index 59e488ee..00000000 --- a/init.d/sysv/functions +++ /dev/null @@ -1,271 +0,0 @@ -# init script functions library for desktop linux systems - -ZAPRET_BASE=${ZAPRET_BASE:-/opt/zapret} -ZAPRET_RW=${ZAPRET_RW:-"$ZAPRET_BASE"} -ZAPRET_CONFIG=${ZAPRET_CONFIG:-"$ZAPRET_RW/config"} -. "$ZAPRET_CONFIG" -. "$ZAPRET_BASE/common/base.sh" -. "$ZAPRET_BASE/common/fwtype.sh" -. "$ZAPRET_BASE/common/linux_iphelper.sh" -. "$ZAPRET_BASE/common/ipt.sh" -. "$ZAPRET_BASE/common/nft.sh" -. "$ZAPRET_BASE/common/linux_fw.sh" -. "$ZAPRET_BASE/common/linux_daemons.sh" -. "$ZAPRET_BASE/common/list.sh" -. "$ZAPRET_BASE/common/custom.sh" -CUSTOM_DIR="$ZAPRET_RW/init.d/sysv" - - -user_exists() -{ - id -u $1 >/dev/null 2>/dev/null -} -useradd_compat() -{ - # $1 - username - # skip for readonly systems - [ -w "/etc" ] && { - if exists useradd ; then - useradd --no-create-home --system --shell /bin/false $1 - elif is_linked_to_busybox adduser ; then - # some systems may miss nogroup group in /etc/group - # adduser fails if it's absent and no group is specified - addgroup nogroup 2>/dev/null - # busybox has special adduser syntax - adduser -S -H -D $1 - elif exists adduser; then - adduser --no-create-home --system --disabled-login $1 - fi - } - user_exists $1 -} -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 - user_exists $WS_USER || { - # fallback to daemon if we cant add WS_USER - useradd_compat $WS_USER || { - for user in daemon nobody; do - user_exists $user && { - WS_USER=$user - return 0 - } - done - return 1 - } - } -} - -# this complex user selection allows to survive in any locked/readonly/minimalistic environment -[ -n "$WS_USER" ] || WS_USER=tpws -if prepare_user; then - USEROPT="--user=$WS_USER" -else - WS_USER=1 - USEROPT="--uid $WS_USER:$WS_USER" -fi - -PIDDIR=/var/run -IPSET_CR="$ZAPRET_BASE/ipset/create_ipset.sh" - -[ -n "$DESYNC_MARK" ] || DESYNC_MARK=0x40000000 -[ -n "$DESYNC_MARK_POSTNAT" ] || DESYNC_MARK_POSTNAT=0x20000000 - -[ -n "$QNUM" ] || QNUM=200 -[ -n "$NFQWS" ] || NFQWS="$ZAPRET_BASE/nfq/nfqws" -NFQWS_OPT_BASE="$USEROPT --dpi-desync-fwmark=$DESYNC_MARK" - -[ -n "$TPPORT" ] || TPPORT=988 -[ -n "$TPPORT_SOCKS" ] || TPPORT_SOCKS=987 -[ -n "$TPWS" ] || TPWS="$ZAPRET_BASE/tpws/tpws" -TPWS_LOCALHOST4=127.0.0.127 - -TPWS_OPT_BASE="$USEROPT" -TPWS_OPT_BASE4="--bind-addr=$TPWS_LOCALHOST4" -TPWS_OPT_BASE6="--bind-addr=::1" -TPWS_WAIT="--bind-wait-ifup=30 --bind-wait-ip=30" -TPWS_WAIT_SOCKS6="$TPWS_WAIT --bind-wait-ip-linklocal=30" -# first wait for lan to ifup, then wait for bind-wait-ip-linklocal seconds for link local address and bind-wait-ip for any ipv6 as the worst case -TPWS_OPT_BASE6_PRE="--bind-linklocal=prefer $TPWS_WAIT --bind-wait-ip-linklocal=3" - -dnat6_target() -{ - _dnat6_target "$@" -} -set_route_localnet() -{ - _set_route_localnet $1 $IFACE_LAN -} - -fw_nfqws_post4() -{ - _fw_nfqws_post4 $1 "$2" $3 "$IFACE_WAN" -} -fw_nfqws_post6() -{ - _fw_nfqws_post6 $1 "$2" $3 "${IFACE_WAN6:-$IFACE_WAN}" -} -fw_nfqws_pre4() -{ - _fw_nfqws_pre4 $1 "$2" $3 "$IFACE_WAN" -} -fw_nfqws_pre6() -{ - _fw_nfqws_pre6 $1 "$2" $3 "${IFACE_WAN6:-$IFACE_WAN}" -} -fw_tpws4() -{ - _fw_tpws4 $1 "$2" $3 "$IFACE_LAN" "$IFACE_WAN" -} -fw_tpws6() -{ - _fw_tpws6 $1 "$2" $3 "$IFACE_LAN" "${IFACE_WAN6:-$IFACE_WAN}" -} -nft_fw_tpws4() -{ - _nft_fw_tpws4 "$1" $2 "$IFACE_WAN" -} -nft_fw_tpws6() -{ - _nft_fw_tpws6 "$1" $2 "$IFACE_LAN" "${IFACE_WAN6:-$IFACE_WAN}" -} -nft_fw_nfqws_post4() -{ - _nft_fw_nfqws_post4 "$1" $2 "$IFACE_WAN" -} -nft_fw_nfqws_post6() -{ - _nft_fw_nfqws_post6 "$1" $2 "${IFACE_WAN6:-$IFACE_WAN}" -} -nft_fw_nfqws_pre4() -{ - _nft_fw_nfqws_pre4 "$1" $2 "$IFACE_WAN" -} -nft_fw_nfqws_pre6() -{ - _nft_fw_nfqws_pre6 "$1" $2 "${IFACE_WAN6:-$IFACE_WAN}" -} -nft_fill_ifsets_overload() -{ - nft_fill_ifsets "$IFACE_LAN" "$IFACE_WAN" "${IFACE_WAN6:-$IFACE_WAN}" -} - - -run_daemon() -{ - # $1 - daemon number : 1,2,3,... - # $2 - daemon - # $3 - daemon args - # use $PIDDIR/$DAEMONBASE$1.pid as pidfile - - local DAEMONBASE="$(basename "$2")" - local PID= PIDFILE=$PIDDIR/$DAEMONBASE$1.pid - echo "Starting daemon $1: $2 $3" - - [ -f "$PIDFILE" ] && { - read PID <"$PIDFILE" - [ -d "/proc/$PID" ] || PID= - } - - if [ -n "$PID" ]; then - echo already running - else - "$2" $3 >/dev/null & - PID=$! - if [ -n "$PID" ]; then - echo $PID >$PIDFILE - else - echo could not start daemon $1 : $2 $3 - false - fi - fi -} -stop_daemon() -{ - # $1 - daemon number : 1,2,3,... - # $2 - daemon - # use $PIDDIR/$DAEMONBASE$1.pid as pidfile - local DAEMONBASE="$(basename "$2")" - local PID PIDFILE=$PIDDIR/$DAEMONBASE$1.pid - echo "Stopping daemon $1: $2" - if [ -f "$PIDFILE" ]; then - read PID <"$PIDFILE" - kill $PID - rm -f "$PIDFILE" - else - echo no pidfile : $PIDFILE - fi -} -do_daemon() -{ - # $1 - 1 - run, 0 - stop - on_off_function run_daemon stop_daemon "$@" -} - - -do_tpws() -{ - # $1 : 1 - run, 0 - stop - # $2 : daemon number - # $3 : daemon args - - [ "$DISABLE_IPV4" = "1" ] && [ "$DISABLE_IPV6" = "1" ] && return 0 - - local OPT="$TPWS_OPT_BASE" - - [ "$DISABLE_IPV4" = "1" ] || OPT="$OPT $TPWS_OPT_BASE4" - [ "$DISABLE_IPV6" = "1" ] || { - OPT="$OPT $TPWS_OPT_BASE6" - for lan in $IFACE_LAN; do - OPT="$OPT --bind-iface6=$lan $TPWS_OPT_BASE6_PRE" - done - } - - do_daemon $1 $2 "$TPWS" "$OPT $3" -} -do_tpws_socks() -{ - # $1 : 1 - run, 0 - stop - # $2 : daemon number - # $3 : daemon args - - [ "$DISABLE_IPV4" = "1" ] && [ "$DISABLE_IPV6" = "1" ] && return 0 - - local opt="$TPWS_OPT_BASE --socks" - - tpws_apply_socks_binds opt - - do_daemon $1 $2 "$TPWS" "$opt $3" -} - -do_nfqws() -{ - # $1 : 1 - run, 0 - stop - # $2 : daemon number - # $3 : daemon args - - do_daemon $1 $2 "$NFQWS" "$NFQWS_OPT_BASE $3" -} - -tpws_apply_socks_binds() -{ - local o - - [ "$DISABLE_IPV4" = "1" ] || o="--bind-addr=127.0.0.1" - [ "$DISABLE_IPV6" = "1" ] || o="$o --bind-addr=::1" - - for lan in $IFACE_LAN; do - [ "$DISABLE_IPV4" = "1" ] || o="$o --bind-iface4=$lan $TPWS_WAIT" - [ "$DISABLE_IPV6" = "1" ] || o="$o --bind-iface6=$lan --bind-linklocal=unwanted $TPWS_WAIT_SOCKS6" - done - eval $1="\"\$$1 $o\"" -} - - -create_ipset() -{ - echo "Creating ip list table (firewall type $FWTYPE)" - "$IPSET_CR" "$@" -} diff --git a/init.d/sysv/zapret b/init.d/sysv/zapret deleted file mode 100755 index bb15abb7..00000000 --- a/init.d/sysv/zapret +++ /dev/null @@ -1,82 +0,0 @@ -#!/bin/sh -### BEGIN INIT INFO -# Provides: zapret -# Required-Start: $local_fs $network -# Required-Stop: $local_fs $network -# Default-Start: 2 3 4 5 -# Default-Stop: 0 1 6 -### END INIT INFO - -SCRIPT=$(readlink -f "$0") -EXEDIR=$(dirname "$SCRIPT") -ZAPRET_BASE=$(readlink -f "$EXEDIR/../..") -. "$EXEDIR/functions" - -NAME=zapret -DESC=anti-zapret - -do_start() -{ - zapret_run_daemons - [ "$INIT_APPLY_FW" != "1" ] || { zapret_apply_firewall; } -} -do_stop() -{ - zapret_stop_daemons - [ "$INIT_APPLY_FW" != "1" ] || zapret_unapply_firewall -} - -case "$1" in - start) - do_start - ;; - - stop) - do_stop - ;; - - restart) - do_stop - do_start - ;; - - start-fw|start_fw) - zapret_apply_firewall - ;; - stop-fw|stop_fw) - zapret_unapply_firewall - ;; - - restart-fw|restart_fw) - zapret_unapply_firewall - zapret_apply_firewall - ;; - - start-daemons|start_daemons) - zapret_run_daemons - ;; - stop-daemons|stop_daemons) - zapret_stop_daemons - ;; - restart-daemons|restart_daemons) - zapret_stop_daemons - zapret_run_daemons - ;; - - reload-ifsets|reload_ifsets) - zapret_reload_ifsets - ;; - list-ifsets|list_ifsets) - zapret_list_ifsets - ;; - list-table|list_table) - zapret_list_table - ;; - - *) - echo "Usage: $SCRIPT {start|stop|restart|start-fw|stop-fw|restart-fw|start-daemons|stop-daemons|restart-daemons|reload-ifsets|list-ifsets|list-table}" >&2 - exit 1 - ;; -esac - -exit 0 diff --git a/init.d/windivert.filter.examples/README.txt b/init.d/windivert.filter.examples/README.txt deleted file mode 100644 index decde507..00000000 --- a/init.d/windivert.filter.examples/README.txt +++ /dev/null @@ -1,14 +0,0 @@ -Цель этих фильтров - отсекать полезную нагрузку в режиме ядра, не насилуя процессор перенаправлением целого потока на winws. -Задействуются через `winws --wf-raw-part=@filename`. Может быть несколько частичных фильтров. Они могут сочетаться с --wf-tcp и --wf-udp. -Однако, язык фильтров windivert не содержит операций с битовыми полями, сдвигов и побитовой логики. -Поэтому фильтры получились более слабыми, способными передавать неправильную нагрузку. -Дофильтрация производится силами winws. - -Описание языка фильтров : https://reqrypt.org/windivert-doc.html#filter_language -Пример инстанса для пробития медиапотоков в discord : `winws --wf-raw-part=@windivert_part.discord_media.txt --wf-raw-part=@windivert_part.stun.txt --filter-l7=stun,discord --dpi-desync=fake` - - -These filters are invoked using `winws --wf-raw-part=@filename`. Multiple filter parts are supported. They can be combined with --wf-tcp and --wf-udp. -Filters are kernel mode and save great amount of CPU. -However windivert cannot filter by bit fields, lacks shift and bitwise logic operations. -Filters are relaxed and can pass wrong payloads. Finer filtering is done by winws. diff --git a/init.d/windivert.filter.examples/windivert_part.discord_media.txt b/init.d/windivert.filter.examples/windivert_part.discord_media.txt deleted file mode 100644 index 26c2014b..00000000 --- a/init.d/windivert.filter.examples/windivert_part.discord_media.txt +++ /dev/null @@ -1,20 +0,0 @@ - outbound and ip and - udp.DstPort>=50000 and udp.DstPort<=50099 and - udp.PayloadLength=74 and - udp.Payload32[0]=0x00010046 and - udp.Payload32[2]=0 and - udp.Payload32[3]=0 and - udp.Payload32[4]=0 and - udp.Payload32[5]=0 and - udp.Payload32[6]=0 and - udp.Payload32[7]=0 and - udp.Payload32[8]=0 and - udp.Payload32[9]=0 and - udp.Payload32[10]=0 and - udp.Payload32[11]=0 and - udp.Payload32[12]=0 and - udp.Payload32[13]=0 and - udp.Payload32[14]=0 and - udp.Payload32[15]=0 and - udp.Payload32[16]=0 and - udp.Payload32[17]=0 \ No newline at end of file diff --git a/init.d/windivert.filter.examples/windivert_part.quic_initial_ietf.txt b/init.d/windivert.filter.examples/windivert_part.quic_initial_ietf.txt deleted file mode 100644 index 6e01bef9..00000000 --- a/init.d/windivert.filter.examples/windivert_part.quic_initial_ietf.txt +++ /dev/null @@ -1,4 +0,0 @@ - outbound and - udp.PayloadLength>=256 and - udp.Payload[0]>=0xC0 and udp.Payload[0]<0xD0 and - udp.Payload[1]=0 and udp.Payload16[1]=0 and udp.Payload[4]=1 diff --git a/init.d/windivert.filter.examples/windivert_part.stun.txt b/init.d/windivert.filter.examples/windivert_part.stun.txt deleted file mode 100644 index a7616902..00000000 --- a/init.d/windivert.filter.examples/windivert_part.stun.txt +++ /dev/null @@ -1,3 +0,0 @@ - outbound and - udp.PayloadLength>=20 and - udp.Payload32[1]=0x2112A442 and udp.Payload[0]<0x40 \ No newline at end of file diff --git a/init.d/windivert.filter.examples/windivert_part.wireguard.txt b/init.d/windivert.filter.examples/windivert_part.wireguard.txt deleted file mode 100644 index 9b07c08f..00000000 --- a/init.d/windivert.filter.examples/windivert_part.wireguard.txt +++ /dev/null @@ -1,3 +0,0 @@ - outbound and - udp.PayloadLength=148 and - udp.Payload[0]=0x01 \ No newline at end of file diff --git a/ip2net/Makefile b/ip2net/Makefile deleted file mode 100644 index 3c442563..00000000 --- a/ip2net/Makefile +++ /dev/null @@ -1,33 +0,0 @@ -CC ?= cc -OPTIMIZE ?= -Os -CFLAGS += -std=gnu99 $(OPTIMIZE) -flto=auto -CFLAGS_BSD = -Wno-address-of-packed-member -CFLAGS_WIN = -static -LIBS = -LIBS_WIN = -lws2_32 -SRC_FILES = ip2net.c qsort.c - -all: ip2net - -ip2net: $(SRC_FILES) - $(CC) -s $(CFLAGS) -o ip2net $(SRC_FILES) $(LIBS) $(LDFLAGS) - -systemd: ip2net - -android: ip2net - -bsd: $(SRC_FILES) - $(CC) -s $(CFLAGS) $(CFLAGS_BSD) -o ip2net $(SRC_FILES) $(LIBS) $(LDFLAGS) - -mac: $(SRC_FILES) - $(CC) $(CFLAGS) $(CFLAGS_BSD) -o ip2neta $(SRC_FILES) -target arm64-apple-macos10.8 $(LIBS) $(LDFLAGS) - $(CC) $(CFLAGS) $(CFLAGS_BSD) -o ip2netx $(SRC_FILES) -target x86_64-apple-macos10.8 $(LIBS) $(LDFLAGS) - strip ip2neta ip2netx - lipo -create -output ip2net ip2netx ip2neta - rm -f ip2netx ip2neta - -win: $(SRC_FILES) - $(CC) -s $(CFLAGS) $(CFLAGS_WIN) -o ip2net $(SRC_FILES) $(LIBS_WIN) $(LDFLAGS) - -clean: - rm -f ip2net *.o diff --git a/ip2net/ip2net.c b/ip2net/ip2net.c deleted file mode 100644 index 9badb080..00000000 --- a/ip2net/ip2net.c +++ /dev/null @@ -1,516 +0,0 @@ -// 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 - -// can be compiled in mingw. msvc not supported because of absent getopt - -#include -#include -#include -#include -#include -#ifdef _WIN32 -#undef _WIN32_WINNT -#define _WIN32_WINNT 0x600 -#include -#include -#include -#else -#include -#include -#include -#endif -#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 zct<32 ? ~((1u << zct) - 1) : 0; -} -// 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; -} - - - -#if defined(__GNUC__) && !defined(__llvm__) -__attribute__((optimize ("no-strict-aliasing"))) -#endif -static int cmp6(const void * a, const void * b, void *arg) -{ - // this function is critical for sort performance - // on big endian systems cpu byte order is equal to network byte order - // no conversion required. it's possible to improve speed by using big size compares - // on little endian systems byte conversion also gives better result than byte comparision - // 64-bit archs often have cpu command to reverse byte order - // assume that a and b are properly aligned - -#if defined(__BYTE_ORDER__) && ((__BYTE_ORDER__==__ORDER_BIG_ENDIAN__) || (__BYTE_ORDER__==__ORDER_LITTLE_ENDIAN__)) - - uint64_t aa,bb; -#if __BYTE_ORDER__==__ORDER_LITTLE_ENDIAN__ - aa = __builtin_bswap64(((uint64_t*)((struct in6_addr *)a)->s6_addr)[0]); - bb = __builtin_bswap64(((uint64_t*)((struct in6_addr *)b)->s6_addr)[0]); -#else - aa = ((uint64_t*)((struct in6_addr *)a)->s6_addr)[0]; - bb = ((uint64_t*)((struct in6_addr *)b)->s6_addr)[0]; -#endif - if (aa < bb) - return -1; - else if (aa > bb) - return 1; - else - { -#if __BYTE_ORDER__==__ORDER_LITTLE_ENDIAN__ - aa = __builtin_bswap64(((uint64_t*)((struct in6_addr *)a)->s6_addr)[1]); - bb = __builtin_bswap64(((uint64_t*)((struct in6_addr *)b)->s6_addr)[1]); -#else - aa = ((uint64_t*)((struct in6_addr *)a)->s6_addr)[1]; - bb = ((uint64_t*)((struct in6_addr *)b)->s6_addr)[1]; -#endif - return aa < bb ? -1 : aa > bb ? 1 : 0; - } - -#else - // fallback case - 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; -#endif -} - -// 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_make(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] = ~((1u << (zct & 7)) - 1); - } -} -static struct in6_addr ip6_mask[129]; -static void mask_from_bitcount6_prepare(void) -{ - for (int zct=0;zct<=128;zct++) mask_from_bitcount6_make(zct, ip6_mask+zct); -} -static inline const struct in6_addr *mask_from_bitcount6(uint32_t zct) -{ - return ip6_mask+zct; -} - - -/* -// this is "correct" solution for strict aliasing feature -// but I don't like this style of coding -// write what I don't mean to force smart optimizer to do what it's best -// it produces better code sometimes but not on all compilers/versions/archs -// sometimes it even generates real memcpy calls (mips32,arm32) -// so I will not do it - -static void ip6_and(const struct in6_addr *a, const struct in6_addr *b, struct in6_addr *result) -{ - uint64_t a_addr[2], b_addr[2]; - memcpy(a_addr, a->s6_addr, 16); - memcpy(b_addr, b->s6_addr, 16); - a_addr[0] &= b_addr[0]; - a_addr[1] &= b_addr[1]; - memcpy(result->s6_addr, a_addr, 16); -} -*/ - -// YES, from my point of view C should work as a portable assembler. It must do what I instruct it to do. -// that's why I disable strict aliasing for this function. I observed gcc can miscompile with O2/O3 setting if inlined and not coded "correct" -// result = a & b -// assume that a and b are properly aligned -#if defined(__GNUC__) && !defined(__llvm__) -__attribute__((optimize ("no-strict-aliasing"))) -#endif -static void ip6_and(const struct in6_addr * restrict a, const struct in6_addr * restrict b, struct in6_addr * restrict result) -{ -#ifdef __SIZEOF_INT128__ - // gcc and clang have 128 bit int types on some 64-bit archs. take some advantage - *((unsigned __int128*)result->s6_addr) = *((unsigned __int128*)a->s6_addr) & *((unsigned __int128*)b->s6_addr); -#else - ((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]; -#endif -} - -static void rtrim(char *s) -{ - if (s) - for (char *p = s + strlen(s) - 1; p >= s && (*p == '\n' || *p == '\r' || *p == ' ' || *p == '\t'); 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(void) -{ - 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); -} - -#define STRINGIFY(x) #x -#define TOSTRING(x) STRINGIFY(x) -#if defined(ZAPRET_GH_VER) || defined (ZAPRET_GH_HASH) -#define PRINT_VER printf("github version %s (%s)\n\n", TOSTRING(ZAPRET_GH_VER), TOSTRING(ZAPRET_GH_HASH)) -#else -#define PRINT_VER printf("self-built version %s %s\n\n", __DATE__, __TIME__) -#endif - -enum opt_indices { - IDX_HELP, - IDX_H, - IDX_4, - IDX_6, - IDX_PREFIX_LENGTH, - IDX_V4_THRESHOLD, - IDX_V6_THRESHOLD, - IDX_LAST, -}; - -static const struct option long_options[] = { - [IDX_HELP] = {"help", no_argument, 0, 0}, - [IDX_H] = {"h", no_argument, 0, 0}, - [IDX_4] = {"4", no_argument, 0, 0}, - [IDX_6] = {"6", no_argument, 0, 0}, - [IDX_PREFIX_LENGTH] = {"prefix-length", required_argument, 0, 0}, - [IDX_V4_THRESHOLD] = {"v4-threshold", required_argument, 0, 0}, - [IDX_V6_THRESHOLD] = {"v6-threshold", required_argument, 0, 0}, - [IDX_LAST] = {NULL, 0, NULL, 0}, -}; - -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; - - while ((v = getopt_long_only(argc, argv, "", long_options, &option_index)) != -1) - { - if (v) exithelp(); - switch (option_index) - { - case IDX_HELP: - case IDX_H: - PRINT_VER; - exithelp(); - break; - case IDX_4: - params.ipv6 = false; - break; - case IDX_6: - params.ipv6 = true; - break; - case IDX_PREFIX_LENGTH: - i = sscanf(optarg,"%u-%u",&plen1,&plen2); - if (i == 1) plen2 = plen1; - if (i<=0 || plen2=params.pctdiv) - { - fprintf(stderr, "invalid parameter for v4-threshold : %s\n", optarg); - exit(1); - } - break; - case IDX_V6_THRESHOLD: - 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 - if (sscanf(s + 1, "%u", &zct)==1 && zct!=128) - { - if (zct<128) printf("%s/%u\n", str, zct); - continue; - } - } - else if (d=='-') - { - if (inet_pton(AF_INET6, s+1, &a)) printf("%s-%s\n", str, s+1); - 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); - mask_from_bitcount6_prepare(); - - /* - for(uint32_t i=0;i= params.zct_min; zct--) - { - mask = mask_from_bitcount6(zct); - 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) - // network was found - ip6_and(iplist + pos, mask_from_bitcount6(zct_best), &ip_start); - else - ip_start = iplist[pos], pos_end = pos + 1; // network not found, use single ip - inet_ntop(AF_INET6, &ip_start, str, sizeof(str)); - printf(zct_best ? "%s/%u\n" : "%s\n", str, 128 - zct_best); - - pos = pos_end; - } - - free(iplist); - } - else // ipv4 - { - uint32_t u1,u2,u3,u4, u11,u22,u33,u44, ip; - uint32_t *iplist = NULL, *iplist_new, i; - - while (fgets(str, sizeof(str), stdin)) - { - if ((i = sscanf(str, "%u.%u.%u.%u-%u.%u.%u.%u", &u1, &u2, &u3, &u4, &u11, &u22, &u33, &u44)) >= 8 && - !(u1 & 0xFFFFFF00) && !(u2 & 0xFFFFFF00) && !(u3 & 0xFFFFFF00) && !(u4 & 0xFFFFFF00) && - !(u11 & 0xFFFFFF00) && !(u22 & 0xFFFFFF00) && !(u33 & 0xFFFFFF00) && !(u44 & 0xFFFFFF00)) - { - printf("%u.%u.%u.%u-%u.%u.%u.%u\n", u1, u2, u3, u4, u11, u22, u33, u44); - } - else - if ((i = sscanf(str, "%u.%u.%u.%u/%u", &u1, &u2, &u3, &u4, &zct)) >= 4 && - !(u1 & 0xFFFFFF00) && !(u2 & 0xFFFFFF00) && !(u3 & 0xFFFFFF00) && !(u4 & 0xFFFFFF00)) - { - if (i == 5 && zct != 32) - { - // we have subnet x.x.x.x/y - // output it as is if valid, ignore otherwise - if (zct < 32) - printf("%u.%u.%u.%u/%u\n", u1, u2, u3, u4, zct); - } - else - { - ip = u1 << 24 | u2 << 16 | u3 << 8 | u4; - if (ipct >= iplist_size) - { - iplist_size += ALLOC_STEP; - iplist_new = (uint32_t*)(iplist ? realloc(iplist, sizeof(*iplist)*iplist_size) : malloc(sizeof(*iplist)*iplist_size)); - if (!iplist_new) - { - free(iplist); - fprintf(stderr, "out of memory\n"); - return 100; - } - iplist = iplist_new; - } - iplist[ipct++] = ip; - } - } - } - - gnu_quicksort(iplist, ipct, sizeof(*iplist), ucmp, NULL); - ipct = unique(iplist, ipct); - - while (pos < ipct) - { - uint32_t mask, ip_start, ip_end, subnet_ct; - uint32_t ip_ct_best = 0, zct_best = 0; - - // find smallest network with maximum ip coverage with no less than mul/div percent addresses - for (zct = params.zct_max; zct >= params.zct_min; zct--) - { - mask = mask_from_bitcount(zct); - ip_start = iplist[pos] & mask; - subnet_ct = ~mask + 1; - if (iplist[pos] > (ip_start + subnet_ct*(params.pctdiv - params.pctmult) / params.pctdiv)) - continue; // ip is higher than (1-PCT). definitely coverage is not enough. skip searching - ip_end = ip_start | ~mask; - for (p=pos+1, ip_ct=1; p < ipct && iplist[p] <= ip_end; p++) ip_ct++; // count ips within subnet range - if (ip_ct == 1) break; - if (ip_ct >= (subnet_ct*params.pctmult / params.pctdiv)) - { - // network found. but is there smaller network with the same ip_ct ? dont do carpet bombing if possible, use smaller subnets - if (!ip_ct_best || ip_ct == ip_ct_best) - { - ip_ct_best = ip_ct; - zct_best = zct; - pos_end = p; - } - else - break; - } - } - if (zct_best) - ip_start = iplist[pos] & mask_from_bitcount(zct_best); - else - 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 deleted file mode 100644 index 2ee11850..00000000 --- a/ip2net/qsort.c +++ /dev/null @@ -1,250 +0,0 @@ -/* 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 deleted file mode 100644 index f537ab75..00000000 --- a/ip2net/qsort.h +++ /dev/null @@ -1,6 +0,0 @@ -#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/mdig/Makefile b/mdig/Makefile deleted file mode 100644 index e4a011bc..00000000 --- a/mdig/Makefile +++ /dev/null @@ -1,35 +0,0 @@ -CC ?= cc -OPTIMIZE ?= -Os -CFLAGS += -std=gnu99 $(OPTIMIZE) -CFLAGS_BSD = -Wno-address-of-packed-member -CFLAGS_WIN = -static -LIBS = -lpthread -LIBS_ANDROID = -LIBS_WIN = -lws2_32 -SRC_FILES = *.c - -all: mdig - -mdig: $(SRC_FILES) - $(CC) -s $(CFLAGS) -o mdig $(SRC_FILES) $(LIBS) $(LDFLAGS) - -systemd: mdig - -android: $(SRC_FILES) - $(CC) -s $(CFLAGS) -o mdig $(SRC_FILES) $(LIBS_ANDROID) $(LDFLAGS) - -bsd: $(SRC_FILES) - $(CC) -s $(CFLAGS) $(CFLAGS_BSD) -o mdig $(SRC_FILES) $(LIBS) $(LDFLAGS) - -mac: $(SRC_FILES) - $(CC) $(CFLAGS) $(CFLAGS_BSD) -o mdiga $(SRC_FILES) -target arm64-apple-macos10.8 $(LIBS_BSD) $(LDFLAGS) - $(CC) $(CFLAGS) $(CFLAGS_BSD) -o mdigx $(SRC_FILES) -target x86_64-apple-macos10.8 $(LIBS_BSD) $(LDFLAGS) - strip mdiga mdigx - lipo -create -output mdig mdigx mdiga - rm -f mdigx mdiga - -win: $(SRC_FILES) - $(CC) -s $(CFLAGS) $(CFLAGS_WIN) -o mdig $(SRC_FILES) $(LIBS_WIN) $(LDFLAGS) - -clean: - rm -f mdig *.o diff --git a/mdig/mdig.c b/mdig/mdig.c deleted file mode 100644 index 847f2831..00000000 --- a/mdig/mdig.c +++ /dev/null @@ -1,596 +0,0 @@ -// 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 -#ifdef _WIN32 -#undef _WIN32_WINNT -#define _WIN32_WINNT 0x600 -#include -#include -#include -#include -#else -#include -#include -#include -#include -#include -#endif -#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 == '\t'); p--) *p = '\0'; -} - -static const char* eai_str(int r) -{ - switch (r) - { - case EAI_NONAME: - return "EAI_NONAME"; - case EAI_AGAIN: - return "EAI_AGAIN"; -#ifdef EAI_ADDRFAMILY - case EAI_ADDRFAMILY: - return "EAI_ADDRFAMILY"; -#endif -#ifdef EAI_NODATA - case EAI_NODATA: - return "EAI_NODATA"; -#endif - 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_SERVICE: - return "EAI_SERVICE"; - case EAI_SOCKTYPE: - return "EAI_SOCKTYPE"; -#ifdef EAI_SYSTEM - case EAI_SYSTEM: - return "EAI_SYSTEM"; -#endif - default: - return "UNKNOWN"; - } -} - -static bool dom_valid(char *dom) -{ - if (!dom || *dom=='.') return false; - for (; *dom; dom++) - if (*dom < 0x20 || (*dom & 0x80) || !(*dom == '.' || *dom == '-' || *dom == '_' || (*dom >= '0' && *dom <= '9') || (*dom >= 'a' && *dom <= 'z') || (*dom >= 'A' && *dom <= 'Z'))) - return false; - return true; -} - -static void invalid_domain_beautify(char *dom) -{ - for (int i = 0; *dom && i < 64; i++, dom++) - if (*dom < 0x20 || *dom<0) *dom = '?'; - if (*dom) *dom = 0; -} - -#define FAMILY4 1 -#define FAMILY6 2 -static struct -{ - char verbose; - char family; - int threads; - time_t start_time; - pthread_mutex_t flock; - pthread_mutex_t slock; // stats lock - int stats_every, stats_ct, stats_ct_ok; // stats - FILE *F_log_resolved, *F_log_failed; -} 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, ...) -{ - if (stream) - { - 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) - { - time_t tm = time(NULL)-glob.start_time; - interlocked_fprintf(stderr, "mdig stats : %02u:%02u:%02u : domains=%d success=%d error=%d\n", (unsigned int)(tm/3600), (unsigned int)((tm/60)%60), (unsigned int)(tm%60), ct, ct_ok, ct - ct_ok); - } -} - -static void stat_plus(bool 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]; - bool 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))) - { - is_ok = false; - if (*dom) - { - uint16_t family; - char *s_mask, s_ip[sizeof(dom)]; - - 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)==1) - { - 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 = true; - 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 if (dom_valid(dom)) - { - 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 = true; - } - break; - } - } - else if (glob.verbose) - { - char dom2[sizeof(dom)]; - strcpy(dom2,dom); - invalid_domain_beautify(dom2); - VLOG("invalid domain : %s", dom2); - } - interlocked_fprintf(is_ok ? glob.F_log_resolved : glob.F_log_failed,"%s\n",dom); - } - stat_plus(is_ok); - } - VLOG("ended"); - return NULL; -} - -static int run_threads(void) -{ - int i, thread; - pthread_t *t; - - glob.stats_ct = glob.stats_ct_ok = 0; - time(&glob.start_time); - 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; -} - -// slightly patched musl code -size_t dns_mk_query_blob(uint8_t op, const char *dname, uint8_t class, uint8_t type, uint8_t *buf, size_t buflen) -{ - int i, j; - uint16_t id; - struct timespec ts; - size_t l = strnlen(dname, 255); - size_t n; - - if (l && dname[l-1]=='.') l--; - if (l && dname[l-1]=='.') return 0; - n = 17+l+!!l; - if (l>253 || buflen15u) return 0; - - /* Construct query template - ID will be filled later */ - memset(buf, 0, n); - buf[2] = (op<<3) | 1; - buf[5] = 1; - memcpy((char *)buf+13, dname, l); - for (i=13; buf[i]; i=j+1) - { - for (j=i; buf[j] && buf[j] != '.'; j++); - if (j-i-1u > 62u) return 0; - buf[i-1] = j-i; - } - buf[i+1] = type; - buf[i+3] = class; - - /* Make a reasonably unpredictable id */ - clock_gettime(CLOCK_REALTIME, &ts); - id = (uint16_t)ts.tv_nsec + (uint16_t)(ts.tv_nsec>>16); - buf[0] = id>>8; - buf[1] = id; - - return n; -} -int dns_make_query(const char *dom, char family) -{ - uint8_t q[280]; - size_t l = dns_mk_query_blob(0, dom, 1, family == FAMILY6 ? 28 : 1, q, sizeof(q)); - if (!l) - { - fprintf(stderr, "could not make DNS query\n"); - return 1; - } -#ifdef _WIN32 - _setmode(_fileno(stdout), _O_BINARY); -#endif - if (fwrite(q,l,1,stdout)!=1) - { - fprintf(stderr, "could not write DNS query blob to stdout\n"); - return 10; - } - return 0; -} - -bool dns_parse_print(const uint8_t *a, size_t len) -{ - // check of minimum header length and response flag - uint16_t k, dlen, qcount = a[4]<<8 | a[5], acount = a[6]<<8 | a[7]; - char s_ip[40]; - - if (len<12 || !(a[2]&0x80)) return false; - a+=12; len-=12; - for(k=0;klen) return false; - // skip to next label - len -= *a+1; a += *a+1; - } - if (len<5) return false; - // skip zero length label, type, class - a+=5; len-=5; - } - for(k=0;k\n" - " --family=<4|6|46>\t\t; ipv4, ipv6, ipv4+ipv6\n" - " --verbose\t\t\t; print query progress to stderr\n" - " --stats=N\t\t\t; print resolve stats to stderr every N domains\n" - " --log-resolved=\t\t; log successfully resolved domains to a file\n" - " --log-failed=\t\t; log failed domains to a file\n" - " --dns-make-query=\t; output to stdout binary blob with DNS query. use --family to specify ip version.\n" - " --dns-parse-query\t\t; read from stdin binary DNS answer blob and parse it to ipv4/ipv6 addresses\n" - ); - exit(1); -} - -#define STRINGIFY(x) #x -#define TOSTRING(x) STRINGIFY(x) -#if defined(ZAPRET_GH_VER) || defined (ZAPRET_GH_HASH) -#define PRINT_VER printf("github version %s (%s)\n\n", TOSTRING(ZAPRET_GH_VER), TOSTRING(ZAPRET_GH_HASH)) -#else -#define PRINT_VER printf("self-built version %s %s\n\n", __DATE__, __TIME__) -#endif - -enum opt_indices { - IDX_HELP, - IDX_THREADS, - IDX_FAMILY, - IDX_VERBOSE, - IDX_STATS, - IDX_LOG_RESOLVED, - IDX_LOG_FAILED, - IDX_DNS_MAKE_QUERY, - IDX_DNS_PARSE_QUERY, - IDX_LAST, -}; - -static const struct option long_options[] = { - [IDX_HELP] = {"help", no_argument, 0, 0}, - [IDX_THREADS] = {"threads", required_argument, 0, 0}, - [IDX_FAMILY] = {"family", required_argument, 0, 0}, - [IDX_VERBOSE] = {"verbose", no_argument, 0, 0}, - [IDX_STATS] = {"stats", required_argument, 0, 0}, - [IDX_LOG_RESOLVED] = {"log-resolved", required_argument, 0, 0}, - [IDX_LOG_FAILED] = {"log-failed", required_argument, 0, 0}, - [IDX_DNS_MAKE_QUERY] = {"dns-make-query", required_argument, 0, 0}, - [IDX_DNS_PARSE_QUERY] = {"dns-parse-query", no_argument, 0, 0}, - [IDX_LAST] = {NULL, 0, NULL, 0}, -}; - -int main(int argc, char **argv) -{ - int r, v, option_index = 0; - char fn1[256],fn2[256]; - char dom[256]; - - memset(&glob, 0, sizeof(glob)); - *fn1 = *fn2 = *dom = 0; - 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 IDX_HELP: - PRINT_VER; - exithelp(); - break; - case IDX_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 IDX_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 IDX_VERBOSE: - glob.verbose = '\1'; - break; - case IDX_STATS: - glob.stats_every = optarg ? atoi(optarg) : 0; - break; - case IDX_LOG_RESOLVED: - strncpy(fn1,optarg,sizeof(fn1)); - fn1[sizeof(fn1)-1] = 0; - break; - case IDX_LOG_FAILED: - strncpy(fn2,optarg,sizeof(fn2)); - fn2[sizeof(fn2)-1] = 0; - break; - case IDX_DNS_MAKE_QUERY: - strncpy(dom,optarg,sizeof(dom)); - dom[sizeof(dom)-1] = 0; - break; - case IDX_DNS_PARSE_QUERY: - return dns_parse_query(); - } - } - -#ifdef _WIN32 - WSADATA wsaData; - if (WSAStartup(MAKEWORD(2, 2), &wsaData)) - { - fprintf(stderr,"WSAStartup failed\n"); - return 4; - } -#endif - - if (*dom) return dns_make_query(dom, glob.family); - - if (*fn1) - { - glob.F_log_resolved = fopen(fn1,"wt"); - if (!glob.F_log_resolved) - { - fprintf(stderr,"failed to create %s\n",fn1); - r=5; goto ex; - } - } - if (*fn2) - { - glob.F_log_failed = fopen(fn2,"wt"); - if (!glob.F_log_failed) - { - fprintf(stderr,"failed to create %s\n",fn2); - r=5; goto ex; - } - } - - r = run_threads(); - -ex: - if (glob.F_log_resolved) fclose(glob.F_log_resolved); - if (glob.F_log_failed) fclose(glob.F_log_failed); - - return r; -} diff --git a/nfq/BSDmakefile b/nfq/BSDmakefile deleted file mode 100644 index f21d1180..00000000 --- a/nfq/BSDmakefile +++ /dev/null @@ -1,13 +0,0 @@ -CC ?= cc -OPTIMIZE ?= -Os -CFLAGS += -std=gnu99 -s $(OPTIMIZE) -flto=auto -Wno-address-of-packed-member -LIBS = -lz -SRC_FILES = *.c crypto/*.c - -all: dvtws - -dvtws: $(SRC_FILES) - $(CC) $(CFLAGS) -o dvtws $(SRC_FILES) $(LIBS) $(LDFLAGS) - -clean: - rm -f dvtws diff --git a/nfq/Makefile b/nfq/Makefile deleted file mode 100644 index 97cf6bba..00000000 --- a/nfq/Makefile +++ /dev/null @@ -1,46 +0,0 @@ -CC ?= cc -OPTIMIZE ?= -Os -CFLAGS += -std=gnu99 $(OPTIMIZE) -flto=auto -CFLAGS_SYSTEMD = -DUSE_SYSTEMD -CFLAGS_BSD = -Wno-address-of-packed-member -CFLAGS_CYGWIN = -Wno-address-of-packed-member -static -LDFLAGS_ANDROID = -llog -LIBS_LINUX = -lz -lnetfilter_queue -lnfnetlink -lmnl -LIBS_SYSTEMD = -lsystemd -LIBS_BSD = -lz -LIBS_CYGWIN = -lz -Lwindows/windivert -Iwindows -lwlanapi -lole32 -loleaut32 -LIBS_CYGWIN32 = -lwindivert32 -LIBS_CYGWIN64 = -lwindivert64 -RES_CYGWIN32 = windows/res/32/winmanifest.o windows/res/32/winicon.o -RES_CYGWIN64 = windows/res/64/winmanifest.o windows/res/64/winicon.o -SRC_FILES = *.c crypto/*.c - -all: nfqws - -nfqws: $(SRC_FILES) - $(CC) -s $(CFLAGS) -o nfqws $(SRC_FILES) $(LIBS_LINUX) $(LDFLAGS) - -systemd: $(SRC_FILES) - $(CC) -s $(CFLAGS) $(CFLAGS_SYSTEMD) -o nfqws $(SRC_FILES) $(LIBS_LINUX) $(LIBS_SYSTEMD) $(LDFLAGS) - -android: $(SRC_FILES) - $(CC) -s $(CFLAGS) -o nfqws $(SRC_FILES) $(LIBS_LINUX) $(LDFLAGS) $(LDFLAGS_ANDROID) - -bsd: $(SRC_FILES) - $(CC) -s $(CFLAGS) $(CFLAGS_BSD) -o dvtws $(SRC_FILES) $(LIBS_BSD) $(LDFLAGS) - -mac: $(SRC_FILES) - $(CC) $(CFLAGS) $(CFLAGS_BSD) -o dvtwsa $(SRC_FILES) -target arm64-apple-macos10.8 $(LIBS_BSD) $(LDFLAGS) - $(CC) $(CFLAGS) $(CFLAGS_BSD) -o dvtwsx $(SRC_FILES) -target x86_64-apple-macos10.8 $(LIBS_BSD) $(LDFLAGS) - strip dvtwsa dvtwsx - lipo -create -output dvtws dvtwsx dvtwsa - rm -f dvtwsx dvtwsa - -cygwin64: - $(CC) -s $(CFLAGS) $(CFLAGS_CYGWIN) -o winws $(SRC_FILES) $(LIBS_CYGWIN) $(LIBS_CYGWIN64) $(RES_CYGWIN64) $(LDFLAGS) -cygwin32: - $(CC) -s $(CFLAGS) $(CFLAGS_CYGWIN) -o winws $(SRC_FILES) $(LIBS_CYGWIN) $(LIBS_CYGWIN32) $(RES_CYGWIN32) $(LDFLAGS) -cygwin: cygwin64 - -clean: - rm -f nfqws dvtws winws.exe diff --git a/nfq/checksum.c b/nfq/checksum.c deleted file mode 100644 index dcc36579..00000000 --- a/nfq/checksum.c +++ /dev/null @@ -1,159 +0,0 @@ -#define _GNU_SOURCE -#include "checksum.h" -#include - -//#define htonll(x) ((1==htonl(1)) ? (x) : ((uint64_t)htonl((x) & 0xFFFFFFFF) << 32) | htonl((x) >> 32)) -//#define ntohll(x) ((1==ntohl(1)) ? (x) : ((uint64_t)ntohl((x) & 0xFFFFFFFF) << 32) | ntohl((x) >> 32)) - -static uint16_t from64to16(uint64_t x) -{ - uint32_t u = (uint32_t)(uint16_t)x + (uint16_t)(x>>16) + (uint16_t)(x>>32) + (uint16_t)(x>>48); - return (uint16_t)u + (uint16_t)(u>>16); -} - -// this function preserves data alignment requirements (otherwise it will be damn slow on mips arch) -// and uses 64-bit arithmetics to improve speed -// taken from linux source code -static uint16_t do_csum(const uint8_t * buff, size_t len) -{ - uint8_t odd; - size_t count; - uint64_t result,w,carry=0; - uint16_t u16; - - if (!len) return 0; - odd = (uint8_t)(1 & (size_t)buff); - if (odd) - { - // any endian compatible - u16 = 0; - *((uint8_t*)&u16+1) = *buff; - result = u16; - len--; - buff++; - } - else - result = 0; - count = len >> 1; /* nr of 16-bit words.. */ - if (count) - { - if (2 & (size_t) buff) - { - result += *(uint16_t *) buff; - count--; - len -= 2; - buff += 2; - } - count >>= 1; /* nr of 32-bit words.. */ - if (count) - { - if (4 & (size_t) buff) - { - result += *(uint32_t *) buff; - count--; - len -= 4; - buff += 4; - } - count >>= 1; /* nr of 64-bit words.. */ - if (count) - { - do - { - w = *(uint64_t *) buff; - count--; - buff += 8; - result += carry; - result += w; - carry = (w > result); - } while (count); - result += carry; - result = (result & 0xffffffff) + (result >> 32); - } - if (len & 4) - { - result += *(uint32_t *) buff; - buff += 4; - } - } - if (len & 2) - { - result += *(uint16_t *) buff; - buff += 2; - } - } - if (len & 1) - { - // any endian compatible - u16 = 0; - *(uint8_t*)&u16 = *buff; - result += u16; - } - u16 = from64to16(result); - if (odd) u16 = ((u16 >> 8) & 0xff) | ((u16 & 0xff) << 8); - return u16; -} - -uint16_t csum_partial(const void *buff, size_t len) -{ - return do_csum(buff,len); -} - -uint16_t csum_tcpudp_magic(uint32_t saddr, uint32_t daddr, size_t len, uint8_t proto, uint16_t sum) -{ - return ~from64to16((uint64_t)saddr + daddr + sum + htonl(len+proto)); -} - -uint16_t ip4_compute_csum(const void *buff, size_t len) -{ - return ~from64to16(do_csum(buff,len)); -} -void ip4_fix_checksum(struct ip *ip) -{ - ip->ip_sum = 0; - ip->ip_sum = ip4_compute_csum(ip, ip->ip_hl<<2); -} - -uint16_t csum_ipv6_magic(const void *saddr, const void *daddr, size_t len, uint8_t proto, uint16_t sum) -{ - uint64_t a = (uint64_t)sum + htonl(len+proto) + - *(uint32_t*)saddr + *((uint32_t*)saddr+1) + *((uint32_t*)saddr+2) + *((uint32_t*)saddr+3) + - *(uint32_t*)daddr + *((uint32_t*)daddr+1) + *((uint32_t*)daddr+2) + *((uint32_t*)daddr+3); - return ~from64to16(a); -} - - -void tcp4_fix_checksum(struct tcphdr *tcp,size_t len, const struct in_addr *src_addr, const struct in_addr *dest_addr) -{ - tcp->th_sum = 0; - tcp->th_sum = csum_tcpudp_magic(src_addr->s_addr,dest_addr->s_addr,len,IPPROTO_TCP,csum_partial(tcp,len)); -} -void tcp6_fix_checksum(struct tcphdr *tcp,size_t len, const struct in6_addr *src_addr, const struct in6_addr *dest_addr) -{ - tcp->th_sum = 0; - tcp->th_sum = csum_ipv6_magic(src_addr,dest_addr,len,IPPROTO_TCP,csum_partial(tcp,len)); -} -void tcp_fix_checksum(struct tcphdr *tcp,size_t len,const struct ip *ip,const struct ip6_hdr *ip6hdr) -{ - if (ip) - tcp4_fix_checksum(tcp, len, &ip->ip_src, &ip->ip_dst); - else if (ip6hdr) - tcp6_fix_checksum(tcp, len, &ip6hdr->ip6_src, &ip6hdr->ip6_dst); -} - -void udp4_fix_checksum(struct udphdr *udp,size_t len, const struct in_addr *src_addr, const struct in_addr *dest_addr) -{ - udp->uh_sum = 0; - udp->uh_sum = csum_tcpudp_magic(src_addr->s_addr,dest_addr->s_addr,len,IPPROTO_UDP,csum_partial(udp,len)); -} -void udp6_fix_checksum(struct udphdr *udp,size_t len, const struct in6_addr *src_addr, const struct in6_addr *dest_addr) -{ - udp->uh_sum = 0; - udp->uh_sum = csum_ipv6_magic(src_addr,dest_addr,len,IPPROTO_UDP,csum_partial(udp,len)); -} -void udp_fix_checksum(struct udphdr *udp,size_t len,const struct ip *ip,const struct ip6_hdr *ip6hdr) -{ - if (ip) - udp4_fix_checksum(udp, len, &ip->ip_src, &ip->ip_dst); - else if (ip6hdr) - udp6_fix_checksum(udp, len, &ip6hdr->ip6_src, &ip6hdr->ip6_dst); -} diff --git a/nfq/checksum.h b/nfq/checksum.h deleted file mode 100644 index c33831eb..00000000 --- a/nfq/checksum.h +++ /dev/null @@ -1,27 +0,0 @@ -#pragma once - -#include -#include -#include -#include - -#define __FAVOR_BSD -#include -#include -#include -#include - -uint16_t csum_partial(const void *buff, size_t len); -uint16_t csum_tcpudp_magic(uint32_t saddr, uint32_t daddr, size_t len, uint8_t proto, uint16_t sum); -uint16_t csum_ipv6_magic(const void *saddr, const void *daddr, size_t len, uint8_t proto, uint16_t sum); - -uint16_t ip4_compute_csum(const void *buff, size_t len); -void ip4_fix_checksum(struct ip *ip); - -void tcp4_fix_checksum(struct tcphdr *tcp,size_t len, const struct in_addr *src_addr, const struct in_addr *dest_addr); -void tcp6_fix_checksum(struct tcphdr *tcp,size_t len, const struct in6_addr *src_addr, const struct in6_addr *dest_addr); -void tcp_fix_checksum(struct tcphdr *tcp,size_t len,const struct ip *ip,const struct ip6_hdr *ip6hdr); - -void udp4_fix_checksum(struct udphdr *udp,size_t len, const struct in_addr *src_addr, const struct in_addr *dest_addr); -void udp6_fix_checksum(struct udphdr *udp,size_t len, const struct in6_addr *src_addr, const struct in6_addr *dest_addr); -void udp_fix_checksum(struct udphdr *udp,size_t len,const struct ip *ip,const struct ip6_hdr *ip6hdr); diff --git a/nfq/conntrack.c b/nfq/conntrack.c deleted file mode 100644 index 52b6d6e2..00000000 --- a/nfq/conntrack.c +++ /dev/null @@ -1,391 +0,0 @@ -#include "conntrack.h" -#include "darkmagic.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(void *elem) -{ - oom = true; -} - -static const char *connstate_s[]={"SYN","ESTABLISHED","FIN"}; - -static void connswap(const t_conn *c, t_conn *c2) -{ - memset(c2,0,sizeof(*c2)); - c2->l3proto = c->l3proto; - c2->l4proto = c->l4proto; - c2->src = c->dst; - c2->dst = c->src; - c2->sport = c->dport; - c2->dport = c->sport; -} - -void ConntrackClearHostname(t_ctrack *track) -{ - free(track->hostname); - track->hostname = NULL; - track->hostname_is_ip = false; -} -static void ConntrackClearTrack(t_ctrack *track) -{ - ConntrackClearHostname(track); - ReasmClear(&track->reasm_orig); - rawpacket_queue_destroy(&track->delayed); -} - -static void ConntrackFreeElem(t_conntrack_pool *elem) -{ - ConntrackClearTrack(&elem->track); - free(elem); -} - -static void ConntrackPoolDestroyPool(t_conntrack_pool **pp) -{ - t_conntrack_pool *elem, *tmp; - HASH_ITER(hh, *pp, elem, tmp) { HASH_DEL(*pp, elem); ConntrackFreeElem(elem); } -} -void ConntrackPoolDestroy(t_conntrack *p) -{ - ConntrackPoolDestroyPool(&p->pool); -} - -void ConntrackPoolInit(t_conntrack *p, time_t purge_interval, uint32_t timeout_syn, uint32_t timeout_established, uint32_t timeout_fin, uint32_t timeout_udp) -{ - p->timeout_syn = timeout_syn; - p->timeout_established = timeout_established; - p->timeout_fin = timeout_fin; - p->timeout_udp= timeout_udp; - p->t_purge_interval = purge_interval; - time(&p->t_last_purge); - p->pool = NULL; -} - -void ConntrackExtractConn(t_conn *c, bool bReverse, const struct ip *ip, const struct ip6_hdr *ip6, const struct tcphdr *tcphdr, const struct udphdr *udphdr) -{ - memset(c,0,sizeof(*c)); - if (ip) - { - c->l3proto = IPPROTO_IP; - c->dst.ip = bReverse ? ip->ip_src : ip->ip_dst; - c->src.ip = bReverse ? ip->ip_dst : ip->ip_src; - } - else if (ip6) - { - c->l3proto = IPPROTO_IPV6; - c->dst.ip6 = bReverse ? ip6->ip6_src : ip6->ip6_dst; - c->src.ip6 = bReverse ? ip6->ip6_dst : ip6->ip6_src; - } - else - c->l3proto = -1; - extract_ports(tcphdr, udphdr, &c->l4proto, bReverse ? &c->dport : &c->sport, bReverse ? &c->sport : &c->dport); -} - - -static t_conntrack_pool *ConntrackPoolSearch(t_conntrack_pool *p, const t_conn *c) -{ - t_conntrack_pool *t; - HASH_FIND(hh, p, c, sizeof(*c), t); - return t; -} - -static void ConntrackInitTrack(t_ctrack *t) -{ - memset(t,0,sizeof(*t)); - t->scale_orig = t->scale_reply = SCALE_NONE; - time(&t->t_start); - rawpacket_queue_init(&t->delayed); -} -static void ConntrackReInitTrack(t_ctrack *t) -{ - ConntrackClearTrack(t); - ConntrackInitTrack(t); -} - -static t_conntrack_pool *ConntrackNew(t_conntrack_pool **pp, const t_conn *c) -{ - t_conntrack_pool *ctnew; - if (!(ctnew = malloc(sizeof(*ctnew)))) return NULL; - ctnew->conn = *c; - oom = false; - HASH_ADD(hh, *pp, conn, sizeof(*c), ctnew); - if (oom) { free(ctnew); return NULL; } - ConntrackInitTrack(&ctnew->track); - return ctnew; -} - -// non-tcp packets are passed with tcphdr=NULL but len_payload filled -static void ConntrackFeedPacket(t_ctrack *t, bool bReverse, const struct tcphdr *tcphdr, uint32_t len_payload) -{ - uint8_t scale; - - if (bReverse) - { - t->pcounter_reply++; - t->pdcounter_reply+=!!len_payload; - - } - else - { - t->pcounter_orig++; - t->pdcounter_orig+=!!len_payload; - } - - if (tcphdr) - { - if (tcp_syn_segment(tcphdr)) - { - if (t->state!=SYN) ConntrackReInitTrack(t); // erase current entry - t->seq0 = ntohl(tcphdr->th_seq); - } - else if (tcp_synack_segment(tcphdr)) - { - // ignore SA dups - uint32_t seq0 = ntohl(tcphdr->th_ack)-1; - if (t->state!=SYN && t->seq0!=seq0) - ConntrackReInitTrack(t); // erase current entry - if (!t->seq0) t->seq0 = seq0; - t->ack0 = ntohl(tcphdr->th_seq); - } - else if (tcphdr->th_flags & (TH_FIN|TH_RST)) - { - t->state = FIN; - } - else - { - if (t->state==SYN) - { - t->state=ESTABLISHED; - if (!bReverse && !t->ack0) t->ack0 = ntohl(tcphdr->th_ack)-1; - } - } - scale = tcp_find_scale_factor(tcphdr); - if (bReverse) - { - t->pos_orig = t->seq_last = ntohl(tcphdr->th_ack); - t->ack_last = ntohl(tcphdr->th_seq); - t->pos_reply = t->ack_last + len_payload; - t->winsize_reply = ntohs(tcphdr->th_win); - if (scale!=SCALE_NONE) t->scale_reply = scale; - - } - else - { - t->seq_last = ntohl(tcphdr->th_seq); - t->pos_orig = t->seq_last + len_payload; - t->pos_reply = t->ack_last = ntohl(tcphdr->th_ack); - t->winsize_orig = ntohs(tcphdr->th_win); - if (scale!=SCALE_NONE) t->scale_orig = scale; - } - } - else - { - if (bReverse) - { - t->ack_last=t->pos_reply; - t->pos_reply+=len_payload; - } - else - { - t->seq_last=t->pos_orig; - t->pos_orig+=len_payload; - } - } - - time(&t->t_last); -} - -static bool ConntrackPoolDoubleSearchPool(t_conntrack_pool **pp, const struct ip *ip, const struct ip6_hdr *ip6, const struct tcphdr *tcphdr, const struct udphdr *udphdr, t_ctrack **ctrack, bool *bReverse) -{ - t_conn conn,connswp; - t_conntrack_pool *ctr; - - ConntrackExtractConn(&conn,false,ip,ip6,tcphdr,udphdr); - if ((ctr=ConntrackPoolSearch(*pp,&conn))) - { - if (bReverse) *bReverse = false; - if (ctrack) *ctrack = &ctr->track; - return true; - } - else - { - connswap(&conn,&connswp); - if ((ctr=ConntrackPoolSearch(*pp,&connswp))) - { - if (bReverse) *bReverse = true; - if (ctrack) *ctrack = &ctr->track; - return true; - } - } - return false; -} -bool ConntrackPoolDoubleSearch(t_conntrack *p, const struct ip *ip, const struct ip6_hdr *ip6, const struct tcphdr *tcphdr, const struct udphdr *udphdr, t_ctrack **ctrack, bool *bReverse) -{ - return ConntrackPoolDoubleSearchPool(&p->pool, ip, ip6, tcphdr, udphdr, ctrack, bReverse); -} - -static bool ConntrackPoolFeedPool(t_conntrack_pool **pp, const struct ip *ip, const struct ip6_hdr *ip6, const struct tcphdr *tcphdr, const struct udphdr *udphdr, size_t len_payload, t_ctrack **ctrack, bool *bReverse) -{ - t_conn conn, connswp; - t_conntrack_pool *ctr; - bool b_rev; - - ConntrackExtractConn(&conn,false,ip,ip6,tcphdr,udphdr); - if ((ctr=ConntrackPoolSearch(*pp,&conn))) - { - ConntrackFeedPacket(&ctr->track, (b_rev=false), tcphdr, len_payload); - goto ok; - } - else - { - connswap(&conn,&connswp); - if ((ctr=ConntrackPoolSearch(*pp,&connswp))) - { - ConntrackFeedPacket(&ctr->track, (b_rev=true), tcphdr, len_payload); - goto ok; - } - } - b_rev = tcphdr && tcp_synack_segment(tcphdr); - if ((tcphdr && tcp_syn_segment(tcphdr)) || b_rev || udphdr) - { - if ((ctr=ConntrackNew(pp, b_rev ? &connswp : &conn))) - { - ConntrackFeedPacket(&ctr->track, b_rev, tcphdr, len_payload); - goto ok; - } - } - return false; -ok: - if (ctrack) *ctrack = &ctr->track; - if (bReverse) *bReverse = b_rev; - return true; -} -bool ConntrackPoolFeed(t_conntrack *p, const struct ip *ip, const struct ip6_hdr *ip6, const struct tcphdr *tcphdr, const struct udphdr *udphdr, size_t len_payload, t_ctrack **ctrack, bool *bReverse) -{ - return ConntrackPoolFeedPool(&p->pool,ip,ip6,tcphdr,udphdr,len_payload,ctrack,bReverse); -} - -static bool ConntrackPoolDropPool(t_conntrack_pool **pp, const struct ip *ip, const struct ip6_hdr *ip6, const struct tcphdr *tcphdr, const struct udphdr *udphdr) -{ - t_conn conn, connswp; - t_conntrack_pool *t; - ConntrackExtractConn(&conn,false,ip,ip6,tcphdr,udphdr); - if (!(t=ConntrackPoolSearch(*pp,&conn))) - { - connswap(&conn,&connswp); - t=ConntrackPoolSearch(*pp,&connswp); - } - if (!t) return false; - HASH_DEL(*pp, t); ConntrackFreeElem(t); - return true; -} -bool ConntrackPoolDrop(t_conntrack *p, const struct ip *ip, const struct ip6_hdr *ip6, const struct tcphdr *tcphdr, const struct udphdr *udphdr) -{ - return ConntrackPoolDropPool(&p->pool,ip,ip6,tcphdr,udphdr); -} - -void ConntrackPoolPurge(t_conntrack *p) -{ - time_t tidle, tnow = time(NULL); - t_conntrack_pool *t, *tmp; - - if ((tnow - p->t_last_purge)>=p->t_purge_interval) - { - HASH_ITER(hh, p->pool , t, tmp) { - tidle = tnow - t->track.t_last; - if ( t->track.b_cutoff || - (t->conn.l4proto==IPPROTO_TCP && ( - (t->track.state==SYN && tidle>=p->timeout_syn) || - (t->track.state==ESTABLISHED && tidle>=p->timeout_established) || - (t->track.state==FIN && tidle>=p->timeout_fin)) - ) || (t->conn.l4proto==IPPROTO_UDP && tidle>=p->timeout_udp) - ) - { - HASH_DEL(p->pool, t); ConntrackFreeElem(t); - } - } - p->t_last_purge = tnow; - } -} - -static void taddr2str(uint8_t l3proto, const t_addr *a, char *buf, size_t bufsize) -{ - if (!inet_ntop(family_from_proto(l3proto), a, buf, bufsize) && bufsize) *buf=0; -} - -void ConntrackPoolDump(const t_conntrack *p) -{ - t_conntrack_pool *t, *tmp; - char sa1[40],sa2[40]; - time_t tnow = time(NULL); - HASH_ITER(hh, p->pool, t, tmp) { - taddr2str(t->conn.l3proto, &t->conn.src, sa1, sizeof(sa1)); - taddr2str(t->conn.l3proto, &t->conn.dst, sa2, sizeof(sa2)); - printf("%s [%s]:%u => [%s]:%u : %s : t0=%llu last=t0+%llu now=last+%llu packets_orig=d%llu/n%llu packets_reply=d%llu/n%llu ", - proto_name(t->conn.l4proto), - sa1, t->conn.sport, sa2, t->conn.dport, - t->conn.l4proto==IPPROTO_TCP ? connstate_s[t->track.state] : "-", - (unsigned long long)t->track.t_start, (unsigned long long)(t->track.t_last - t->track.t_start), (unsigned long long)(tnow - t->track.t_last), - (unsigned long long)t->track.pdcounter_orig, (unsigned long long)t->track.pcounter_orig, - (unsigned long long)t->track.pdcounter_reply, (unsigned long long)t->track.pcounter_reply); - if (t->conn.l4proto==IPPROTO_TCP) - printf("seq0=%u rseq=%u pos_orig=%u ack0=%u rack=%u pos_reply=%u wsize_orig=%u:%d wsize_reply=%u:%d", - t->track.seq0, t->track.seq_last - t->track.seq0, t->track.pos_orig - t->track.seq0, - t->track.ack0, t->track.ack_last - t->track.ack0, t->track.pos_reply - t->track.ack0, - t->track.winsize_orig, t->track.scale_orig==SCALE_NONE ? -1 : t->track.scale_orig, - t->track.winsize_reply, t->track.scale_reply==SCALE_NONE ? -1 : t->track.scale_reply); - else - printf("rseq=%u pos_orig=%u rack=%u pos_reply=%u", - t->track.seq_last, t->track.pos_orig, - t->track.ack_last, t->track.pos_reply); - printf(" req_retrans=%u cutoff=%u wss_cutoff=%u desync_cutoff=%u dup_cutoff=%u orig_cutoff=%u hostname=%s l7proto=%s\n", - t->track.req_retrans_counter, t->track.b_cutoff, t->track.b_wssize_cutoff, t->track.b_desync_cutoff, t->track.b_dup_cutoff, t->track.b_orig_mod_cutoff, t->track.hostname, l7proto_str(t->track.l7proto)); - }; -} - - -void ReasmClear(t_reassemble *reasm) -{ - free(reasm->packet); - reasm->packet = NULL; - reasm->size = reasm->size_present = 0; -} -bool ReasmInit(t_reassemble *reasm, size_t size_requested, uint32_t seq_start) -{ - reasm->packet = malloc(size_requested); - if (!reasm->packet) return false; - reasm->size = size_requested; - reasm->size_present = 0; - reasm->seq = seq_start; - return true; -} -bool ReasmResize(t_reassemble *reasm, size_t new_size) -{ - uint8_t *p = realloc(reasm->packet, new_size); - if (!p) return false; - reasm->packet = p; - reasm->size = new_size; - if (reasm->size_present > new_size) reasm->size_present = new_size; - return true; -} -bool ReasmFeed(t_reassemble *reasm, uint32_t seq, const void *payload, size_t len) -{ - if (reasm->seq!=seq) return false; // fail session if out of sequence - - size_t szcopy; - szcopy = reasm->size - reasm->size_present; - if (lenpacket + reasm->size_present, payload, szcopy); - reasm->size_present += szcopy; - reasm->seq += (uint32_t)szcopy; - - return true; -} -bool ReasmHasSpace(t_reassemble *reasm, size_t len) -{ - return (reasm->size_present+len)<=reasm->size; -} diff --git a/nfq/conntrack.h b/nfq/conntrack.h deleted file mode 100644 index f82a3fb5..00000000 --- a/nfq/conntrack.h +++ /dev/null @@ -1,132 +0,0 @@ -#pragma once - - -// this conntrack is not bullet-proof -// its designed to satisfy dpi desync needs only - -#include -#include -#include -#include -#include -#include - -#define __FAVOR_BSD -#include -#include -#include -#include - -#include "packet_queue.h" -#include "protocol.h" - -//#define HASH_BLOOM 20 -#define HASH_NONFATAL_OOM 1 -#undef HASH_FUNCTION -#define HASH_FUNCTION HASH_BER -#include "uthash.h" - -#define RETRANS_COUNTER_STOP ((uint8_t)-1) - -typedef union { - struct in_addr ip; - struct in6_addr ip6; -} t_addr; -typedef struct -{ - t_addr src, dst; - uint16_t sport,dport; - uint8_t l3proto; // IPPROTO_IP, IPPROTO_IPV6 - uint8_t l4proto; // IPPROTO_TCP, IPPROTO_UDP -} t_conn; - -// this structure helps to reassemble continuous packets streams. it does not support out-of-orders -typedef struct { - uint8_t *packet; // allocated for size during reassemble request. requestor must know the message size. - uint32_t seq; // current seq number. if a packet comes with an unexpected seq - it fails reassemble session. - size_t size; // expected message size. success means that we have received exactly 'size' bytes and have them in 'packet' - size_t size_present; // how many bytes already stored in 'packet' -} t_reassemble; - -// SYN - SYN or SYN/ACK received -// ESTABLISHED - any except SYN or SYN/ACK received -// FIN - FIN or RST received -typedef enum {SYN=0, ESTABLISHED, FIN} t_connstate; - -typedef struct -{ - bool bCheckDone, bCheckResult, bCheckExcluded; // hostlist check result cache - - struct desync_profile *dp; // desync profile cache - bool dp_search_complete; - - // common state - time_t t_start, t_last; - uint64_t pcounter_orig, pcounter_reply; // packet counter - uint64_t pdcounter_orig, pdcounter_reply; // data packet counter (with payload) - uint32_t pos_orig, pos_reply; // TCP: seq_last+payload, ack_last+payload UDP: sum of all seen payload lenghts including current - uint32_t seq_last, ack_last; // TCP: last seen seq and ack UDP: sum of all seen payload lenghts NOT including current - - // tcp only state, not used in udp - t_connstate state; - uint32_t seq0, ack0; // starting seq and ack - uint16_t winsize_orig, winsize_reply; // last seen window size - uint8_t scale_orig, scale_reply; // last seen window scale factor. SCALE_NONE if none - - uint8_t req_retrans_counter; // number of request retransmissions - bool req_seq_present,req_seq_finalized,req_seq_abandoned; - uint32_t req_seq_start,req_seq_end; // sequence interval of the request (to track retransmissions) - - uint8_t incoming_ttl, desync_autottl, orig_autottl, dup_autottl; - bool b_autottl_discovered; - - bool b_cutoff; // mark for deletion - bool b_wssize_cutoff, b_desync_cutoff, b_dup_cutoff, b_orig_mod_cutoff; - - uint16_t ip_id; - - t_l7proto l7proto; - bool l7proto_discovered; - char *hostname; - bool hostname_is_ip; - bool hostname_discovered; - bool hostname_ah_check; // should perform autohostlist checks - - t_reassemble reasm_orig; - struct rawpacket_tailhead delayed; -} t_ctrack; - -typedef struct -{ - t_ctrack track; - UT_hash_handle hh; // makes this structure hashable - t_conn conn; // key -} t_conntrack_pool; -typedef struct -{ - // inactivity time to purge an entry in each connection state - uint32_t timeout_syn,timeout_established,timeout_fin,timeout_udp; - time_t t_purge_interval, t_last_purge; - t_conntrack_pool *pool; -} t_conntrack; - -void ConntrackPoolInit(t_conntrack *p, time_t purge_interval, uint32_t timeout_syn, uint32_t timeout_established, uint32_t timeout_fin, uint32_t timeout_udp); -void ConntrackPoolDestroy(t_conntrack *p); -bool ConntrackPoolFeed(t_conntrack *p, const struct ip *ip, const struct ip6_hdr *ip6, const struct tcphdr *tcphdr, const struct udphdr *udphdr, size_t len_payload, t_ctrack **ctrack, bool *bReverse); -// do not create, do not update. only find existing -bool ConntrackPoolDoubleSearch(t_conntrack *p, const struct ip *ip, const struct ip6_hdr *ip6, const struct tcphdr *tcphdr, const struct udphdr *udphdr, t_ctrack **ctrack, bool *bReverse); -bool ConntrackPoolDrop(t_conntrack *p, const struct ip *ip, const struct ip6_hdr *ip6, const struct tcphdr *tcphdr, const struct udphdr *udphdr); -void CaonntrackExtractConn(t_conn *c, bool bReverse, const struct ip *ip, const struct ip6_hdr *ip6, const struct tcphdr *tcphdr, const struct udphdr *udphdr); -void ConntrackPoolDump(const t_conntrack *p); -void ConntrackPoolPurge(t_conntrack *p); -void ConntrackClearHostname(t_ctrack *track); - -bool ReasmInit(t_reassemble *reasm, size_t size_requested, uint32_t seq_start); -bool ReasmResize(t_reassemble *reasm, size_t new_size); -void ReasmClear(t_reassemble *reasm); -// false means reassemble session has failed and we should ReasmClear() it -bool ReasmFeed(t_reassemble *reasm, uint32_t seq, const void *payload, size_t len); -// check if it has enough space to buffer 'len' bytes -bool ReasmHasSpace(t_reassemble *reasm, size_t len); -inline static bool ReasmIsEmpty(t_reassemble *reasm) {return !reasm->size;} -inline static bool ReasmIsFull(t_reassemble *reasm) {return !ReasmIsEmpty(reasm) && (reasm->size==reasm->size_present);} diff --git a/nfq/crypto/aes-gcm.c b/nfq/crypto/aes-gcm.c deleted file mode 100644 index 130cd609..00000000 --- a/nfq/crypto/aes-gcm.c +++ /dev/null @@ -1,15 +0,0 @@ -#include "aes-gcm.h" - -int aes_gcm_crypt(int mode, uint8_t *output, const uint8_t *input, size_t input_length, const uint8_t *key, const size_t key_len, const uint8_t *iv, const size_t iv_len, const uint8_t *adata, size_t adata_len, uint8_t *atag, size_t atag_len) -{ - int ret = 0; - gcm_context ctx; - - if (!(ret = gcm_setkey(&ctx, key, (const uint)key_len))) - { - ret = gcm_crypt_and_tag(&ctx, mode, iv, iv_len, adata, adata_len, input, output, input_length, atag, atag_len); - gcm_zero_ctx(&ctx); - } - - return ret; -} diff --git a/nfq/crypto/aes-gcm.h b/nfq/crypto/aes-gcm.h deleted file mode 100644 index d8360013..00000000 --- a/nfq/crypto/aes-gcm.h +++ /dev/null @@ -1,6 +0,0 @@ -#pragma once - -#include "gcm.h" - -// mode : AES_ENCRYPT, AES_DECRYPT -int aes_gcm_crypt(int mode, uint8_t *output, const uint8_t *input, size_t input_length, const uint8_t *key, const size_t key_len, const uint8_t *iv, const size_t iv_len, const uint8_t *adata, size_t adata_len, uint8_t *atag, size_t atag_len); diff --git a/nfq/crypto/aes.c b/nfq/crypto/aes.c deleted file mode 100644 index 49cd313e..00000000 --- a/nfq/crypto/aes.c +++ /dev/null @@ -1,483 +0,0 @@ -/****************************************************************************** -* -* THIS SOURCE CODE IS HEREBY PLACED INTO THE PUBLIC DOMAIN FOR THE GOOD OF ALL -* -* This is a simple and straightforward implementation of the AES Rijndael -* 128-bit block cipher designed by Vincent Rijmen and Joan Daemen. The focus -* of this work was correctness & accuracy. It is written in 'C' without any -* particular focus upon optimization or speed. It should be endian (memory -* byte order) neutral since the few places that care are handled explicitly. -* -* This implementation of Rijndael was created by Steven M. Gibson of GRC.com. -* -* It is intended for general purpose use, but was written in support of GRC's -* reference implementation of the SQRL (Secure Quick Reliable Login) client. -* -* See: http://csrc.nist.gov/archive/aes/rijndael/wsdindex.html -* -* NO COPYRIGHT IS CLAIMED IN THIS WORK, HOWEVER, NEITHER IS ANY WARRANTY MADE -* REGARDING ITS FITNESS FOR ANY PARTICULAR PURPOSE. USE IT AT YOUR OWN RISK. -* -*******************************************************************************/ - -#include "aes.h" - -static int aes_tables_inited = 0; // run-once flag for performing key - // expasion table generation (see below) -/* - * The following static local tables must be filled-in before the first use of - * the GCM or AES ciphers. They are used for the AES key expansion/scheduling - * and once built are read-only and thread safe. The "gcm_initialize" function - * must be called once during system initialization to populate these arrays - * for subsequent use by the AES key scheduler. If they have not been built - * before attempted use, an error will be returned to the caller. - * - * NOTE: GCM Encryption/Decryption does NOT REQUIRE AES decryption. Since - * GCM uses AES in counter-mode, where the AES cipher output is XORed with - * the GCM input, we ONLY NEED AES encryption. Thus, to save space AES - * decryption is typically disabled by setting AES_DECRYPTION to 0 in aes.h. - */ - // We always need our forward tables -static uchar FSb[256]; // Forward substitution box (FSb) -static uint32_t FT0[256]; // Forward key schedule assembly tables -static uint32_t FT1[256]; -static uint32_t FT2[256]; -static uint32_t FT3[256]; - -#if AES_DECRYPTION // We ONLY need reverse for decryption -static uchar RSb[256]; // Reverse substitution box (RSb) -static uint32_t RT0[256]; // Reverse key schedule assembly tables -static uint32_t RT1[256]; -static uint32_t RT2[256]; -static uint32_t RT3[256]; -#endif /* AES_DECRYPTION */ - -static uint32_t RCON[10]; // AES round constants - -/* - * Platform Endianness Neutralizing Load and Store Macro definitions - * AES wants platform-neutral Little Endian (LE) byte ordering - */ -#define GET_UINT32_LE(n,b,i) { \ - (n) = ( (uint32_t) (b)[(i) ] ) \ - | ( (uint32_t) (b)[(i) + 1] << 8 ) \ - | ( (uint32_t) (b)[(i) + 2] << 16 ) \ - | ( (uint32_t) (b)[(i) + 3] << 24 ); } - -#define PUT_UINT32_LE(n,b,i) { \ - (b)[(i) ] = (uchar) ( (n) ); \ - (b)[(i) + 1] = (uchar) ( (n) >> 8 ); \ - (b)[(i) + 2] = (uchar) ( (n) >> 16 ); \ - (b)[(i) + 3] = (uchar) ( (n) >> 24 ); } - - /* - * AES forward and reverse encryption round processing macros - */ -#define AES_FROUND(X0,X1,X2,X3,Y0,Y1,Y2,Y3) \ -{ \ - X0 = *RK++ ^ FT0[ ( Y0 ) & 0xFF ] ^ \ - FT1[ ( Y1 >> 8 ) & 0xFF ] ^ \ - FT2[ ( Y2 >> 16 ) & 0xFF ] ^ \ - FT3[ ( Y3 >> 24 ) & 0xFF ]; \ - \ - X1 = *RK++ ^ FT0[ ( Y1 ) & 0xFF ] ^ \ - FT1[ ( Y2 >> 8 ) & 0xFF ] ^ \ - FT2[ ( Y3 >> 16 ) & 0xFF ] ^ \ - FT3[ ( Y0 >> 24 ) & 0xFF ]; \ - \ - X2 = *RK++ ^ FT0[ ( Y2 ) & 0xFF ] ^ \ - FT1[ ( Y3 >> 8 ) & 0xFF ] ^ \ - FT2[ ( Y0 >> 16 ) & 0xFF ] ^ \ - FT3[ ( Y1 >> 24 ) & 0xFF ]; \ - \ - X3 = *RK++ ^ FT0[ ( Y3 ) & 0xFF ] ^ \ - FT1[ ( Y0 >> 8 ) & 0xFF ] ^ \ - FT2[ ( Y1 >> 16 ) & 0xFF ] ^ \ - FT3[ ( Y2 >> 24 ) & 0xFF ]; \ -} - -#define AES_RROUND(X0,X1,X2,X3,Y0,Y1,Y2,Y3) \ -{ \ - X0 = *RK++ ^ RT0[ ( Y0 ) & 0xFF ] ^ \ - RT1[ ( Y3 >> 8 ) & 0xFF ] ^ \ - RT2[ ( Y2 >> 16 ) & 0xFF ] ^ \ - RT3[ ( Y1 >> 24 ) & 0xFF ]; \ - \ - X1 = *RK++ ^ RT0[ ( Y1 ) & 0xFF ] ^ \ - RT1[ ( Y0 >> 8 ) & 0xFF ] ^ \ - RT2[ ( Y3 >> 16 ) & 0xFF ] ^ \ - RT3[ ( Y2 >> 24 ) & 0xFF ]; \ - \ - X2 = *RK++ ^ RT0[ ( Y2 ) & 0xFF ] ^ \ - RT1[ ( Y1 >> 8 ) & 0xFF ] ^ \ - RT2[ ( Y0 >> 16 ) & 0xFF ] ^ \ - RT3[ ( Y3 >> 24 ) & 0xFF ]; \ - \ - X3 = *RK++ ^ RT0[ ( Y3 ) & 0xFF ] ^ \ - RT1[ ( Y2 >> 8 ) & 0xFF ] ^ \ - RT2[ ( Y1 >> 16 ) & 0xFF ] ^ \ - RT3[ ( Y0 >> 24 ) & 0xFF ]; \ -} - - /* - * These macros improve the readability of the key - * generation initialization code by collapsing - * repetitive common operations into logical pieces. - */ -#define ROTL8(x) ( ( x << 8 ) & 0xFFFFFFFF ) | ( x >> 24 ) -#define XTIME(x) ( ( x << 1 ) ^ ( ( x & 0x80 ) ? 0x1B : 0x00 ) ) -#define MUL(x,y) ( ( x && y ) ? pow[(log[x]+log[y]) % 255] : 0 ) -#define MIX(x,y) { y = ( (y << 1) | (y >> 7) ) & 0xFF; x ^= y; } -#define CPY128 { *RK++ = *SK++; *RK++ = *SK++; \ - *RK++ = *SK++; *RK++ = *SK++; } - - /****************************************************************************** - * - * AES_INIT_KEYGEN_TABLES - * - * Fills the AES key expansion tables allocated above with their static - * data. This is not "per key" data, but static system-wide read-only - * table data. THIS FUNCTION IS NOT THREAD SAFE. It must be called once - * at system initialization to setup the tables for all subsequent use. - * - ******************************************************************************/ -void aes_init_keygen_tables(void) -{ - int i, x, y, z; // general purpose iteration and computation locals - int pow[256]; - int log[256]; - - if (aes_tables_inited) return; - - // fill the 'pow' and 'log' tables over GF(2^8) - for (i = 0, x = 1; i < 256; i++) { - pow[i] = x; - log[x] = i; - x = (x ^ XTIME(x)) & 0xFF; - } - // compute the round constants - for (i = 0, x = 1; i < 10; i++) { - RCON[i] = (uint32_t)x; - x = XTIME(x) & 0xFF; - } - // fill the forward and reverse substitution boxes - FSb[0x00] = 0x63; -#if AES_DECRYPTION // whether AES decryption is supported - RSb[0x63] = 0x00; -#endif /* AES_DECRYPTION */ - - for (i = 1; i < 256; i++) { - x = y = pow[255 - log[i]]; - MIX(x, y); - MIX(x, y); - MIX(x, y); - MIX(x, y); - FSb[i] = (uchar)(x ^= 0x63); -#if AES_DECRYPTION // whether AES decryption is supported - RSb[x] = (uchar)i; -#endif /* AES_DECRYPTION */ - - } - // generate the forward and reverse key expansion tables - for (i = 0; i < 256; i++) { - x = FSb[i]; - y = XTIME(x) & 0xFF; - z = (y ^ x) & 0xFF; - - FT0[i] = ((uint32_t)y) ^ ((uint32_t)x << 8) ^ - ((uint32_t)x << 16) ^ ((uint32_t)z << 24); - - FT1[i] = ROTL8(FT0[i]); - FT2[i] = ROTL8(FT1[i]); - FT3[i] = ROTL8(FT2[i]); - -#if AES_DECRYPTION // whether AES decryption is supported - x = RSb[i]; - - RT0[i] = ((uint32_t)MUL(0x0E, x)) ^ - ((uint32_t)MUL(0x09, x) << 8) ^ - ((uint32_t)MUL(0x0D, x) << 16) ^ - ((uint32_t)MUL(0x0B, x) << 24); - - RT1[i] = ROTL8(RT0[i]); - RT2[i] = ROTL8(RT1[i]); - RT3[i] = ROTL8(RT2[i]); -#endif /* AES_DECRYPTION */ - } - aes_tables_inited = 1; // flag that the tables have been generated -} // to permit subsequent use of the AES cipher - -/****************************************************************************** - * - * AES_SET_ENCRYPTION_KEY - * - * This is called by 'aes_setkey' when we're establishing a key for - * subsequent encryption. We give it a pointer to the encryption - * context, a pointer to the key, and the key's length in bytes. - * Valid lengths are: 16, 24 or 32 bytes (128, 192, 256 bits). - * - ******************************************************************************/ -int aes_set_encryption_key(aes_context *ctx, - const uchar *key, - uint keysize) -{ - uint i; // general purpose iteration local - uint32_t *RK = ctx->rk; // initialize our RoundKey buffer pointer - - for (i = 0; i < (keysize >> 2); i++) { - GET_UINT32_LE(RK[i], key, i << 2); - } - - switch (ctx->rounds) - { - case 10: - for (i = 0; i < 10; i++, RK += 4) { - RK[4] = RK[0] ^ RCON[i] ^ - ((uint32_t)FSb[(RK[3] >> 8) & 0xFF]) ^ - ((uint32_t)FSb[(RK[3] >> 16) & 0xFF] << 8) ^ - ((uint32_t)FSb[(RK[3] >> 24) & 0xFF] << 16) ^ - ((uint32_t)FSb[(RK[3]) & 0xFF] << 24); - - RK[5] = RK[1] ^ RK[4]; - RK[6] = RK[2] ^ RK[5]; - RK[7] = RK[3] ^ RK[6]; - } - break; - - case 12: - for (i = 0; i < 8; i++, RK += 6) { - RK[6] = RK[0] ^ RCON[i] ^ - ((uint32_t)FSb[(RK[5] >> 8) & 0xFF]) ^ - ((uint32_t)FSb[(RK[5] >> 16) & 0xFF] << 8) ^ - ((uint32_t)FSb[(RK[5] >> 24) & 0xFF] << 16) ^ - ((uint32_t)FSb[(RK[5]) & 0xFF] << 24); - - RK[7] = RK[1] ^ RK[6]; - RK[8] = RK[2] ^ RK[7]; - RK[9] = RK[3] ^ RK[8]; - RK[10] = RK[4] ^ RK[9]; - RK[11] = RK[5] ^ RK[10]; - } - break; - - case 14: - for (i = 0; i < 7; i++, RK += 8) { - RK[8] = RK[0] ^ RCON[i] ^ - ((uint32_t)FSb[(RK[7] >> 8) & 0xFF]) ^ - ((uint32_t)FSb[(RK[7] >> 16) & 0xFF] << 8) ^ - ((uint32_t)FSb[(RK[7] >> 24) & 0xFF] << 16) ^ - ((uint32_t)FSb[(RK[7]) & 0xFF] << 24); - - RK[9] = RK[1] ^ RK[8]; - RK[10] = RK[2] ^ RK[9]; - RK[11] = RK[3] ^ RK[10]; - - RK[12] = RK[4] ^ - ((uint32_t)FSb[(RK[11]) & 0xFF]) ^ - ((uint32_t)FSb[(RK[11] >> 8) & 0xFF] << 8) ^ - ((uint32_t)FSb[(RK[11] >> 16) & 0xFF] << 16) ^ - ((uint32_t)FSb[(RK[11] >> 24) & 0xFF] << 24); - - RK[13] = RK[5] ^ RK[12]; - RK[14] = RK[6] ^ RK[13]; - RK[15] = RK[7] ^ RK[14]; - } - break; - - default: - return -1; - } - return(0); -} - -#if AES_DECRYPTION // whether AES decryption is supported - -/****************************************************************************** - * - * AES_SET_DECRYPTION_KEY - * - * This is called by 'aes_setkey' when we're establishing a - * key for subsequent decryption. We give it a pointer to - * the encryption context, a pointer to the key, and the key's - * length in bits. Valid lengths are: 128, 192, or 256 bits. - * - ******************************************************************************/ -int aes_set_decryption_key(aes_context *ctx, - const uchar *key, - uint keysize) -{ - int i, j; - aes_context cty; // a calling aes context for set_encryption_key - uint32_t *RK = ctx->rk; // initialize our RoundKey buffer pointer - uint32_t *SK; - int ret; - - cty.rounds = ctx->rounds; // initialize our local aes context - cty.rk = cty.buf; // round count and key buf pointer - - if ((ret = aes_set_encryption_key(&cty, key, keysize)) != 0) - return(ret); - - SK = cty.rk + cty.rounds * 4; - - CPY128 // copy a 128-bit block from *SK to *RK - - for (i = ctx->rounds - 1, SK -= 8; i > 0; i--, SK -= 8) { - for (j = 0; j < 4; j++, SK++) { - *RK++ = RT0[FSb[(*SK) & 0xFF]] ^ - RT1[FSb[(*SK >> 8) & 0xFF]] ^ - RT2[FSb[(*SK >> 16) & 0xFF]] ^ - RT3[FSb[(*SK >> 24) & 0xFF]]; - } - } - CPY128 // copy a 128-bit block from *SK to *RK - memset(&cty, 0, sizeof(aes_context)); // clear local aes context - return(0); -} - -#endif /* AES_DECRYPTION */ - -/****************************************************************************** - * - * AES_SETKEY - * - * Invoked to establish the key schedule for subsequent encryption/decryption - * - ******************************************************************************/ -int aes_setkey(aes_context *ctx, // AES context provided by our caller - int mode, // ENCRYPT or DECRYPT flag - const uchar *key, // pointer to the key - uint keysize) // key length in bytes -{ - // since table initialization is not thread safe, we could either add - // system-specific mutexes and init the AES key generation tables on - // demand, or ask the developer to simply call "gcm_initialize" once during - // application startup before threading begins. That's what we choose. - if (!aes_tables_inited) return (-1); // fail the call when not inited. - - ctx->mode = mode; // capture the key type we're creating - ctx->rk = ctx->buf; // initialize our round key pointer - - switch (keysize) // set the rounds count based upon the keysize - { - case 16: ctx->rounds = 10; break; // 16-byte, 128-bit key - case 24: ctx->rounds = 12; break; // 24-byte, 192-bit key - case 32: ctx->rounds = 14; break; // 32-byte, 256-bit key - default: return(-1); - } - -#if AES_DECRYPTION - if (mode == AES_DECRYPT) // expand our key for encryption or decryption - return(aes_set_decryption_key(ctx, key, keysize)); - else /* ENCRYPT */ -#endif /* AES_DECRYPTION */ - return(aes_set_encryption_key(ctx, key, keysize)); -} - -/****************************************************************************** - * - * AES_CIPHER - * - * Perform AES encryption and decryption. - * The AES context will have been setup with the encryption mode - * and all keying information appropriate for the task. - * - ******************************************************************************/ -int aes_cipher(aes_context *ctx, - const uchar input[16], - uchar output[16]) -{ - int i; - uint32_t *RK, X0, X1, X2, X3, Y0, Y1, Y2, Y3; // general purpose locals - - RK = ctx->rk; - - GET_UINT32_LE(X0, input, 0); X0 ^= *RK++; // load our 128-bit - GET_UINT32_LE(X1, input, 4); X1 ^= *RK++; // input buffer in a storage - GET_UINT32_LE(X2, input, 8); X2 ^= *RK++; // memory endian-neutral way - GET_UINT32_LE(X3, input, 12); X3 ^= *RK++; - -#if AES_DECRYPTION // whether AES decryption is supported - - if (ctx->mode == AES_DECRYPT) - { - for (i = (ctx->rounds >> 1) - 1; i > 0; i--) - { - AES_RROUND(Y0, Y1, Y2, Y3, X0, X1, X2, X3); - AES_RROUND(X0, X1, X2, X3, Y0, Y1, Y2, Y3); - } - - AES_RROUND(Y0, Y1, Y2, Y3, X0, X1, X2, X3); - - X0 = *RK++ ^ \ - ((uint32_t)RSb[(Y0) & 0xFF]) ^ - ((uint32_t)RSb[(Y3 >> 8) & 0xFF] << 8) ^ - ((uint32_t)RSb[(Y2 >> 16) & 0xFF] << 16) ^ - ((uint32_t)RSb[(Y1 >> 24) & 0xFF] << 24); - - X1 = *RK++ ^ \ - ((uint32_t)RSb[(Y1) & 0xFF]) ^ - ((uint32_t)RSb[(Y0 >> 8) & 0xFF] << 8) ^ - ((uint32_t)RSb[(Y3 >> 16) & 0xFF] << 16) ^ - ((uint32_t)RSb[(Y2 >> 24) & 0xFF] << 24); - - X2 = *RK++ ^ \ - ((uint32_t)RSb[(Y2) & 0xFF]) ^ - ((uint32_t)RSb[(Y1 >> 8) & 0xFF] << 8) ^ - ((uint32_t)RSb[(Y0 >> 16) & 0xFF] << 16) ^ - ((uint32_t)RSb[(Y3 >> 24) & 0xFF] << 24); - - X3 = *RK++ ^ \ - ((uint32_t)RSb[(Y3) & 0xFF]) ^ - ((uint32_t)RSb[(Y2 >> 8) & 0xFF] << 8) ^ - ((uint32_t)RSb[(Y1 >> 16) & 0xFF] << 16) ^ - ((uint32_t)RSb[(Y0 >> 24) & 0xFF] << 24); - } - else /* ENCRYPT */ - { -#endif /* AES_DECRYPTION */ - - for (i = (ctx->rounds >> 1) - 1; i > 0; i--) - { - AES_FROUND(Y0, Y1, Y2, Y3, X0, X1, X2, X3); - AES_FROUND(X0, X1, X2, X3, Y0, Y1, Y2, Y3); - } - - AES_FROUND(Y0, Y1, Y2, Y3, X0, X1, X2, X3); - - X0 = *RK++ ^ \ - ((uint32_t)FSb[(Y0) & 0xFF]) ^ - ((uint32_t)FSb[(Y1 >> 8) & 0xFF] << 8) ^ - ((uint32_t)FSb[(Y2 >> 16) & 0xFF] << 16) ^ - ((uint32_t)FSb[(Y3 >> 24) & 0xFF] << 24); - - X1 = *RK++ ^ \ - ((uint32_t)FSb[(Y1) & 0xFF]) ^ - ((uint32_t)FSb[(Y2 >> 8) & 0xFF] << 8) ^ - ((uint32_t)FSb[(Y3 >> 16) & 0xFF] << 16) ^ - ((uint32_t)FSb[(Y0 >> 24) & 0xFF] << 24); - - X2 = *RK++ ^ \ - ((uint32_t)FSb[(Y2) & 0xFF]) ^ - ((uint32_t)FSb[(Y3 >> 8) & 0xFF] << 8) ^ - ((uint32_t)FSb[(Y0 >> 16) & 0xFF] << 16) ^ - ((uint32_t)FSb[(Y1 >> 24) & 0xFF] << 24); - - X3 = *RK++ ^ \ - ((uint32_t)FSb[(Y3) & 0xFF]) ^ - ((uint32_t)FSb[(Y0 >> 8) & 0xFF] << 8) ^ - ((uint32_t)FSb[(Y1 >> 16) & 0xFF] << 16) ^ - ((uint32_t)FSb[(Y2 >> 24) & 0xFF] << 24); - -#if AES_DECRYPTION // whether AES decryption is supported - } -#endif /* AES_DECRYPTION */ - - PUT_UINT32_LE(X0, output, 0); - PUT_UINT32_LE(X1, output, 4); - PUT_UINT32_LE(X2, output, 8); - PUT_UINT32_LE(X3, output, 12); - - return(0); -} -/* end of aes.c */ diff --git a/nfq/crypto/aes.h b/nfq/crypto/aes.h deleted file mode 100644 index b04724d0..00000000 --- a/nfq/crypto/aes.h +++ /dev/null @@ -1,78 +0,0 @@ -/****************************************************************************** -* -* THIS SOURCE CODE IS HEREBY PLACED INTO THE PUBLIC DOMAIN FOR THE GOOD OF ALL -* -* This is a simple and straightforward implementation of the AES Rijndael -* 128-bit block cipher designed by Vincent Rijmen and Joan Daemen. The focus -* of this work was correctness & accuracy. It is written in 'C' without any -* particular focus upon optimization or speed. It should be endian (memory -* byte order) neutral since the few places that care are handled explicitly. -* -* This implementation of Rijndael was created by Steven M. Gibson of GRC.com. -* -* It is intended for general purpose use, but was written in support of GRC's -* reference implementation of the SQRL (Secure Quick Reliable Login) client. -* -* See: http://csrc.nist.gov/archive/aes/rijndael/wsdindex.html -* -* NO COPYRIGHT IS CLAIMED IN THIS WORK, HOWEVER, NEITHER IS ANY WARRANTY MADE -* REGARDING ITS FITNESS FOR ANY PARTICULAR PURPOSE. USE IT AT YOUR OWN RISK. -* -*******************************************************************************/ - -#pragma once - -/******************************************************************************/ -#define AES_DECRYPTION 0 // whether AES decryption is supported -/******************************************************************************/ - -#include - -#define AES_ENCRYPT 1 // specify whether we're encrypting -#define AES_DECRYPT 0 // or decrypting - -#if defined(_MSC_VER) -#include -typedef UINT32 uint32_t; -#else -#include -#endif - -typedef unsigned char uchar; // add some convienent shorter types -typedef unsigned int uint; - - -/****************************************************************************** - * AES_INIT_KEYGEN_TABLES : MUST be called once before any AES use - ******************************************************************************/ -void aes_init_keygen_tables(void); - - -/****************************************************************************** - * AES_CONTEXT : cipher context / holds inter-call data - ******************************************************************************/ -typedef struct { - int mode; // 1 for Encryption, 0 for Decryption - int rounds; // keysize-based rounds count - uint32_t *rk; // pointer to current round key - uint32_t buf[68]; // key expansion buffer -} aes_context; - - -/****************************************************************************** - * AES_SETKEY : called to expand the key for encryption or decryption - ******************************************************************************/ -int aes_setkey(aes_context *ctx, // pointer to context - int mode, // 1 or 0 for Encrypt/Decrypt - const uchar *key, // AES input key - uint keysize); // size in bytes (must be 16, 24, 32 for - // 128, 192 or 256-bit keys respectively) - // returns 0 for success - -/****************************************************************************** - * AES_CIPHER : called to encrypt or decrypt ONE 128-bit block of data - ******************************************************************************/ -int aes_cipher(aes_context *ctx, // pointer to context - const uchar input[16], // 128-bit block to en/decipher - uchar output[16]); // 128-bit output result block - // returns 0 for success diff --git a/nfq/crypto/gcm.c b/nfq/crypto/gcm.c deleted file mode 100644 index 74842ce6..00000000 --- a/nfq/crypto/gcm.c +++ /dev/null @@ -1,512 +0,0 @@ -/****************************************************************************** -* -* THIS SOURCE CODE IS HEREBY PLACED INTO THE PUBLIC DOMAIN FOR THE GOOD OF ALL -* -* This is a simple and straightforward implementation of AES-GCM authenticated -* encryption. The focus of this work was correctness & accuracy. It is written -* in straight 'C' without any particular focus upon optimization or speed. It -* should be endian (memory byte order) neutral since the few places that care -* are handled explicitly. -* -* This implementation of AES-GCM was created by Steven M. Gibson of GRC.com. -* -* It is intended for general purpose use, but was written in support of GRC's -* reference implementation of the SQRL (Secure Quick Reliable Login) client. -* -* See: http://csrc.nist.gov/publications/nistpubs/800-38D/SP-800-38D.pdf -* http://csrc.nist.gov/groups/ST/toolkit/BCM/documents/proposedmodes/ -* gcm/gcm-revised-spec.pdf -* -* NO COPYRIGHT IS CLAIMED IN THIS WORK, HOWEVER, NEITHER IS ANY WARRANTY MADE -* REGARDING ITS FITNESS FOR ANY PARTICULAR PURPOSE. USE IT AT YOUR OWN RISK. -* -*******************************************************************************/ - -#include "gcm.h" -#include "aes.h" - -/****************************************************************************** - * ==== IMPLEMENTATION WARNING ==== - * - * This code was developed for use within SQRL's fixed environmnent. Thus, it - * is somewhat less "general purpose" than it would be if it were designed as - * a general purpose AES-GCM library. Specifically, it bothers with almost NO - * error checking on parameter limits, buffer bounds, etc. It assumes that it - * is being invoked by its author or by someone who understands the values it - * expects to receive. Its behavior will be undefined otherwise. - * - * All functions that might fail are defined to return 'ints' to indicate a - * problem. Most do not do so now. But this allows for error propagation out - * of internal functions if robust error checking should ever be desired. - * - ******************************************************************************/ - - /* Calculating the "GHASH" - * - * There are many ways of calculating the so-called GHASH in software, each with - * a traditional size vs performance tradeoff. The GHASH (Galois field hash) is - * an intriguing construction which takes two 128-bit strings (also the cipher's - * block size and the fundamental operation size for the system) and hashes them - * into a third 128-bit result. - * - * Many implementation solutions have been worked out that use large precomputed - * table lookups in place of more time consuming bit fiddling, and this approach - * can be scaled easily upward or downward as needed to change the time/space - * tradeoff. It's been studied extensively and there's a solid body of theory and - * practice. For example, without using any lookup tables an implementation - * might obtain 119 cycles per byte throughput, whereas using a simple, though - * large, key-specific 64 kbyte 8-bit lookup table the performance jumps to 13 - * cycles per byte. - * - * And Intel's processors have, since 2010, included an instruction which does - * the entire 128x128->128 bit job in just several 64x64->128 bit pieces. - * - * Since SQRL is interactive, and only processing a few 128-bit blocks, I've - * settled upon a relatively slower but appealing small-table compromise which - * folds a bunch of not only time consuming but also bit twiddling into a simple - * 16-entry table which is attributed to Victor Shoup's 1996 work while at - * Bellcore: "On Fast and Provably Secure MessageAuthentication Based on - * Universal Hashing." See: http://www.shoup.net/papers/macs.pdf - * See, also section 4.1 of the "gcm-revised-spec" cited above. - */ - - /* - * This 16-entry table of pre-computed constants is used by the - * GHASH multiplier to improve over a strictly table-free but - * significantly slower 128x128 bit multiple within GF(2^128). - */ -static const uint64_t last4[16] = { - 0x0000, 0x1c20, 0x3840, 0x2460, 0x7080, 0x6ca0, 0x48c0, 0x54e0, - 0xe100, 0xfd20, 0xd940, 0xc560, 0x9180, 0x8da0, 0xa9c0, 0xb5e0 }; - -/* - * Platform Endianness Neutralizing Load and Store Macro definitions - * GCM wants platform-neutral Big Endian (BE) byte ordering - */ -#define GET_UINT32_BE(n,b,i) { \ - (n) = ( (uint32_t) (b)[(i) ] << 24 ) \ - | ( (uint32_t) (b)[(i) + 1] << 16 ) \ - | ( (uint32_t) (b)[(i) + 2] << 8 ) \ - | ( (uint32_t) (b)[(i) + 3] ); } - -#define PUT_UINT32_BE(n,b,i) { \ - (b)[(i) ] = (uchar) ( (n) >> 24 ); \ - (b)[(i) + 1] = (uchar) ( (n) >> 16 ); \ - (b)[(i) + 2] = (uchar) ( (n) >> 8 ); \ - (b)[(i) + 3] = (uchar) ( (n) ); } - - - /****************************************************************************** - * - * GCM_INITIALIZE - * - * Must be called once to initialize the GCM library. - * - * At present, this only calls the AES keygen table generator, which expands - * the AES keying tables for use. This is NOT A THREAD-SAFE function, so it - * MUST be called during system initialization before a multi-threading - * environment is running. - * - ******************************************************************************/ -int gcm_initialize(void) -{ - aes_init_keygen_tables(); - return(0); -} - - -/****************************************************************************** - * - * GCM_MULT - * - * Performs a GHASH operation on the 128-bit input vector 'x', setting - * the 128-bit output vector to 'x' times H using our precomputed tables. - * 'x' and 'output' are seen as elements of GCM's GF(2^128) Galois field. - * - ******************************************************************************/ -static void gcm_mult(gcm_context *ctx, // pointer to established context - const uchar x[16], // pointer to 128-bit input vector - uchar output[16]) // pointer to 128-bit output vector -{ - int i; - uchar lo, hi, rem; - uint64_t zh, zl; - - lo = (uchar)(x[15] & 0x0f); - hi = (uchar)(x[15] >> 4); - zh = ctx->HH[lo]; - zl = ctx->HL[lo]; - - for (i = 15; i >= 0; i--) { - lo = (uchar)(x[i] & 0x0f); - hi = (uchar)(x[i] >> 4); - - if (i != 15) { - rem = (uchar)(zl & 0x0f); - zl = (zh << 60) | (zl >> 4); - zh = (zh >> 4); - zh ^= (uint64_t)last4[rem] << 48; - zh ^= ctx->HH[lo]; - zl ^= ctx->HL[lo]; - } - rem = (uchar)(zl & 0x0f); - zl = (zh << 60) | (zl >> 4); - zh = (zh >> 4); - zh ^= (uint64_t)last4[rem] << 48; - zh ^= ctx->HH[hi]; - zl ^= ctx->HL[hi]; - } - PUT_UINT32_BE(zh >> 32, output, 0); - PUT_UINT32_BE(zh, output, 4); - PUT_UINT32_BE(zl >> 32, output, 8); - PUT_UINT32_BE(zl, output, 12); -} - - -/****************************************************************************** - * - * GCM_SETKEY - * - * This is called to set the AES-GCM key. It initializes the AES key - * and populates the gcm context's pre-calculated HTables. - * - ******************************************************************************/ -int gcm_setkey(gcm_context *ctx, // pointer to caller-provided gcm context - const uchar *key, // pointer to the AES encryption key - const uint keysize) // size in bytes (must be 16, 24, 32 for - // 128, 192 or 256-bit keys respectively) -{ - int ret, i, j; - uint64_t hi, lo; - uint64_t vl, vh; - unsigned char h[16]; - - memset(ctx, 0, sizeof(gcm_context)); // zero caller-provided GCM context - memset(h, 0, 16); // initialize the block to encrypt - - // encrypt the null 128-bit block to generate a key-based value - // which is then used to initialize our GHASH lookup tables - if ((ret = aes_setkey(&ctx->aes_ctx, AES_ENCRYPT, key, keysize)) != 0) - return(ret); - if ((ret = aes_cipher(&ctx->aes_ctx, h, h)) != 0) - return(ret); - - GET_UINT32_BE(hi, h, 0); // pack h as two 64-bit ints, big-endian - GET_UINT32_BE(lo, h, 4); - vh = (uint64_t)hi << 32 | lo; - - GET_UINT32_BE(hi, h, 8); - GET_UINT32_BE(lo, h, 12); - vl = (uint64_t)hi << 32 | lo; - - ctx->HL[8] = vl; // 8 = 1000 corresponds to 1 in GF(2^128) - ctx->HH[8] = vh; - ctx->HH[0] = 0; // 0 corresponds to 0 in GF(2^128) - ctx->HL[0] = 0; - - for (i = 4; i > 0; i >>= 1) { - uint32_t T = (uint32_t)(vl & 1) * 0xe1000000U; - vl = (vh << 63) | (vl >> 1); - vh = (vh >> 1) ^ ((uint64_t)T << 32); - ctx->HL[i] = vl; - ctx->HH[i] = vh; - } - for (i = 2; i < 16; i <<= 1) { - uint64_t *HiL = ctx->HL + i, *HiH = ctx->HH + i; - vh = *HiH; - vl = *HiL; - for (j = 1; j < i; j++) { - HiH[j] = vh ^ ctx->HH[j]; - HiL[j] = vl ^ ctx->HL[j]; - } - } - return(0); -} - - -/****************************************************************************** - * - * GCM processing occurs four phases: SETKEY, START, UPDATE and FINISH. - * - * SETKEY: - * - * START: Sets the Encryption/Decryption mode. - * Accepts the initialization vector and additional data. - * - * UPDATE: Encrypts or decrypts the plaintext or ciphertext. - * - * FINISH: Performs a final GHASH to generate the authentication tag. - * - ****************************************************************************** - * - * GCM_START - * - * Given a user-provided GCM context, this initializes it, sets the encryption - * mode, and preprocesses the initialization vector and additional AEAD data. - * - ******************************************************************************/ -int gcm_start(gcm_context *ctx, // pointer to user-provided GCM context - int mode, // AES_ENCRYPT or AES_DECRYPT - const uchar *iv, // pointer to initialization vector - size_t iv_len, // IV length in bytes (should == 12) - const uchar *add, // ptr to additional AEAD data (NULL if none) - size_t add_len) // length of additional AEAD data (bytes) -{ - int ret; // our error return if the AES encrypt fails - uchar work_buf[16]; // XOR source built from provided IV if len != 16 - const uchar *p; // general purpose array pointer - size_t use_len; // byte count to process, up to 16 bytes - size_t i; // local loop iterator - - // since the context might be reused under the same key - // we zero the working buffers for this next new process - memset(ctx->y, 0x00, sizeof(ctx->y)); - memset(ctx->buf, 0x00, sizeof(ctx->buf)); - ctx->len = 0; - ctx->add_len = 0; - - ctx->mode = mode; // set the GCM encryption/decryption mode - ctx->aes_ctx.mode = AES_ENCRYPT; // GCM *always* runs AES in ENCRYPTION mode - - if (iv_len == 12) { // GCM natively uses a 12-byte, 96-bit IV - memcpy(ctx->y, iv, iv_len); // copy the IV to the top of the 'y' buff - ctx->y[15] = 1; // start "counting" from 1 (not 0) - } - else // if we don't have a 12-byte IV, we GHASH whatever we've been given - { - memset(work_buf, 0x00, 16); // clear the working buffer - PUT_UINT32_BE(iv_len * 8, work_buf, 12); // place the IV into buffer - - p = iv; - while (iv_len > 0) { - use_len = (iv_len < 16) ? iv_len : 16; - for (i = 0; i < use_len; i++) ctx->y[i] ^= p[i]; - gcm_mult(ctx, ctx->y, ctx->y); - iv_len -= use_len; - p += use_len; - } - for (i = 0; i < 16; i++) ctx->y[i] ^= work_buf[i]; - gcm_mult(ctx, ctx->y, ctx->y); - } - - if ((ret = aes_cipher(&ctx->aes_ctx, ctx->y, ctx->base_ectr)) != 0) - return(ret); - - ctx->add_len = add_len; - p = add; - while (add_len > 0) { - use_len = (add_len < 16) ? add_len : 16; - for (i = 0; i < use_len; i++) ctx->buf[i] ^= p[i]; - gcm_mult(ctx, ctx->buf, ctx->buf); - add_len -= use_len; - p += use_len; - } - return(0); -} - -/****************************************************************************** - * - * GCM_UPDATE - * - * This is called once or more to process bulk plaintext or ciphertext data. - * We give this some number of bytes of input and it returns the same number - * of output bytes. If called multiple times (which is fine) all but the final - * invocation MUST be called with length mod 16 == 0. (Only the final call can - * have a partial block length of < 128 bits.) - * - ******************************************************************************/ -int gcm_update(gcm_context *ctx, // pointer to user-provided GCM context - size_t length, // length, in bytes, of data to process - const uchar *input, // pointer to source data - uchar *output) // pointer to destination data -{ - int ret; // our error return if the AES encrypt fails - uchar ectr[16]; // counter-mode cipher output for XORing - size_t use_len; // byte count to process, up to 16 bytes - size_t i; // local loop iterator - - ctx->len += length; // bump the GCM context's running length count - - while (length > 0) { - // clamp the length to process at 16 bytes - use_len = (length < 16) ? length : 16; - - // increment the context's 128-bit IV||Counter 'y' vector - for (i = 16; i > 12; i--) if (++ctx->y[i - 1] != 0) break; - - // encrypt the context's 'y' vector under the established key - if ((ret = aes_cipher(&ctx->aes_ctx, ctx->y, ectr)) != 0) - return(ret); - - // encrypt or decrypt the input to the output - if (ctx->mode == AES_ENCRYPT) - { - for (i = 0; i < use_len; i++) { - // XOR the cipher's ouptut vector (ectr) with our input - output[i] = (uchar)(ectr[i] ^ input[i]); - // now we mix in our data into the authentication hash. - // if we're ENcrypting we XOR in the post-XOR (output) - // results, but if we're DEcrypting we XOR in the input - // data - ctx->buf[i] ^= output[i]; - } - } - else - { - for (i = 0; i < use_len; i++) { - // but if we're DEcrypting we XOR in the input data first, - // i.e. before saving to ouput data, otherwise if the input - // and output buffer are the same (inplace decryption) we - // would not get the correct auth tag - - ctx->buf[i] ^= input[i]; - - // XOR the cipher's ouptut vector (ectr) with our input - output[i] = (uchar)(ectr[i] ^ input[i]); - } - } - gcm_mult(ctx, ctx->buf, ctx->buf); // perform a GHASH operation - - length -= use_len; // drop the remaining byte count to process - input += use_len; // bump our input pointer forward - output += use_len; // bump our output pointer forward - } - return(0); -} - -/****************************************************************************** - * - * GCM_FINISH - * - * This is called once after all calls to GCM_UPDATE to finalize the GCM. - * It performs the final GHASH to produce the resulting authentication TAG. - * - ******************************************************************************/ -int gcm_finish(gcm_context *ctx, // pointer to user-provided GCM context - uchar *tag, // pointer to buffer which receives the tag - size_t tag_len) // length, in bytes, of the tag-receiving buf -{ - uchar work_buf[16]; - uint64_t orig_len = ctx->len * 8; - uint64_t orig_add_len = ctx->add_len * 8; - size_t i; - - if (tag_len != 0) memcpy(tag, ctx->base_ectr, tag_len); - - if (orig_len || orig_add_len) { - memset(work_buf, 0x00, 16); - - PUT_UINT32_BE((orig_add_len >> 32), work_buf, 0); - PUT_UINT32_BE((orig_add_len), work_buf, 4); - PUT_UINT32_BE((orig_len >> 32), work_buf, 8); - PUT_UINT32_BE((orig_len), work_buf, 12); - - for (i = 0; i < 16; i++) ctx->buf[i] ^= work_buf[i]; - gcm_mult(ctx, ctx->buf, ctx->buf); - for (i = 0; i < tag_len; i++) tag[i] ^= ctx->buf[i]; - } - return(0); -} - - -/****************************************************************************** - * - * GCM_CRYPT_AND_TAG - * - * This either encrypts or decrypts the user-provided data and, either - * way, generates an authentication tag of the requested length. It must be - * called with a GCM context whose key has already been set with GCM_SETKEY. - * - * The user would typically call this explicitly to ENCRYPT a buffer of data - * and optional associated data, and produce its an authentication tag. - * - * To reverse the process the user would typically call the companion - * GCM_AUTH_DECRYPT function to decrypt data and verify a user-provided - * authentication tag. The GCM_AUTH_DECRYPT function calls this function - * to perform its decryption and tag generation, which it then compares. - * - ******************************************************************************/ -int gcm_crypt_and_tag( - gcm_context *ctx, // gcm context with key already setup - int mode, // cipher direction: AES_ENCRYPT or AES_DECRYPT - const uchar *iv, // pointer to the 12-byte initialization vector - size_t iv_len, // byte length if the IV. should always be 12 - const uchar *add, // pointer to the non-ciphered additional data - size_t add_len, // byte length of the additional AEAD data - const uchar *input, // pointer to the cipher data source - uchar *output, // pointer to the cipher data destination - size_t length, // byte length of the cipher data - uchar *tag, // pointer to the tag to be generated - size_t tag_len) // byte length of the tag to be generated -{ /* - assuming that the caller has already invoked gcm_setkey to - prepare the gcm context with the keying material, we simply - invoke each of the three GCM sub-functions in turn... - */ - gcm_start(ctx, mode, iv, iv_len, add, add_len); - gcm_update(ctx, length, input, output); - gcm_finish(ctx, tag, tag_len); - return(0); -} - - -/****************************************************************************** - * - * GCM_AUTH_DECRYPT - * - * This DECRYPTS a user-provided data buffer with optional associated data. - * It then verifies a user-supplied authentication tag against the tag just - * re-created during decryption to verify that the data has not been altered. - * - * This function calls GCM_CRYPT_AND_TAG (above) to perform the decryption - * and authentication tag generation. - * - ******************************************************************************/ -int gcm_auth_decrypt( - gcm_context *ctx, // gcm context with key already setup - const uchar *iv, // pointer to the 12-byte initialization vector - size_t iv_len, // byte length if the IV. should always be 12 - const uchar *add, // pointer to the non-ciphered additional data - size_t add_len, // byte length of the additional AEAD data - const uchar *input, // pointer to the cipher data source - uchar *output, // pointer to the cipher data destination - size_t length, // byte length of the cipher data - const uchar *tag, // pointer to the tag to be authenticated - size_t tag_len) // byte length of the tag <= 16 -{ - uchar check_tag[16]; // the tag generated and returned by decryption - int diff; // an ORed flag to detect authentication errors - size_t i; // our local iterator - /* - we use GCM_DECRYPT_AND_TAG (above) to perform our decryption - (which is an identical XORing to reverse the previous one) - and also to re-generate the matching authentication tag - */ - gcm_crypt_and_tag(ctx, AES_DECRYPT, iv, iv_len, add, add_len, - input, output, length, check_tag, tag_len); - - // now we verify the authentication tag in 'constant time' - for (diff = 0, i = 0; i < tag_len; i++) - diff |= tag[i] ^ check_tag[i]; - - if (diff != 0) { // see whether any bits differed? - memset(output, 0, length); // if so... wipe the output data - return(GCM_AUTH_FAILURE); // return GCM_AUTH_FAILURE - } - return(0); -} - -/****************************************************************************** - * - * GCM_ZERO_CTX - * - * The GCM context contains both the GCM context and the AES context. - * This includes keying and key-related material which is security- - * sensitive, so it MUST be zeroed after use. This function does that. - * - ******************************************************************************/ -void gcm_zero_ctx(gcm_context *ctx) -{ - // zero the context originally provided to us - memset(ctx, 0, sizeof(gcm_context)); -} diff --git a/nfq/crypto/gcm.h b/nfq/crypto/gcm.h deleted file mode 100644 index 42adad99..00000000 --- a/nfq/crypto/gcm.h +++ /dev/null @@ -1,183 +0,0 @@ -/****************************************************************************** -* -* THIS SOURCE CODE IS HEREBY PLACED INTO THE PUBLIC DOMAIN FOR THE GOOD OF ALL -* -* This is a simple and straightforward implementation of AES-GCM authenticated -* encryption. The focus of this work was correctness & accuracy. It is written -* in straight 'C' without any particular focus upon optimization or speed. It -* should be endian (memory byte order) neutral since the few places that care -* are handled explicitly. -* -* This implementation of AES-GCM was created by Steven M. Gibson of GRC.com. -* -* It is intended for general purpose use, but was written in support of GRC's -* reference implementation of the SQRL (Secure Quick Reliable Login) client. -* -* See: http://csrc.nist.gov/publications/nistpubs/800-38D/SP-800-38D.pdf -* http://csrc.nist.gov/groups/ST/toolkit/BCM/documents/proposedmodes/ \ -* gcm/gcm-revised-spec.pdf -* -* NO COPYRIGHT IS CLAIMED IN THIS WORK, HOWEVER, NEITHER IS ANY WARRANTY MADE -* REGARDING ITS FITNESS FOR ANY PARTICULAR PURPOSE. USE IT AT YOUR OWN RISK. -* -*******************************************************************************/ -#pragma once - -#define GCM_AUTH_FAILURE 0x55555555 // authentication failure - -#include "aes.h" // gcm_context includes aes_context - -#if defined(_MSC_VER) -#include -typedef unsigned int size_t;// use the right type for length declarations -typedef UINT32 uint32_t; -typedef UINT64 uint64_t; -#else -#include -#endif - - -/****************************************************************************** - * GCM_CONTEXT : GCM context / holds keytables, instance data, and AES ctx - ******************************************************************************/ -typedef struct { - int mode; // cipher direction: encrypt/decrypt - uint64_t len; // cipher data length processed so far - uint64_t add_len; // total add data length - uint64_t HL[16]; // precalculated lo-half HTable - uint64_t HH[16]; // precalculated hi-half HTable - uchar base_ectr[16]; // first counter-mode cipher output for tag - uchar y[16]; // the current cipher-input IV|Counter value - uchar buf[16]; // buf working value - aes_context aes_ctx; // cipher context used -} gcm_context; - - -/****************************************************************************** - * GCM_CONTEXT : MUST be called once before ANY use of this library - ******************************************************************************/ -int gcm_initialize(void); - - -/****************************************************************************** - * GCM_SETKEY : sets the GCM (and AES) keying material for use - ******************************************************************************/ -int gcm_setkey(gcm_context *ctx, // caller-provided context ptr - const uchar *key, // pointer to cipher key - const uint keysize // size in bytes (must be 16, 24, 32 for - // 128, 192 or 256-bit keys respectively) -); // returns 0 for success - - -/****************************************************************************** - * - * GCM_CRYPT_AND_TAG - * - * This either encrypts or decrypts the user-provided data and, either - * way, generates an authentication tag of the requested length. It must be - * called with a GCM context whose key has already been set with GCM_SETKEY. - * - * The user would typically call this explicitly to ENCRYPT a buffer of data - * and optional associated data, and produce its an authentication tag. - * - * To reverse the process the user would typically call the companion - * GCM_AUTH_DECRYPT function to decrypt data and verify a user-provided - * authentication tag. The GCM_AUTH_DECRYPT function calls this function - * to perform its decryption and tag generation, which it then compares. - * - ******************************************************************************/ -int gcm_crypt_and_tag( - gcm_context *ctx, // gcm context with key already setup - int mode, // cipher direction: ENCRYPT (1) or DECRYPT (0) - const uchar *iv, // pointer to the 12-byte initialization vector - size_t iv_len, // byte length if the IV. should always be 12 - const uchar *add, // pointer to the non-ciphered additional data - size_t add_len, // byte length of the additional AEAD data - const uchar *input, // pointer to the cipher data source - uchar *output, // pointer to the cipher data destination - size_t length, // byte length of the cipher data - uchar *tag, // pointer to the tag to be generated - size_t tag_len); // byte length of the tag to be generated - - -/****************************************************************************** - * - * GCM_AUTH_DECRYPT - * - * This DECRYPTS a user-provided data buffer with optional associated data. - * It then verifies a user-supplied authentication tag against the tag just - * re-created during decryption to verify that the data has not been altered. - * - * This function calls GCM_CRYPT_AND_TAG (above) to perform the decryption - * and authentication tag generation. - * - ******************************************************************************/ -int gcm_auth_decrypt( - gcm_context *ctx, // gcm context with key already setup - const uchar *iv, // pointer to the 12-byte initialization vector - size_t iv_len, // byte length if the IV. should always be 12 - const uchar *add, // pointer to the non-ciphered additional data - size_t add_len, // byte length of the additional AEAD data - const uchar *input, // pointer to the cipher data source - uchar *output, // pointer to the cipher data destination - size_t length, // byte length of the cipher data - const uchar *tag, // pointer to the tag to be authenticated - size_t tag_len); // byte length of the tag <= 16 - - -/****************************************************************************** - * - * GCM_START - * - * Given a user-provided GCM context, this initializes it, sets the encryption - * mode, and preprocesses the initialization vector and additional AEAD data. - * - ******************************************************************************/ -int gcm_start(gcm_context *ctx, // pointer to user-provided GCM context - int mode, // ENCRYPT (1) or DECRYPT (0) - const uchar *iv, // pointer to initialization vector - size_t iv_len, // IV length in bytes (should == 12) - const uchar *add, // pointer to additional AEAD data (NULL if none) - size_t add_len); // length of additional AEAD data (bytes) - - -/****************************************************************************** - * - * GCM_UPDATE - * - * This is called once or more to process bulk plaintext or ciphertext data. - * We give this some number of bytes of input and it returns the same number - * of output bytes. If called multiple times (which is fine) all but the final - * invocation MUST be called with length mod 16 == 0. (Only the final call can - * have a partial block length of < 128 bits.) - * - ******************************************************************************/ -int gcm_update(gcm_context *ctx, // pointer to user-provided GCM context - size_t length, // length, in bytes, of data to process - const uchar *input, // pointer to source data - uchar *output); // pointer to destination data - - -/****************************************************************************** - * - * GCM_FINISH - * - * This is called once after all calls to GCM_UPDATE to finalize the GCM. - * It performs the final GHASH to produce the resulting authentication TAG. - * - ******************************************************************************/ -int gcm_finish(gcm_context *ctx, // pointer to user-provided GCM context - uchar *tag, // ptr to tag buffer - NULL if tag_len = 0 - size_t tag_len); // length, in bytes, of the tag-receiving buf - - -/****************************************************************************** - * - * GCM_ZERO_CTX - * - * The GCM context contains both the GCM context and the AES context. - * This includes keying and key-related material which is security- - * sensitive, so it MUST be zeroed after use. This function does that. - * - ******************************************************************************/ -void gcm_zero_ctx(gcm_context *ctx); diff --git a/nfq/crypto/hkdf.c b/nfq/crypto/hkdf.c deleted file mode 100644 index 266cb37d..00000000 --- a/nfq/crypto/hkdf.c +++ /dev/null @@ -1,337 +0,0 @@ -/**************************** hkdf.c ***************************/ -/***************** See RFC 6234 for details. *******************/ -/* Copyright (c) 2011 IETF Trust and the persons identified as */ -/* authors of the code. All rights reserved. */ -/* See sha.h for terms of use and redistribution. */ - -/* - * Description: - * This file implements the HKDF algorithm (HMAC-based - * Extract-and-Expand Key Derivation Function, RFC 5869), - * expressed in terms of the various SHA algorithms. - */ - -#include "sha.h" -#include -#include - - /* - * hkdf - * - * Description: - * This function will generate keying material using HKDF. - * - * Parameters: - * whichSha: [in] - * One of SHA1, SHA224, SHA256, SHA384, SHA512 - * salt[ ]: [in] - * The optional salt value (a non-secret random value); - * if not provided (salt == NULL), it is set internally - * to a string of HashLen(whichSha) zeros. - * salt_len: [in] - * The length of the salt value. (Ignored if salt == NULL.) - * ikm[ ]: [in] - * Input keying material. - * ikm_len: [in] - * The length of the input keying material. - * info[ ]: [in] - * The optional context and application specific information. - * If info == NULL or a zero-length string, it is ignored. - * info_len: [in] - * The length of the optional context and application specific - * information. (Ignored if info == NULL.) - * okm[ ]: [out] - * Where the HKDF is to be stored. - * okm_len: [in] - * The length of the buffer to hold okm. - * okm_len must be <= 255 * USHABlockSize(whichSha) - * - * Notes: - * Calls hkdfExtract() and hkdfExpand(). - * - * Returns: - * sha Error Code. - * - */ -int hkdf(SHAversion whichSha, - const unsigned char *salt, size_t salt_len, - const unsigned char *ikm, size_t ikm_len, - const unsigned char *info, size_t info_len, - uint8_t okm[], size_t okm_len) -{ - uint8_t prk[USHAMaxHashSize]; - return hkdfExtract(whichSha, salt, salt_len, ikm, ikm_len, prk) || - hkdfExpand(whichSha, prk, USHAHashSize(whichSha), info, - info_len, okm, okm_len); -} - -/* - * hkdfExtract - * - * Description: - * This function will perform HKDF extraction. - * - * Parameters: - * whichSha: [in] - * One of SHA1, SHA224, SHA256, SHA384, SHA512 - * salt[ ]: [in] - * The optional salt value (a non-secret random value); - * if not provided (salt == NULL), it is set internally - * to a string of HashLen(whichSha) zeros. - * salt_len: [in] - * The length of the salt value. (Ignored if salt == NULL.) - * ikm[ ]: [in] - * Input keying material. - * ikm_len: [in] - * The length of the input keying material. - * prk[ ]: [out] - * Array where the HKDF extraction is to be stored. - * Must be larger than USHAHashSize(whichSha); - * - * Returns: - * sha Error Code. - * - */ -int hkdfExtract(SHAversion whichSha, - const unsigned char *salt, size_t salt_len, - const unsigned char *ikm, size_t ikm_len, - uint8_t prk[USHAMaxHashSize]) -{ - unsigned char nullSalt[USHAMaxHashSize]; - if (salt == 0) { - salt = nullSalt; - salt_len = USHAHashSize(whichSha); - memset(nullSalt, '\0', salt_len); - } - else if (salt_len < 0) { - return shaBadParam; - } - return hmac(whichSha, ikm, ikm_len, salt, salt_len, prk); -} - -/* - * hkdfExpand - * - * Description: - * This function will perform HKDF expansion. - * - * Parameters: - * whichSha: [in] - * One of SHA1, SHA224, SHA256, SHA384, SHA512 - * prk[ ]: [in] - * The pseudo-random key to be expanded; either obtained - * directly from a cryptographically strong, uniformly - * distributed pseudo-random number generator, or as the - * output from hkdfExtract(). - * prk_len: [in] - * The length of the pseudo-random key in prk; - * should at least be equal to USHAHashSize(whichSHA). - * info[ ]: [in] - * The optional context and application specific information. - * If info == NULL or a zero-length string, it is ignored. - * info_len: [in] - * The length of the optional context and application specific - * information. (Ignored if info == NULL.) - * okm[ ]: [out] - * Where the HKDF is to be stored. - * okm_len: [in] - * The length of the buffer to hold okm. - * okm_len must be <= 255 * USHABlockSize(whichSha) - * - * Returns: - * sha Error Code. - * - */ -int hkdfExpand(SHAversion whichSha, const uint8_t prk[], size_t prk_len, - const unsigned char *info, size_t info_len, - uint8_t okm[], size_t okm_len) -{ - size_t hash_len, N; - unsigned char T[USHAMaxHashSize]; - size_t Tlen, where, i; - - if (info == 0) { - info = (const unsigned char *)""; - info_len = 0; - } - else if (info_len < 0) { - return shaBadParam; - } - if (okm_len <= 0) return shaBadParam; - if (!okm) return shaBadParam; - - hash_len = USHAHashSize(whichSha); - if (prk_len < hash_len) return shaBadParam; - N = okm_len / hash_len; - if ((okm_len % hash_len) != 0) N++; - if (N > 255) return shaBadParam; - - Tlen = 0; - where = 0; - for (i = 1; i <= N; i++) { - HMACContext context; - unsigned char c = i; - int ret = hmacReset(&context, whichSha, prk, prk_len) || - hmacInput(&context, T, Tlen) || - hmacInput(&context, info, info_len) || - hmacInput(&context, &c, 1) || - hmacResult(&context, T); - if (ret != shaSuccess) return ret; - memcpy(okm + where, T, - (i != N) ? hash_len : (okm_len - where)); - where += hash_len; - Tlen = hash_len; - } - return shaSuccess; -} - -/* - * hkdfReset - * - * Description: - * This function will initialize the hkdfContext in preparation - * for key derivation using the modular HKDF interface for - * arbitrary length inputs. - * - * Parameters: - * context: [in/out] - * The context to reset. - * whichSha: [in] - * One of SHA1, SHA224, SHA256, SHA384, SHA512 - * salt[ ]: [in] - * The optional salt value (a non-secret random value); - * if not provided (salt == NULL), it is set internally - * to a string of HashLen(whichSha) zeros. - * salt_len: [in] - * The length of the salt value. (Ignored if salt == NULL.) - * - * Returns: - * sha Error Code. - * - */ -int hkdfReset(HKDFContext *context, enum SHAversion whichSha, - const unsigned char *salt, size_t salt_len) -{ - unsigned char nullSalt[USHAMaxHashSize]; - if (!context) return shaNull; - - context->whichSha = whichSha; - context->hashSize = USHAHashSize(whichSha); - if (salt == 0) { - salt = nullSalt; - salt_len = context->hashSize; - memset(nullSalt, '\0', salt_len); - } - - return hmacReset(&context->hmacContext, whichSha, salt, salt_len); -} - -/* - * hkdfInput - * - * Description: - * This function accepts an array of octets as the next portion - * of the input keying material. It may be called multiple times. - * - * Parameters: - * context: [in/out] - * The HKDF context to update. - * ikm[ ]: [in] - * An array of octets representing the next portion of - * the input keying material. - * ikm_len: [in] - * The length of ikm. - * - * Returns: - * sha Error Code. - * - */ -int hkdfInput(HKDFContext *context, const unsigned char *ikm, - size_t ikm_len) -{ - if (!context) return shaNull; - if (context->Corrupted) return context->Corrupted; - if (context->Computed) return context->Corrupted = shaStateError; - return hmacInput(&context->hmacContext, ikm, ikm_len); -} - -/* - * hkdfFinalBits - * - * Description: - * This function will add in any final bits of the - * input keying material. - * - * Parameters: - * context: [in/out] - * The HKDF context to update - * ikm_bits: [in] - * The final bits of the input keying material, in the upper - * portion of the byte. (Use 0b###00000 instead of 0b00000### - * to input the three bits ###.) - * ikm_bit_count: [in] - * The number of bits in message_bits, between 1 and 7. - * - * Returns: - * sha Error Code. - */ -int hkdfFinalBits(HKDFContext *context, uint8_t ikm_bits, - unsigned int ikm_bit_count) -{ - if (!context) return shaNull; - if (context->Corrupted) return context->Corrupted; - if (context->Computed) return context->Corrupted = shaStateError; - return hmacFinalBits(&context->hmacContext, ikm_bits, ikm_bit_count); -} - -/* - * hkdfResult - * - * Description: - * This function will finish the HKDF extraction and perform the - * final HKDF expansion. - * - * Parameters: - * context: [in/out] - * The HKDF context to use to calculate the HKDF hash. - * prk[ ]: [out] - * An optional location to store the HKDF extraction. - * Either NULL, or pointer to a buffer that must be - * larger than USHAHashSize(whichSha); - * info[ ]: [in] - * The optional context and application specific information. - * If info == NULL or a zero-length string, it is ignored. - * info_len: [in] - * The length of the optional context and application specific - * information. (Ignored if info == NULL.) - * okm[ ]: [out] - * Where the HKDF is to be stored. - * okm_len: [in] - * The length of the buffer to hold okm. - * okm_len must be <= 255 * USHABlockSize(whichSha) - * - * Returns: - * sha Error Code. - * - */ -int hkdfResult(HKDFContext *context, - uint8_t prk[USHAMaxHashSize], - const unsigned char *info, size_t info_len, - uint8_t okm[], size_t okm_len) -{ - uint8_t prkbuf[USHAMaxHashSize]; - int ret; - - if (!context) return shaNull; - if (context->Corrupted) return context->Corrupted; - if (context->Computed) return context->Corrupted = shaStateError; - if (!okm) return context->Corrupted = shaBadParam; - if (!prk) prk = prkbuf; - - ret = hmacResult(&context->hmacContext, prk) || - hkdfExpand(context->whichSha, prk, context->hashSize, info, - info_len, okm, okm_len); - context->Computed = 1; - return context->Corrupted = ret; -} - diff --git a/nfq/crypto/hmac.c b/nfq/crypto/hmac.c deleted file mode 100644 index 9e053255..00000000 --- a/nfq/crypto/hmac.c +++ /dev/null @@ -1,250 +0,0 @@ -/**************************** hmac.c ***************************/ -/***************** See RFC 6234 for details. *******************/ -/* Copyright (c) 2011 IETF Trust and the persons identified as */ -/* authors of the code. All rights reserved. */ -/* See sha.h for terms of use and redistribution. */ - -/* - * Description: - * This file implements the HMAC algorithm (Keyed-Hashing for - * Message Authentication, [RFC 2104]), expressed in terms of - * the various SHA algorithms. - */ - -#include "sha.h" -#include - - /* - * hmac - * - * Description: - * This function will compute an HMAC message digest. - * - * Parameters: - * whichSha: [in] - * One of SHA1, SHA224, SHA256, SHA384, SHA512 - * message_array[ ]: [in] - * An array of octets representing the message. - * Note: in RFC 2104, this parameter is known - * as 'text'. - * length: [in] - * The length of the message in message_array. - * key[ ]: [in] - * The secret shared key. - * key_len: [in] - * The length of the secret shared key. - * digest[ ]: [out] - * Where the digest is to be returned. - * NOTE: The length of the digest is determined by - * the value of whichSha. - * - * Returns: - * sha Error Code. - * - */ - -int hmac(SHAversion whichSha, - const unsigned char *message_array, size_t length, - const unsigned char *key, size_t key_len, - uint8_t digest[USHAMaxHashSize]) -{ - HMACContext context; - return hmacReset(&context, whichSha, key, key_len) || - hmacInput(&context, message_array, length) || - hmacResult(&context, digest); -} - -/* - * hmacReset - * - * Description: - * This function will initialize the hmacContext in preparation - * for computing a new HMAC message digest. - * - * Parameters: - * context: [in/out] - * The context to reset. - * whichSha: [in] - * One of SHA1, SHA224, SHA256, SHA384, SHA512 - * key[ ]: [in] - * The secret shared key. - * key_len: [in] - * The length of the secret shared key. - * - * Returns: - * sha Error Code. - * - */ -int hmacReset(HMACContext *context, enum SHAversion whichSha, - const unsigned char *key, size_t key_len) -{ - size_t i, blocksize, hashsize; - int ret; - - /* inner padding - key XORd with ipad */ - unsigned char k_ipad[USHA_Max_Message_Block_Size]; - - /* temporary buffer when keylen > blocksize */ - unsigned char tempkey[USHAMaxHashSize]; - - if (!context) return shaNull; - context->Computed = 0; - context->Corrupted = shaSuccess; - - blocksize = context->blockSize = USHABlockSize(whichSha); - hashsize = context->hashSize = USHAHashSize(whichSha); - context->whichSha = whichSha; - - /* - * If key is longer than the hash blocksize, - * reset it to key = HASH(key). - */ - if (key_len > blocksize) { - USHAContext tcontext; - int err = USHAReset(&tcontext, whichSha) || - USHAInput(&tcontext, key, key_len) || - USHAResult(&tcontext, tempkey); - if (err != shaSuccess) return err; - - key = tempkey; - key_len = hashsize; - } - - /* - * The HMAC transform looks like: - * - * SHA(K XOR opad, SHA(K XOR ipad, text)) - * - * where K is an n byte key, 0-padded to a total of blocksize bytes, - * ipad is the byte 0x36 repeated blocksize times, - * opad is the byte 0x5c repeated blocksize times, - * and text is the data being protected. - */ - - /* store key into the pads, XOR'd with ipad and opad values */ - for (i = 0; i < key_len; i++) { - k_ipad[i] = key[i] ^ 0x36; - context->k_opad[i] = key[i] ^ 0x5c; - } - /* remaining pad bytes are '\0' XOR'd with ipad and opad values */ - for (; i < blocksize; i++) { - k_ipad[i] = 0x36; - context->k_opad[i] = 0x5c; - } - - /* perform inner hash */ - /* init context for 1st pass */ - ret = USHAReset(&context->shaContext, whichSha) || - /* and start with inner pad */ - USHAInput(&context->shaContext, k_ipad, blocksize); - return context->Corrupted = ret; -} - -/* - * hmacInput - * - * Description: - * This function accepts an array of octets as the next portion - * of the message. It may be called multiple times. - * - * Parameters: - * context: [in/out] - * The HMAC context to update. - * text[ ]: [in] - * An array of octets representing the next portion of - * the message. - * text_len: [in] - * The length of the message in text. - * - * Returns: - * sha Error Code. - * - */ -int hmacInput(HMACContext *context, const unsigned char *text, - size_t text_len) -{ - if (!context) return shaNull; - if (context->Corrupted) return context->Corrupted; - if (context->Computed) return context->Corrupted = shaStateError; - /* then text of datagram */ - return context->Corrupted = - USHAInput(&context->shaContext, text, text_len); -} - -/* - * hmacFinalBits - * - * Description: - * This function will add in any final bits of the message. - * - * Parameters: - * context: [in/out] - * The HMAC context to update. - * message_bits: [in] - * The final bits of the message, in the upper portion of the - * byte. (Use 0b###00000 instead of 0b00000### to input the - * three bits ###.) - * length: [in] - * The number of bits in message_bits, between 1 and 7. - * - * Returns: - * sha Error Code. - */ -int hmacFinalBits(HMACContext *context, - uint8_t bits, unsigned int bit_count) -{ - if (!context) return shaNull; - if (context->Corrupted) return context->Corrupted; - if (context->Computed) return context->Corrupted = shaStateError; - /* then final bits of datagram */ - return context->Corrupted = - USHAFinalBits(&context->shaContext, bits, bit_count); -} - -/* - * hmacResult - * - * Description: - * This function will return the N-byte message digest into the - * Message_Digest array provided by the caller. - * - * Parameters: - * context: [in/out] - * The context to use to calculate the HMAC hash. - * digest[ ]: [out] - * Where the digest is returned. - * NOTE 2: The length of the hash is determined by the value of - * whichSha that was passed to hmacReset(). - * - * Returns: - * sha Error Code. - * - */ -int hmacResult(HMACContext *context, uint8_t *digest) -{ - int ret; - if (!context) return shaNull; - if (context->Corrupted) return context->Corrupted; - if (context->Computed) return context->Corrupted = shaStateError; - - /* finish up 1st pass */ - /* (Use digest here as a temporary buffer.) */ - ret = - USHAResult(&context->shaContext, digest) || - - /* perform outer SHA */ - /* init context for 2nd pass */ - USHAReset(&context->shaContext, context->whichSha) || - - /* start with outer pad */ - USHAInput(&context->shaContext, context->k_opad, - context->blockSize) || - - /* then results of 1st hash */ - USHAInput(&context->shaContext, digest, context->hashSize) || - /* finish up 2nd pass */ - USHAResult(&context->shaContext, digest); - - context->Computed = 1; - return context->Corrupted = ret; -} diff --git a/nfq/crypto/sha-private.h b/nfq/crypto/sha-private.h deleted file mode 100644 index 4ceba0d6..00000000 --- a/nfq/crypto/sha-private.h +++ /dev/null @@ -1,25 +0,0 @@ -/************************ sha-private.h ************************/ -/***************** See RFC 6234 for details. *******************/ -#pragma once -/* - * These definitions are defined in FIPS 180-3, section 4.1. - * Ch() and Maj() are defined identically in sections 4.1.1, - * 4.1.2, and 4.1.3. - * - * The definitions used in FIPS 180-3 are as follows: - */ - -#ifndef USE_MODIFIED_MACROS -#define SHA_Ch(x,y,z) (((x) & (y)) ^ ((~(x)) & (z))) -#define SHA_Maj(x,y,z) (((x) & (y)) ^ ((x) & (z)) ^ ((y) & (z))) -#else /* USE_MODIFIED_MACROS */ -/* - * The following definitions are equivalent and potentially faster. - */ - -#define SHA_Ch(x, y, z) (((x) & ((y) ^ (z))) ^ (z)) -#define SHA_Maj(x, y, z) (((x) & ((y) | (z))) | ((y) & (z))) - -#endif /* USE_MODIFIED_MACROS */ - -#define SHA_Parity(x, y, z) ((x) ^ (y) ^ (z)) diff --git a/nfq/crypto/sha.h b/nfq/crypto/sha.h deleted file mode 100644 index 8b3a63b2..00000000 --- a/nfq/crypto/sha.h +++ /dev/null @@ -1,278 +0,0 @@ -/**************************** sha.h ****************************/ -/***************** See RFC 6234 for details. *******************/ -/* - Copyright (c) 2011 IETF Trust and the persons identified as - authors of the code. 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. - - Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the following - disclaimer in the documentation and/or other materials provided - with the distribution. - - Neither the name of Internet Society, IETF or IETF Trust, nor - the names of specific contributors, may be used to endorse or - promote products derived from this software without specific - prior written permission. - 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. -*/ - -#pragma once - -/* - * Description: - * This file implements the Secure Hash Algorithms - * as defined in the U.S. National Institute of Standards - * and Technology Federal Information Processing Standards - * Publication (FIPS PUB) 180-3 published in October 2008 - * and formerly defined in its predecessors, FIPS PUB 180-1 - * and FIP PUB 180-2. - * - * A combined document showing all algorithms is available at - * http://csrc.nist.gov/publications/fips/ - * fips180-3/fips180-3_final.pdf - * - * The five hashes are defined in these sizes: - * SHA-1 20 byte / 160 bit - * SHA-224 28 byte / 224 bit - * SHA-256 32 byte / 256 bit - * SHA-384 48 byte / 384 bit - * SHA-512 64 byte / 512 bit - * - * Compilation Note: - * These files may be compiled with two options: - * USE_32BIT_ONLY - use 32-bit arithmetic only, for systems - * without 64-bit integers - * - * USE_MODIFIED_MACROS - use alternate form of the SHA_Ch() - * and SHA_Maj() macros that are equivalent - * and potentially faster on many systems - * - */ - -#include -#include - -/* - * If you do not have the ISO standard stdint.h header file, then you - * must typedef the following: - * name meaning - * uint64_t unsigned 64-bit integer - * uint32_t unsigned 32-bit integer - * uint8_t unsigned 8-bit integer (i.e., unsigned char) - * int_least16_t integer of >= 16 bits - * - * See stdint-example.h - */ - -#ifndef _SHA_enum_ -#define _SHA_enum_ -/* - * All SHA functions return one of these values. - */ -enum { - shaSuccess = 0, - shaNull, /* Null pointer parameter */ - shaInputTooLong, /* input data too long */ - shaStateError, /* called Input after FinalBits or Result */ - shaBadParam /* passed a bad parameter */ -}; -#endif /* _SHA_enum_ */ - -/* - * These constants hold size information for each of the SHA - * hashing operations - */ -enum { - SHA1_Message_Block_Size = 64, SHA224_Message_Block_Size = 64, - SHA256_Message_Block_Size = 64, - USHA_Max_Message_Block_Size = SHA256_Message_Block_Size, - - SHA1HashSize = 20, SHA224HashSize = 28, SHA256HashSize = 32, - USHAMaxHashSize = SHA256HashSize, - - SHA1HashSizeBits = 160, SHA224HashSizeBits = 224, - SHA256HashSizeBits = 256, USHAMaxHashSizeBits = SHA256HashSizeBits -}; - -/* - * These constants are used in the USHA (Unified SHA) functions. - */ -typedef enum SHAversion { - SHA224, SHA256 -} SHAversion; - -/* - * This structure will hold context information for the SHA-256 - * hashing operation. - */ -typedef struct SHA256Context { - uint32_t Intermediate_Hash[SHA256HashSize/4]; /* Message Digest */ - - uint32_t Length_High; /* Message length in bits */ - uint32_t Length_Low; /* Message length in bits */ - - int_least16_t Message_Block_Index; /* Message_Block array index */ - /* 512-bit message blocks */ - uint8_t Message_Block[SHA256_Message_Block_Size]; - - int Computed; /* Is the hash computed? */ - int Corrupted; /* Cumulative corruption code */ -} SHA256Context; - -/* - * This structure will hold context information for the SHA-224 - * hashing operation. It uses the SHA-256 structure for computation. - */ -typedef struct SHA256Context SHA224Context; - -/* - * This structure holds context information for all SHA - * hashing operations. - */ -typedef struct USHAContext { - int whichSha; /* which SHA is being used */ - union { - SHA224Context sha224Context; SHA256Context sha256Context; - } ctx; - -} USHAContext; - -/* - * This structure will hold context information for the HMAC - * keyed-hashing operation. - */ -typedef struct HMACContext { - int whichSha; /* which SHA is being used */ - int hashSize; /* hash size of SHA being used */ - int blockSize; /* block size of SHA being used */ - USHAContext shaContext; /* SHA context */ - unsigned char k_opad[USHA_Max_Message_Block_Size]; - /* outer padding - key XORd with opad */ - int Computed; /* Is the MAC computed? */ - int Corrupted; /* Cumulative corruption code */ - -} HMACContext; - -/* - * This structure will hold context information for the HKDF - * extract-and-expand Key Derivation Functions. - */ -typedef struct HKDFContext { - int whichSha; /* which SHA is being used */ - HMACContext hmacContext; - int hashSize; /* hash size of SHA being used */ - unsigned char prk[USHAMaxHashSize]; - /* pseudo-random key - output of hkdfInput */ - int Computed; /* Is the key material computed? */ - int Corrupted; /* Cumulative corruption code */ -} HKDFContext; - -/* - * Function Prototypes - */ - - -/* SHA-224 */ -int SHA224Reset(SHA224Context *); -int SHA224Input(SHA224Context *, const uint8_t *bytes, - unsigned int bytecount); -int SHA224FinalBits(SHA224Context *, uint8_t bits, - unsigned int bit_count); -int SHA224Result(SHA224Context *, - uint8_t Message_Digest[SHA224HashSize]); - -/* SHA-256 */ -int SHA256Reset(SHA256Context *); -int SHA256Input(SHA256Context *, const uint8_t *bytes, - unsigned int bytecount); -int SHA256FinalBits(SHA256Context *, uint8_t bits, - unsigned int bit_count); -int SHA256Result(SHA256Context *, - uint8_t Message_Digest[SHA256HashSize]); - -/* Unified SHA functions, chosen by whichSha */ -int USHAReset(USHAContext *context, SHAversion whichSha); -int USHAInput(USHAContext *context, - const uint8_t *bytes, unsigned int bytecount); -int USHAFinalBits(USHAContext *context, - uint8_t bits, unsigned int bit_count); -int USHAResult(USHAContext *context, - uint8_t Message_Digest[USHAMaxHashSize]); -int USHABlockSize(enum SHAversion whichSha); -int USHAHashSize(enum SHAversion whichSha); - -/* - * HMAC Keyed-Hashing for Message Authentication, RFC 2104, - * for all SHAs. - * This interface allows a fixed-length text input to be used. - */ -int hmac(SHAversion whichSha, /* which SHA algorithm to use */ - const unsigned char *text, /* pointer to data stream */ - size_t text_len, /* length of data stream */ - const unsigned char *key, /* pointer to authentication key */ - size_t key_len, /* length of authentication key */ - uint8_t digest[USHAMaxHashSize]); /* caller digest to fill in */ - -/* - * HMAC Keyed-Hashing for Message Authentication, RFC 2104, - * for all SHAs. - * This interface allows any length of text input to be used. - */ -int hmacReset(HMACContext *context, enum SHAversion whichSha, - const unsigned char *key, size_t key_len); -int hmacInput(HMACContext *context, const unsigned char *text, - size_t text_len); -int hmacFinalBits(HMACContext *context, uint8_t bits, - unsigned int bit_count); -int hmacResult(HMACContext *context, - uint8_t digest[USHAMaxHashSize]); - - -/* - * HKDF HMAC-based Extract-and-Expand Key Derivation Function, - * RFC 5869, for all SHAs. - */ -int hkdf(SHAversion whichSha, - const unsigned char *salt, size_t salt_len, - const unsigned char *ikm, size_t ikm_len, - const unsigned char *info, size_t info_len, - uint8_t okm[ ], size_t okm_len); - -int hkdfExtract(SHAversion whichSha, const unsigned char *salt, - size_t salt_len, const unsigned char *ikm, - size_t ikm_len, uint8_t prk[USHAMaxHashSize]); -int hkdfExpand(SHAversion whichSha, const uint8_t prk[ ], - size_t prk_len, const unsigned char *info, - size_t info_len, uint8_t okm[ ], size_t okm_len); - -/* - * HKDF HMAC-based Extract-and-Expand Key Derivation Function, - * RFC 5869, for all SHAs. - * This interface allows any length of text input to be used. - */ -int hkdfReset(HKDFContext *context, enum SHAversion whichSha, - const unsigned char *salt, size_t salt_len); -int hkdfInput(HKDFContext *context, const unsigned char *ikm, - size_t ikm_len); -int hkdfFinalBits(HKDFContext *context, uint8_t ikm_bits, - unsigned int ikm_bit_count); -int hkdfResult(HKDFContext *context, - uint8_t prk[USHAMaxHashSize], - const unsigned char *info, size_t info_len, - uint8_t okm[USHAMaxHashSize], size_t okm_len); diff --git a/nfq/crypto/sha224-256.c b/nfq/crypto/sha224-256.c deleted file mode 100644 index 2c9bc9c1..00000000 --- a/nfq/crypto/sha224-256.c +++ /dev/null @@ -1,581 +0,0 @@ -/************************* sha224-256.c ************************/ -/***************** See RFC 6234 for details. *******************/ -/* Copyright (c) 2011 IETF Trust and the persons identified as */ -/* authors of the code. All rights reserved. */ -/* See sha.h for terms of use and redistribution. */ - -/* - * Description: - * This file implements the Secure Hash Algorithms SHA-224 and - * SHA-256 as defined in the U.S. National Institute of Standards - * and Technology Federal Information Processing Standards - * Publication (FIPS PUB) 180-3 published in October 2008 - * and formerly defined in its predecessors, FIPS PUB 180-1 - * and FIP PUB 180-2. - * - * A combined document showing all algorithms is available at - * http://csrc.nist.gov/publications/fips/ - * fips180-3/fips180-3_final.pdf - * - * The SHA-224 and SHA-256 algorithms produce 224-bit and 256-bit - * message digests for a given data stream. It should take about - * 2**n steps to find a message with the same digest as a given - * message and 2**(n/2) to find any two messages with the same - * digest, when n is the digest size in bits. Therefore, this - * algorithm can serve as a means of providing a - * "fingerprint" for a message. - * - * Portability Issues: - * SHA-224 and SHA-256 are defined in terms of 32-bit "words". - * This code uses (included via "sha.h") to define 32- - * and 8-bit unsigned integer types. If your C compiler does not - * support 32-bit unsigned integers, this code is not - * appropriate. - * - * Caveats: - * SHA-224 and SHA-256 are designed to work with messages less - * than 2^64 bits long. This implementation uses SHA224/256Input() - * to hash the bits that are a multiple of the size of an 8-bit - * octet, and then optionally uses SHA224/256FinalBits() - * to hash the final few bits of the input. - */ - -#include "sha.h" -#include "sha-private.h" - -/* Define the SHA shift, rotate left, and rotate right macros */ -#define SHA256_SHR(bits,word) ((word) >> (bits)) -#define SHA256_ROTL(bits,word) \ - (((word) << (bits)) | ((word) >> (32-(bits)))) -#define SHA256_ROTR(bits,word) \ - (((word) >> (bits)) | ((word) << (32-(bits)))) - -/* Define the SHA SIGMA and sigma macros */ -#define SHA256_SIGMA0(word) \ - (SHA256_ROTR( 2,word) ^ SHA256_ROTR(13,word) ^ SHA256_ROTR(22,word)) -#define SHA256_SIGMA1(word) \ - (SHA256_ROTR( 6,word) ^ SHA256_ROTR(11,word) ^ SHA256_ROTR(25,word)) -#define SHA256_sigma0(word) \ - (SHA256_ROTR( 7,word) ^ SHA256_ROTR(18,word) ^ SHA256_SHR( 3,word)) -#define SHA256_sigma1(word) \ - (SHA256_ROTR(17,word) ^ SHA256_ROTR(19,word) ^ SHA256_SHR(10,word)) - -/* - * Add "length" to the length. - * Set Corrupted when overflow has occurred. - */ -static uint32_t addTemp; -#define SHA224_256AddLength(context, length) \ - (addTemp = (context)->Length_Low, (context)->Corrupted = \ - (((context)->Length_Low += (length)) < addTemp) && \ - (++(context)->Length_High == 0) ? shaInputTooLong : \ - (context)->Corrupted ) - -/* Local Function Prototypes */ -static int SHA224_256Reset(SHA256Context *context, uint32_t *H0); -static void SHA224_256ProcessMessageBlock(SHA256Context *context); -static void SHA224_256Finalize(SHA256Context *context, - uint8_t Pad_Byte); -static void SHA224_256PadMessage(SHA256Context *context, - uint8_t Pad_Byte); -static int SHA224_256ResultN(SHA256Context *context, - uint8_t Message_Digest[ ], int HashSize); - -/* Initial Hash Values: FIPS 180-3 section 5.3.2 */ -static uint32_t SHA224_H0[SHA256HashSize/4] = { - 0xC1059ED8, 0x367CD507, 0x3070DD17, 0xF70E5939, - 0xFFC00B31, 0x68581511, 0x64F98FA7, 0xBEFA4FA4 -}; - -/* Initial Hash Values: FIPS 180-3 section 5.3.3 */ -static uint32_t SHA256_H0[SHA256HashSize/4] = { - 0x6A09E667, 0xBB67AE85, 0x3C6EF372, 0xA54FF53A, - 0x510E527F, 0x9B05688C, 0x1F83D9AB, 0x5BE0CD19 -}; - -/* - * SHA224Reset - * - * Description: - * This function will initialize the SHA224Context in preparation - * for computing a new SHA224 message digest. - * - * Parameters: - * context: [in/out] - * The context to reset. - * - * Returns: - * sha Error Code. - */ -int SHA224Reset(SHA224Context *context) -{ - return SHA224_256Reset(context, SHA224_H0); -} - -/* - * SHA224Input - * - * Description: - * This function accepts an array of octets as the next portion - * of the message. - * - * Parameters: - * context: [in/out] - * The SHA context to update. - * message_array[ ]: [in] - * An array of octets representing the next portion of - * the message. - * length: [in] - * The length of the message in message_array. - * - * Returns: - * sha Error Code. - * - */ -int SHA224Input(SHA224Context *context, const uint8_t *message_array, - unsigned int length) -{ - return SHA256Input(context, message_array, length); -} - -/* - * SHA224FinalBits - * - * Description: - * This function will add in any final bits of the message. - * - * Parameters: - * context: [in/out] - * The SHA context to update. - * message_bits: [in] - * The final bits of the message, in the upper portion of the - * byte. (Use 0b###00000 instead of 0b00000### to input the - * three bits ###.) - * length: [in] - * The number of bits in message_bits, between 1 and 7. - * - * Returns: - * sha Error Code. - */ -int SHA224FinalBits(SHA224Context *context, - uint8_t message_bits, unsigned int length) -{ - return SHA256FinalBits(context, message_bits, length); -} - -/* - * SHA224Result - * - * Description: - * This function will return the 224-bit message digest - * into the Message_Digest array provided by the caller. - * NOTE: - * The first octet of hash is stored in the element with index 0, - * the last octet of hash in the element with index 27. - * - * Parameters: - * context: [in/out] - * The context to use to calculate the SHA hash. - * Message_Digest[ ]: [out] - * Where the digest is returned. - * - * Returns: - * sha Error Code. - */ -int SHA224Result(SHA224Context *context, - uint8_t Message_Digest[SHA224HashSize]) -{ - return SHA224_256ResultN(context, Message_Digest, SHA224HashSize); -} - -/* - * SHA256Reset - * - * Description: - * This function will initialize the SHA256Context in preparation - * for computing a new SHA256 message digest. - * - * Parameters: - * context: [in/out] - * The context to reset. - * - * Returns: - * sha Error Code. - */ -int SHA256Reset(SHA256Context *context) -{ - return SHA224_256Reset(context, SHA256_H0); -} - -/* - * SHA256Input - * - * Description: - * This function accepts an array of octets as the next portion - * of the message. - * - * Parameters: - * context: [in/out] - * The SHA context to update. - * message_array[ ]: [in] - * An array of octets representing the next portion of - * the message. - * length: [in] - * The length of the message in message_array. - * - * Returns: - * sha Error Code. - */ -int SHA256Input(SHA256Context *context, const uint8_t *message_array, - unsigned int length) -{ - if (!context) return shaNull; - if (!length) return shaSuccess; - if (!message_array) return shaNull; - if (context->Computed) return context->Corrupted = shaStateError; - if (context->Corrupted) return context->Corrupted; - - while (length--) { - context->Message_Block[context->Message_Block_Index++] = - *message_array; - - if ((SHA224_256AddLength(context, 8) == shaSuccess) && - (context->Message_Block_Index == SHA256_Message_Block_Size)) - SHA224_256ProcessMessageBlock(context); - - message_array++; - } - - return context->Corrupted; - -} - -/* - * SHA256FinalBits - * - * Description: - * This function will add in any final bits of the message. - * - * Parameters: - * context: [in/out] - * The SHA context to update. - * message_bits: [in] - * The final bits of the message, in the upper portion of the - * byte. (Use 0b###00000 instead of 0b00000### to input the - * three bits ###.) - * length: [in] - * The number of bits in message_bits, between 1 and 7. - * - * Returns: - * sha Error Code. - */ -int SHA256FinalBits(SHA256Context *context, - uint8_t message_bits, unsigned int length) -{ - static uint8_t masks[8] = { - /* 0 0b00000000 */ 0x00, /* 1 0b10000000 */ 0x80, - /* 2 0b11000000 */ 0xC0, /* 3 0b11100000 */ 0xE0, - /* 4 0b11110000 */ 0xF0, /* 5 0b11111000 */ 0xF8, - /* 6 0b11111100 */ 0xFC, /* 7 0b11111110 */ 0xFE - }; - static uint8_t markbit[8] = { - /* 0 0b10000000 */ 0x80, /* 1 0b01000000 */ 0x40, - /* 2 0b00100000 */ 0x20, /* 3 0b00010000 */ 0x10, - /* 4 0b00001000 */ 0x08, /* 5 0b00000100 */ 0x04, - /* 6 0b00000010 */ 0x02, /* 7 0b00000001 */ 0x01 - }; - - if (!context) return shaNull; - if (!length) return shaSuccess; - if (context->Corrupted) return context->Corrupted; - if (context->Computed) return context->Corrupted = shaStateError; - if (length >= 8) return context->Corrupted = shaBadParam; - - SHA224_256AddLength(context, length); - SHA224_256Finalize(context, (uint8_t) - ((message_bits & masks[length]) | markbit[length])); - - return context->Corrupted; -} - -/* - * SHA256Result - * - * Description: - * This function will return the 256-bit message digest - * into the Message_Digest array provided by the caller. - * NOTE: - * The first octet of hash is stored in the element with index 0, - * the last octet of hash in the element with index 31. - * - * Parameters: - * context: [in/out] - * The context to use to calculate the SHA hash. - * Message_Digest[ ]: [out] - * Where the digest is returned. - * - * Returns: - * sha Error Code. - */ -int SHA256Result(SHA256Context *context, - uint8_t Message_Digest[SHA256HashSize]) -{ - return SHA224_256ResultN(context, Message_Digest, SHA256HashSize); -} - -/* - * SHA224_256Reset - * - * Description: - * This helper function will initialize the SHA256Context in - * preparation for computing a new SHA-224 or SHA-256 message digest. - * - * Parameters: - * context: [in/out] - * The context to reset. - * H0[ ]: [in] - * The initial hash value array to use. - * - * Returns: - * sha Error Code. - */ -static int SHA224_256Reset(SHA256Context *context, uint32_t *H0) -{ - if (!context) return shaNull; - - context->Length_High = context->Length_Low = 0; - context->Message_Block_Index = 0; - - context->Intermediate_Hash[0] = H0[0]; - context->Intermediate_Hash[1] = H0[1]; - context->Intermediate_Hash[2] = H0[2]; - context->Intermediate_Hash[3] = H0[3]; - context->Intermediate_Hash[4] = H0[4]; - context->Intermediate_Hash[5] = H0[5]; - context->Intermediate_Hash[6] = H0[6]; - context->Intermediate_Hash[7] = H0[7]; - - context->Computed = 0; - context->Corrupted = shaSuccess; - - return shaSuccess; -} - -/* - * SHA224_256ProcessMessageBlock - * - * Description: - * This helper function will process the next 512 bits of the - * message stored in the Message_Block array. - * - * Parameters: - * context: [in/out] - * The SHA context to update. - * - * Returns: - * Nothing. - * - * Comments: - * Many of the variable names in this code, especially the - * single character names, were used because those were the - * names used in the Secure Hash Standard. - */ -static void SHA224_256ProcessMessageBlock(SHA256Context *context) -{ - /* Constants defined in FIPS 180-3, section 4.2.2 */ - static const uint32_t K[64] = { - 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, - 0x59f111f1, 0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, - 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, - 0xc19bf174, 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, - 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, 0x983e5152, - 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, - 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, - 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, - 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, - 0xd6990624, 0xf40e3585, 0x106aa070, 0x19a4c116, 0x1e376c08, - 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, - 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, - 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 - }; - int t, t4; /* Loop counter */ - uint32_t temp1, temp2; /* Temporary word value */ - uint32_t W[64]; /* Word sequence */ - uint32_t A, B, C, D, E, F, G, H; /* Word buffers */ - - /* - * Initialize the first 16 words in the array W - */ - for (t = t4 = 0; t < 16; t++, t4 += 4) - W[t] = (((uint32_t)context->Message_Block[t4]) << 24) | - (((uint32_t)context->Message_Block[t4 + 1]) << 16) | - (((uint32_t)context->Message_Block[t4 + 2]) << 8) | - (((uint32_t)context->Message_Block[t4 + 3])); - for (t = 16; t < 64; t++) - W[t] = SHA256_sigma1(W[t-2]) + W[t-7] + - SHA256_sigma0(W[t-15]) + W[t-16]; - - A = context->Intermediate_Hash[0]; - B = context->Intermediate_Hash[1]; - C = context->Intermediate_Hash[2]; - D = context->Intermediate_Hash[3]; - E = context->Intermediate_Hash[4]; - F = context->Intermediate_Hash[5]; - G = context->Intermediate_Hash[6]; - H = context->Intermediate_Hash[7]; - - for (t = 0; t < 64; t++) { - temp1 = H + SHA256_SIGMA1(E) + SHA_Ch(E,F,G) + K[t] + W[t]; - temp2 = SHA256_SIGMA0(A) + SHA_Maj(A,B,C); - H = G; - G = F; - F = E; - E = D + temp1; - D = C; - C = B; - B = A; - A = temp1 + temp2; - } - - context->Intermediate_Hash[0] += A; - context->Intermediate_Hash[1] += B; - context->Intermediate_Hash[2] += C; - context->Intermediate_Hash[3] += D; - context->Intermediate_Hash[4] += E; - context->Intermediate_Hash[5] += F; - context->Intermediate_Hash[6] += G; - context->Intermediate_Hash[7] += H; - - context->Message_Block_Index = 0; -} - -/* - * SHA224_256Finalize - * - * Description: - * This helper function finishes off the digest calculations. - * - * Parameters: - * context: [in/out] - * The SHA context to update. - * Pad_Byte: [in] - * The last byte to add to the message block before the 0-padding - * and length. This will contain the last bits of the message - * followed by another single bit. If the message was an - * exact multiple of 8-bits long, Pad_Byte will be 0x80. - * - * Returns: - * sha Error Code. - */ -static void SHA224_256Finalize(SHA256Context *context, - uint8_t Pad_Byte) -{ - int i; - SHA224_256PadMessage(context, Pad_Byte); - /* message may be sensitive, so clear it out */ - for (i = 0; i < SHA256_Message_Block_Size; ++i) - context->Message_Block[i] = 0; - context->Length_High = 0; /* and clear length */ - context->Length_Low = 0; - context->Computed = 1; -} - -/* - * SHA224_256PadMessage - * - * Description: - * According to the standard, the message must be padded to the next - * even multiple of 512 bits. The first padding bit must be a '1'. - * The last 64 bits represent the length of the original message. - * All bits in between should be 0. This helper function will pad - * the message according to those rules by filling the - * Message_Block array accordingly. When it returns, it can be - * assumed that the message digest has been computed. - * - * Parameters: - * context: [in/out] - * The context to pad. - * Pad_Byte: [in] - * The last byte to add to the message block before the 0-padding - * and length. This will contain the last bits of the message - * followed by another single bit. If the message was an - * exact multiple of 8-bits long, Pad_Byte will be 0x80. - * - * Returns: - * Nothing. - */ -static void SHA224_256PadMessage(SHA256Context *context, - uint8_t Pad_Byte) -{ - /* - * Check to see if the current message block is too small to hold - * the initial padding bits and length. If so, we will pad the - * block, process it, and then continue padding into a second - * block. - */ - if (context->Message_Block_Index >= (SHA256_Message_Block_Size-8)) { - context->Message_Block[context->Message_Block_Index++] = Pad_Byte; - while (context->Message_Block_Index < SHA256_Message_Block_Size) - context->Message_Block[context->Message_Block_Index++] = 0; - SHA224_256ProcessMessageBlock(context); - } else - context->Message_Block[context->Message_Block_Index++] = Pad_Byte; - - while (context->Message_Block_Index < (SHA256_Message_Block_Size-8)) - context->Message_Block[context->Message_Block_Index++] = 0; - - /* - * Store the message length as the last 8 octets - */ - context->Message_Block[56] = (uint8_t)(context->Length_High >> 24); - context->Message_Block[57] = (uint8_t)(context->Length_High >> 16); - context->Message_Block[58] = (uint8_t)(context->Length_High >> 8); - context->Message_Block[59] = (uint8_t)(context->Length_High); - context->Message_Block[60] = (uint8_t)(context->Length_Low >> 24); - context->Message_Block[61] = (uint8_t)(context->Length_Low >> 16); - context->Message_Block[62] = (uint8_t)(context->Length_Low >> 8); - context->Message_Block[63] = (uint8_t)(context->Length_Low); - - SHA224_256ProcessMessageBlock(context); -} - -/* - * SHA224_256ResultN - * - * Description: - * This helper function will return the 224-bit or 256-bit message - * digest into the Message_Digest array provided by the caller. - * NOTE: - * The first octet of hash is stored in the element with index 0, - * the last octet of hash in the element with index 27/31. - * - * Parameters: - * context: [in/out] - * The context to use to calculate the SHA hash. - * Message_Digest[ ]: [out] - * Where the digest is returned. - * HashSize: [in] - * The size of the hash, either 28 or 32. - * - * Returns: - * sha Error Code. - */ -static int SHA224_256ResultN(SHA256Context *context, - uint8_t Message_Digest[ ], int HashSize) -{ - int i; - - if (!context) return shaNull; - if (!Message_Digest) return shaNull; - if (context->Corrupted) return context->Corrupted; - - if (!context->Computed) - SHA224_256Finalize(context, 0x80); - - for (i = 0; i < HashSize; ++i) - Message_Digest[i] = (uint8_t) - (context->Intermediate_Hash[i>>2] >> 8 * ( 3 - ( i & 0x03 ) )); - - return shaSuccess; -} - diff --git a/nfq/crypto/usha.c b/nfq/crypto/usha.c deleted file mode 100644 index 861b4d0d..00000000 --- a/nfq/crypto/usha.c +++ /dev/null @@ -1,191 +0,0 @@ -/**************************** usha.c ***************************/ -/***************** See RFC 6234 for details. *******************/ -/* Copyright (c) 2011 IETF Trust and the persons identified as */ -/* authors of the code. All rights reserved. */ -/* See sha.h for terms of use and redistribution. */ - -/* - * Description: - * This file implements a unified interface to the SHA algorithms. - */ - -#include "sha.h" - -/* - * USHAReset - * - * Description: - * This function will initialize the SHA Context in preparation - * for computing a new SHA message digest. - * - * Parameters: - * context: [in/out] - * The context to reset. - * whichSha: [in] - * Selects which SHA reset to call - * - * Returns: - * sha Error Code. - * - */ -int USHAReset(USHAContext *context, enum SHAversion whichSha) -{ - if (!context) return shaNull; - context->whichSha = whichSha; - switch (whichSha) { - case SHA224: return SHA224Reset((SHA224Context*)&context->ctx); - case SHA256: return SHA256Reset((SHA256Context*)&context->ctx); - default: return shaBadParam; - } -} - -/* - * USHAInput - * - * Description: - * This function accepts an array of octets as the next portion - * of the message. - * - * Parameters: - * context: [in/out] - * The SHA context to update. - * message_array: [in] - * An array of octets representing the next portion of - * the message. - * length: [in] - * The length of the message in message_array. - * - * Returns: - * sha Error Code. - * - */ -int USHAInput(USHAContext *context, - const uint8_t *bytes, unsigned int bytecount) -{ - if (!context) return shaNull; - switch (context->whichSha) { - case SHA224: - return SHA224Input((SHA224Context*)&context->ctx, bytes, - bytecount); - case SHA256: - return SHA256Input((SHA256Context*)&context->ctx, bytes, - bytecount); - default: return shaBadParam; - } -} - -/* - * USHAFinalBits - * - * Description: - * This function will add in any final bits of the message. - * - * Parameters: - * context: [in/out] - * The SHA context to update. - * message_bits: [in] - * The final bits of the message, in the upper portion of the - * byte. (Use 0b###00000 instead of 0b00000### to input the - * three bits ###.) - * length: [in] - * The number of bits in message_bits, between 1 and 7. - * - * Returns: - * sha Error Code. - */ -int USHAFinalBits(USHAContext *context, - uint8_t bits, unsigned int bit_count) -{ - if (!context) return shaNull; - switch (context->whichSha) { - case SHA224: - return SHA224FinalBits((SHA224Context*)&context->ctx, bits, - bit_count); - case SHA256: - return SHA256FinalBits((SHA256Context*)&context->ctx, bits, - bit_count); - default: return shaBadParam; - } -} - -/* - * USHAResult - * - * Description: - * This function will return the message digest of the appropriate - * bit size, as returned by USHAHashSizeBits(whichSHA) for the - * 'whichSHA' value used in the preceeding call to USHAReset, - * into the Message_Digest array provided by the caller. - * - * Parameters: - * context: [in/out] - * The context to use to calculate the SHA-1 hash. - * Message_Digest: [out] - * Where the digest is returned. - * - * Returns: - * sha Error Code. - * - */ -int USHAResult(USHAContext *context, - uint8_t Message_Digest[USHAMaxHashSize]) -{ - if (!context) return shaNull; - switch (context->whichSha) { - case SHA224: - return SHA224Result((SHA224Context*)&context->ctx, - Message_Digest); - case SHA256: - return SHA256Result((SHA256Context*)&context->ctx, - Message_Digest); - default: return shaBadParam; - } -} - -/* - * USHABlockSize - * - * Description: - * This function will return the blocksize for the given SHA - * algorithm. - * - * Parameters: - * whichSha: - * which SHA algorithm to query - * - * Returns: - * block size - * - */ -int USHABlockSize(enum SHAversion whichSha) -{ - switch (whichSha) { - case SHA224: return SHA224_Message_Block_Size; - default: - case SHA256: return SHA256_Message_Block_Size; - } -} - -/* - * USHAHashSize - * - * Description: - * This function will return the hashsize for the given SHA - * algorithm. - * - * Parameters: - * whichSha: - * which SHA algorithm to query - * - * Returns: - * hash size - * - */ -int USHAHashSize(enum SHAversion whichSha) -{ - switch (whichSha) { - case SHA224: return SHA224HashSize; - default: - case SHA256: return SHA256HashSize; - } -} diff --git a/nfq/darkmagic.c b/nfq/darkmagic.c deleted file mode 100644 index b45b3233..00000000 --- a/nfq/darkmagic.c +++ /dev/null @@ -1,2349 +0,0 @@ -#define _GNU_SOURCE - -#include -#include -#include -#include -#include -#include -#include -#include - -#ifndef IP_NODEFRAG -// for very old toolchains -#define IP_NODEFRAG 22 -#endif - -#include "darkmagic.h" -#include "helpers.h" -#include "params.h" -#include "nfqws.h" - -#ifdef __CYGWIN__ -#include -#include - -#ifndef ERROR_INVALID_IMAGE_HASH -#define ERROR_INVALID_IMAGE_HASH __MSABI_LONG(577) -#endif - -#endif - -#ifdef __linux__ -#include -#include -#include -#include -#define _LINUX_IF_H // prevent conflict between linux/if.h and net/if.h in old gcc 4.x -#include -#include -#endif - -uint32_t net32_add(uint32_t netorder_value, uint32_t cpuorder_increment) -{ - return htonl(ntohl(netorder_value)+cpuorder_increment); -} -uint32_t net16_add(uint16_t netorder_value, uint16_t cpuorder_increment) -{ - return htons(ntohs(netorder_value)+cpuorder_increment); -} - -bool ip_has_df(const struct ip *ip) -{ - return ip && !!(ntohs(ip->ip_off) & IP_DF); -} - -uint8_t *tcp_find_option(struct tcphdr *tcp, uint8_t kind) -{ - uint8_t *t = (uint8_t*)(tcp+1); - uint8_t *end = (uint8_t*)tcp + (tcp->th_off<<2); - while(t=end || t[1]<2 || (t+t[1])>end) - return NULL; - if (*t==kind) - return t; - t+=t[1]; - break; - } - } - return NULL; -} -uint32_t *tcp_find_timestamps(struct tcphdr *tcp) -{ - uint8_t *t = tcp_find_option(tcp,8); - return (t && t[1]==10) ? (uint32_t*)(t+2) : NULL; -} -uint8_t tcp_find_scale_factor(const struct tcphdr *tcp) -{ - uint8_t *scale = tcp_find_option((struct tcphdr*)tcp,3); // tcp option 3 - scale factor - if (scale && scale[1]==3) return scale[2]; - return SCALE_NONE; -} -bool tcp_has_fastopen(const struct tcphdr *tcp) -{ - uint8_t *opt; - // new style RFC7413 - opt = tcp_find_option((struct tcphdr*)tcp, 34); - if (opt) return true; - // old style RFC6994 - opt = tcp_find_option((struct tcphdr*)tcp, 254); - return opt && opt[1]>=4 && opt[2]==0xF9 && opt[3]==0x89; -} -uint16_t tcp_find_mss(struct tcphdr *tcp) -{ - uint8_t *t = tcp_find_option(tcp,2); - return (t && t[1]==4) ? *(uint16_t*)(t+2) : 0; -} -bool tcp_has_sack(struct tcphdr *tcp) -{ - uint8_t *t = tcp_find_option(tcp,4); - return !!t; -} - -// n prefix (nsport, nwsize) means network byte order -static void fill_tcphdr( - struct tcphdr *tcp, uint32_t fooling, uint16_t tcp_flags, - bool sack, - uint16_t nmss, - uint32_t nseq, uint32_t nack_seq, - uint16_t nsport, uint16_t ndport, - uint16_t nwsize, uint8_t scale_factor, - uint32_t *timestamps, - uint32_t ts_increment, - uint32_t badseq_increment, - uint32_t badseq_ack_increment, - uint16_t data_len) -{ - char *tcpopt = (char*)(tcp+1); - uint8_t t=0; - - memset(tcp,0,sizeof(*tcp)); - tcp->th_sport = nsport; - tcp->th_dport = ndport; - if (fooling & FOOL_BADSEQ) - { - tcp->th_seq = net32_add(nseq,badseq_increment); - tcp->th_ack = net32_add(nack_seq,badseq_ack_increment); - } - else - { - tcp->th_seq = nseq; - tcp->th_ack = nack_seq; - } - tcp->th_off = 5; - if ((fooling & FOOL_DATANOACK) && !(tcp_flags & (TH_SYN|TH_RST)) && data_len) - tcp_flags &= ~TH_ACK; - tcp->th_flags = (uint8_t)tcp_flags; - tcp->th_x2 = (tcp_flags>>8) & 0xF; - tcp->th_win = nwsize; - if (nmss) - { - tcpopt[t++] = 2; // kind - tcpopt[t++] = 4; // len - *(uint16_t*)(tcpopt+t) = nmss; - t+=2; - } - if (sack) - { - tcpopt[t++] = 4; // kind - tcpopt[t++] = 2; // len - } - if (fooling & FOOL_MD5SIG) - { - tcpopt[t] = 19; // kind - tcpopt[t+1] = 18; // len - *(uint32_t*)(tcpopt+t+2)=random(); - *(uint32_t*)(tcpopt+t+6)=random(); - *(uint32_t*)(tcpopt+t+10)=random(); - *(uint32_t*)(tcpopt+t+14)=random(); - t+=18; - } - if (timestamps) - { - tcpopt[t] = 8; // kind - tcpopt[t+1] = 10; // len - memcpy(tcpopt+t+2,timestamps,8); - // forge TSval, keep TSecr - if (fooling & FOOL_TS) *(uint32_t*)(tcpopt+t+2) = net32_add(*(uint32_t*)(tcpopt+t+2),ts_increment); - t+=10; - } - if (scale_factor!=SCALE_NONE) - { - tcpopt[t++]=3; - tcpopt[t++]=3; - tcpopt[t++]=scale_factor; - } - while (t&3) tcpopt[t++]=1; // noop - tcp->th_off += t>>2; - tcp->th_sum = 0; -} -static uint16_t tcpopt_len(bool sack, bool mss, uint32_t fooling, const uint32_t *timestamps, uint8_t scale_factor) -{ - uint16_t t=0; - if (sack) t+=2; - if (mss) t+=4; - if (fooling & FOOL_MD5SIG) t+=18; - if ((fooling & FOOL_TS) || timestamps) t+=10; - if (scale_factor!=SCALE_NONE) t+=3; - return (t+3)&~3; -} - -// n prefix (nsport, nwsize) means network byte order -static void fill_udphdr(struct udphdr *udp, uint16_t nsport, uint16_t ndport, uint16_t len_payload) -{ - udp->uh_sport = nsport; - udp->uh_dport = ndport; - udp->uh_ulen = htons(len_payload+sizeof(struct udphdr)); - udp->uh_sum = 0; -} - -static void fill_iphdr(struct ip *ip, const struct in_addr *src, const struct in_addr *dst, uint16_t pktlen, uint8_t proto, bool DF, uint8_t ttl, uint8_t tos, uint16_t ip_id) -{ - ip->ip_tos = tos; - ip->ip_sum = 0; - ip->ip_off = DF ? htons(IP_DF) : 0; - ip->ip_v = 4; - ip->ip_hl = 5; - ip->ip_len = htons(pktlen); - ip->ip_id = ip_id; - ip->ip_ttl = ttl; - ip->ip_p = proto; - ip->ip_src = *src; - ip->ip_dst = *dst; -} -static void fill_ip6hdr(struct ip6_hdr *ip6, const struct in6_addr *src, const struct in6_addr *dst, uint16_t payloadlen, uint8_t proto, uint8_t ttl, uint32_t flow_label) -{ - ip6->ip6_ctlun.ip6_un1.ip6_un1_flow = htonl(ntohl(flow_label) & 0x0FFFFFFF | 0x60000000); - ip6->ip6_ctlun.ip6_un1.ip6_un1_plen = htons(payloadlen); - ip6->ip6_ctlun.ip6_un1.ip6_un1_nxt = proto; - ip6->ip6_ctlun.ip6_un1.ip6_un1_hlim = ttl; - ip6->ip6_src = *src; - ip6->ip6_dst = *dst; -} - -bool prepare_tcp_segment4( - const struct sockaddr_in *src, const struct sockaddr_in *dst, - uint16_t tcp_flags, - bool sack, - uint16_t nmss, - uint32_t nseq, uint32_t nack_seq, - uint16_t nwsize, - uint8_t scale_factor, - uint32_t *timestamps, - bool DF, - uint8_t ttl, - uint8_t tos, - uint16_t ip_id, - uint32_t fooling, - uint32_t ts_increment, - uint32_t badseq_increment, - uint32_t badseq_ack_increment, - const void *data, uint16_t len, - uint8_t *buf, size_t *buflen) -{ - uint16_t tcpoptlen = tcpopt_len(sack,!!nmss,fooling,timestamps,scale_factor); - uint16_t ip_payload_len = sizeof(struct tcphdr) + tcpoptlen + len; - uint16_t pktlen = sizeof(struct ip) + ip_payload_len; - if (pktlen>*buflen) return false; - - struct ip *ip = (struct ip*)buf; - struct tcphdr *tcp = (struct tcphdr*)(ip+1); - uint8_t *payload = (uint8_t*)(tcp+1)+tcpoptlen; - - fill_iphdr(ip, &src->sin_addr, &dst->sin_addr, pktlen, IPPROTO_TCP, DF, ttl, tos, ip_id); - fill_tcphdr(tcp,fooling,tcp_flags,sack,nmss,nseq,nack_seq,src->sin_port,dst->sin_port,nwsize,scale_factor,timestamps,ts_increment,badseq_increment,badseq_ack_increment,len); - - memcpy(payload,data,len); - tcp4_fix_checksum(tcp,ip_payload_len,&ip->ip_src,&ip->ip_dst); - if (fooling & FOOL_BADSUM) tcp->th_sum^=(uint16_t)(1+random()%0xFFFF); - - *buflen = pktlen; - return true; -} - -bool prepare_tcp_segment6( - const struct sockaddr_in6 *src, const struct sockaddr_in6 *dst, - uint16_t tcp_flags, - bool sack, - uint16_t nmss, - uint32_t nseq, uint32_t nack_seq, - uint16_t nwsize, - uint8_t scale_factor, - uint32_t *timestamps, - uint8_t ttl, - uint32_t flow_label, - uint32_t fooling, - uint32_t ts_increment, - uint32_t badseq_increment, - uint32_t badseq_ack_increment, - const void *data, uint16_t len, - uint8_t *buf, size_t *buflen) -{ - uint16_t tcpoptlen = tcpopt_len(sack,!!nmss,fooling,timestamps,scale_factor); - uint16_t transport_payload_len = sizeof(struct tcphdr) + tcpoptlen + len; - uint16_t ip_payload_len = transport_payload_len + - 8*!!((fooling & (FOOL_HOPBYHOP|FOOL_HOPBYHOP2))==FOOL_HOPBYHOP) + - 16*!!(fooling & FOOL_HOPBYHOP2) + - 8*!!(fooling & FOOL_DESTOPT) + - 8*!!(fooling & FOOL_IPFRAG1); - uint16_t pktlen = sizeof(struct ip6_hdr) + ip_payload_len; - if (pktlen>*buflen) return false; - - struct ip6_hdr *ip6 = (struct ip6_hdr*)buf; - struct tcphdr *tcp = (struct tcphdr*)(ip6+1); - uint8_t proto = IPPROTO_TCP, *nexttype = NULL; - - if (fooling & (FOOL_HOPBYHOP|FOOL_HOPBYHOP2)) - { - struct ip6_hbh *hbh = (struct ip6_hbh*)tcp; - tcp = (struct tcphdr*)((uint8_t*)tcp+8); - memset(hbh,0,8); - // extra HOPBYHOP header. standard violation - if (fooling & FOOL_HOPBYHOP2) - { - hbh = (struct ip6_hbh*)tcp; - tcp = (struct tcphdr*)((uint8_t*)tcp+8); - memset(hbh,0,8); - } - hbh->ip6h_nxt = IPPROTO_TCP; - nexttype = &hbh->ip6h_nxt; - proto = IPPROTO_HOPOPTS; - } - if (fooling & FOOL_DESTOPT) - { - struct ip6_dest *dest = (struct ip6_dest*)tcp; - tcp = (struct tcphdr*)((uint8_t*)tcp+8); - memset(dest,0,8); - dest->ip6d_nxt = IPPROTO_TCP; - if (nexttype) - *nexttype = IPPROTO_DSTOPTS; - else - proto = IPPROTO_DSTOPTS; - nexttype = &dest->ip6d_nxt; - } - if (fooling & FOOL_IPFRAG1) - { - struct ip6_frag *frag = (struct ip6_frag*)tcp; - tcp = (struct tcphdr*)((uint8_t*)tcp+sizeof(struct ip6_frag)); - frag->ip6f_nxt = IPPROTO_TCP; - frag->ip6f_ident = htonl(1+random()%0xFFFFFFFF); - frag->ip6f_reserved = 0; - frag->ip6f_offlg = 0; - if (nexttype) - *nexttype = IPPROTO_FRAGMENT; - else - proto = IPPROTO_FRAGMENT; - } - - uint8_t *payload = (uint8_t*)(tcp+1)+tcpoptlen; - - fill_ip6hdr(ip6, &src->sin6_addr, &dst->sin6_addr, ip_payload_len, proto, ttl, flow_label); - fill_tcphdr(tcp,fooling,tcp_flags,sack,nmss,nseq,nack_seq,src->sin6_port,dst->sin6_port,nwsize,scale_factor,timestamps,ts_increment,badseq_increment,badseq_ack_increment,len); - - memcpy(payload,data,len); - tcp6_fix_checksum(tcp,transport_payload_len,&ip6->ip6_src,&ip6->ip6_dst); - if (fooling & FOOL_BADSUM) tcp->th_sum^=(1+random()%0xFFFF); - - *buflen = pktlen; - return true; -} - -bool prepare_tcp_segment( - const struct sockaddr *src, const struct sockaddr *dst, - uint16_t tcp_flags, - bool sack, - uint16_t nmss, - uint32_t nseq, uint32_t nack_seq, - uint16_t nwsize, - uint8_t scale_factor, - uint32_t *timestamps, - bool DF, - uint8_t ttl, - uint8_t tos, - uint16_t ip_id, - uint32_t flow_label, - uint32_t fooling, - uint32_t ts_increment, - uint32_t badseq_increment, - uint32_t badseq_ack_increment, - const void *data, uint16_t len, - uint8_t *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,sack,nmss,nseq,nack_seq,nwsize,scale_factor,timestamps,DF,ttl,tos,ip_id,fooling,ts_increment,badseq_increment,badseq_ack_increment,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,sack,nmss,nseq,nack_seq,nwsize,scale_factor,timestamps,ttl,flow_label,fooling,ts_increment,badseq_increment,badseq_ack_increment,data,len,buf,buflen) : - false; -} - - -// padlen<0 means payload shrinking -bool prepare_udp_segment4( - const struct sockaddr_in *src, const struct sockaddr_in *dst, - bool DF, - uint8_t ttl, - uint8_t tos, - uint16_t ip_id, - uint32_t fooling, - const uint8_t *padding, size_t padding_size, - int padlen, - const void *data, uint16_t len, - uint8_t *buf, size_t *buflen) -{ - if ((len+padlen)<=0) padlen=-(int)len+1; // do not allow payload to be less that 1 byte - if ((len+padlen)>0xFFFF) padlen=0xFFFF-len; // do not allow payload size to exceed u16 range - if (padlen<0) - { - len+=padlen; - padlen=0; - } - uint16_t datalen = (uint16_t)(len + padlen); - uint16_t ip_payload_len = sizeof(struct udphdr) + datalen; - uint16_t pktlen = sizeof(struct ip) + ip_payload_len; - if (pktlen>*buflen) return false; - - struct ip *ip = (struct ip*)buf; - struct udphdr *udp = (struct udphdr*)(ip+1); - uint8_t *payload = (uint8_t*)(udp+1); - - - fill_iphdr(ip, &src->sin_addr, &dst->sin_addr, pktlen, IPPROTO_UDP, DF, ttl, tos, ip_id); - fill_udphdr(udp, src->sin_port, dst->sin_port, datalen); - - memcpy(payload,data,len); - if (padding) - fill_pattern(payload+len,padlen,padding,padding_size,0); - else - memset(payload+len,0,padlen); - udp4_fix_checksum(udp,ip_payload_len,&ip->ip_src,&ip->ip_dst); - if (fooling & FOOL_BADSUM) udp->uh_sum^=(1+random()%0xFFFF); - - *buflen = pktlen; - return true; -} -bool prepare_udp_segment6( - const struct sockaddr_in6 *src, const struct sockaddr_in6 *dst, - uint8_t ttl, - uint32_t flow_label, - uint32_t fooling, - const uint8_t *padding, size_t padding_size, - int padlen, - const void *data, uint16_t len, - uint8_t *buf, size_t *buflen) -{ - if ((len+padlen)<=0) padlen=-(int)len+1; // do not allow payload to be less that 1 byte - if ((len+padlen)>0xFFFF) padlen=0xFFFF-len; // do not allow payload size to exceed u16 range - if (padlen<0) - { - len+=padlen; - padlen=0; - } - uint16_t datalen = (uint16_t)(len + padlen); - uint16_t transport_payload_len = sizeof(struct udphdr) + datalen; - uint16_t ip_payload_len = transport_payload_len + - 8*!!((fooling & (FOOL_HOPBYHOP|FOOL_HOPBYHOP2))==FOOL_HOPBYHOP) + - 16*!!(fooling & FOOL_HOPBYHOP2) + - 8*!!(fooling & FOOL_DESTOPT) + - 8*!!(fooling & FOOL_IPFRAG1); - uint16_t pktlen = sizeof(struct ip6_hdr) + ip_payload_len; - if (pktlen>*buflen) return false; - - struct ip6_hdr *ip6 = (struct ip6_hdr*)buf; - struct udphdr *udp = (struct udphdr*)(ip6+1); - uint8_t proto = IPPROTO_UDP, *nexttype = NULL; - - if (fooling & (FOOL_HOPBYHOP|FOOL_HOPBYHOP2)) - { - struct ip6_hbh *hbh = (struct ip6_hbh*)udp; - udp = (struct udphdr*)((uint8_t*)udp+8); - memset(hbh,0,8); - // extra HOPBYHOP header. standard violation - if (fooling & FOOL_HOPBYHOP2) - { - hbh = (struct ip6_hbh*)udp; - udp = (struct udphdr*)((uint8_t*)udp+8); - memset(hbh,0,8); - } - hbh->ip6h_nxt = IPPROTO_UDP; - nexttype = &hbh->ip6h_nxt; - proto = IPPROTO_HOPOPTS; - } - if (fooling & FOOL_DESTOPT) - { - struct ip6_dest *dest = (struct ip6_dest*)udp; - udp = (struct udphdr*)((uint8_t*)udp+8); - memset(dest,0,8); - dest->ip6d_nxt = IPPROTO_UDP; - if (nexttype) - *nexttype = IPPROTO_DSTOPTS; - else - proto = IPPROTO_DSTOPTS; - nexttype = &dest->ip6d_nxt; - } - if (fooling & FOOL_IPFRAG1) - { - struct ip6_frag *frag = (struct ip6_frag*)udp; - udp = (struct udphdr*)((uint8_t*)udp+sizeof(struct ip6_frag)); - frag->ip6f_nxt = IPPROTO_UDP; - frag->ip6f_ident = htonl(1+random()%0xFFFFFFFF); - frag->ip6f_reserved = 0; - frag->ip6f_offlg = 0; - if (nexttype) - *nexttype = IPPROTO_FRAGMENT; - else - proto = IPPROTO_FRAGMENT; - } - - uint8_t *payload = (uint8_t*)(udp+1); - - fill_ip6hdr(ip6, &src->sin6_addr, &dst->sin6_addr, ip_payload_len, proto, ttl, flow_label); - fill_udphdr(udp, src->sin6_port, dst->sin6_port, datalen); - - memcpy(payload,data,len); - if (padding) - fill_pattern(payload+len,padlen,padding,padding_size,0); - else - memset(payload+len,0,padlen); - udp6_fix_checksum(udp,transport_payload_len,&ip6->ip6_src,&ip6->ip6_dst); - if (fooling & FOOL_BADSUM) udp->uh_sum^=(1+random()%0xFFFF); - - *buflen = pktlen; - return true; -} -bool prepare_udp_segment( - const struct sockaddr *src, const struct sockaddr *dst, - bool DF, - uint8_t ttl, - uint8_t tos, - uint16_t ip_id, - uint32_t flow_label, - uint32_t fooling, - const uint8_t *padding, size_t padding_size, - int padlen, - const void *data, uint16_t len, - uint8_t *buf, size_t *buflen) -{ - return (src->sa_family==AF_INET && dst->sa_family==AF_INET) ? - prepare_udp_segment4((struct sockaddr_in *)src,(struct sockaddr_in *)dst,DF,ttl,tos,ip_id,fooling,padding,padding_size,padlen,data,len,buf,buflen) : - (src->sa_family==AF_INET6 && dst->sa_family==AF_INET6) ? - prepare_udp_segment6((struct sockaddr_in6 *)src,(struct sockaddr_in6 *)dst,ttl,flow_label,fooling,padding,padding_size,padlen,data,len,buf,buflen) : - false; -} - -bool ip6_insert_simple_hdr(uint8_t type, uint8_t *data_pkt, size_t len_pkt, uint8_t *buf, size_t *buflen) -{ - if ((len_pkt+8)<=*buflen && len_pkt>=sizeof(struct ip6_hdr)) - { - struct ip6_hdr *ip6 = (struct ip6_hdr *)buf; - struct ip6_ext *hdr = (struct ip6_ext*)(ip6+1); - *ip6 = *(struct ip6_hdr*)data_pkt; - memset(hdr,0,8); - memcpy((uint8_t*)hdr+8, data_pkt+sizeof(struct ip6_hdr), len_pkt-sizeof(struct ip6_hdr)); - hdr->ip6e_nxt = ip6->ip6_ctlun.ip6_un1.ip6_un1_nxt; - ip6->ip6_ctlun.ip6_un1.ip6_un1_nxt = type; - ip6->ip6_ctlun.ip6_un1.ip6_un1_plen = net16_add(ip6->ip6_ctlun.ip6_un1.ip6_un1_plen, 8); - *buflen = len_pkt + 8; - return true; - } - return false; -} - -// split ipv4 packet into 2 fragments at data payload position frag_pos -bool ip_frag4( - const uint8_t *pkt, size_t pkt_size, - size_t frag_pos, uint32_t ident, - uint8_t *pkt1, size_t *pkt1_size, - uint8_t *pkt2, size_t *pkt2_size) -{ - uint16_t hdrlen, payload_len; - // frag_pos must be 8-byte aligned - if (frag_pos & 7 || pkt_size < sizeof(struct ip)) return false; - payload_len = htons(((struct ip *)pkt)->ip_len); - hdrlen = ((struct ip *)pkt)->ip_hl<<2; - if (payload_len>pkt_size || hdrlen>pkt_size || hdrlen>payload_len) return false; - payload_len -= hdrlen; - if (frag_pos>=payload_len || *pkt1_size<(hdrlen+frag_pos) || *pkt2_size<(hdrlen+payload_len-frag_pos)) return false; - - memcpy(pkt1, pkt, hdrlen+frag_pos); - ((struct ip*)pkt1)->ip_off = htons(IP_MF); - ((struct ip*)pkt1)->ip_len = htons(hdrlen+frag_pos); - if (ident!=(uint32_t)-1) ((struct ip*)pkt1)->ip_id = (uint16_t)ident; - *pkt1_size=hdrlen+frag_pos; - ip4_fix_checksum((struct ip *)pkt1); - - memcpy(pkt2, pkt, hdrlen); - memcpy(pkt2+hdrlen, pkt+hdrlen+frag_pos, payload_len-frag_pos); - ((struct ip*)pkt2)->ip_off = htons((uint16_t)frag_pos>>3 & IP_OFFMASK); - ((struct ip*)pkt2)->ip_len = htons(hdrlen+payload_len-frag_pos); - if (ident!=(uint32_t)-1) ((struct ip*)pkt2)->ip_id = (uint16_t)ident; - *pkt2_size=hdrlen+payload_len-frag_pos; - ip4_fix_checksum((struct ip *)pkt2); - - return true; -} -bool ip_frag6( - const uint8_t *pkt, size_t pkt_size, - size_t frag_pos, uint32_t ident, - uint8_t *pkt1, size_t *pkt1_size, - uint8_t *pkt2, size_t *pkt2_size) -{ - size_t payload_len, unfragmentable; - uint8_t *last_header_type; - uint8_t proto; - struct ip6_frag *frag; - const uint8_t *payload; - - if (frag_pos & 7 || pkt_size < sizeof(struct ip6_hdr)) return false; - payload_len = sizeof(struct ip6_hdr) + htons(((struct ip6_hdr*)pkt)->ip6_ctlun.ip6_un1.ip6_un1_plen); - if (pkt_size < payload_len) return false; - - payload = pkt; - proto_skip_ipv6((uint8_t**)&payload, &payload_len, &proto, &last_header_type); - unfragmentable = payload - pkt; - - //printf("pkt_size=%zu FRAG_POS=%zu payload_len=%zu unfragmentable=%zu dh=%zu\n",pkt_size,frag_pos,payload_len,unfragmentable,last_header_type - pkt); - - if (frag_pos>=payload_len || - *pkt1_size<(unfragmentable + sizeof(struct ip6_frag) + frag_pos) || - *pkt2_size<(unfragmentable + sizeof(struct ip6_frag) + payload_len - frag_pos)) - { - return false; - } - - memcpy(pkt1, pkt, unfragmentable); - ((struct ip6_hdr*)pkt1)->ip6_ctlun.ip6_un1.ip6_un1_plen = htons(unfragmentable - sizeof(struct ip6_hdr) + sizeof(struct ip6_frag) + frag_pos); - pkt1[last_header_type - pkt] = IPPROTO_FRAGMENT; - frag = (struct ip6_frag*)(pkt1 + unfragmentable); - frag->ip6f_nxt = proto; - frag->ip6f_reserved = 0; - frag->ip6f_offlg = IP6F_MORE_FRAG; - frag->ip6f_ident = ident; - memcpy(frag+1, pkt + unfragmentable, frag_pos); - *pkt1_size = unfragmentable + sizeof(struct ip6_frag) + frag_pos; - - memcpy(pkt2, pkt, sizeof(struct ip6_hdr)); - ((struct ip6_hdr*)pkt2)->ip6_ctlun.ip6_un1.ip6_un1_plen = htons(unfragmentable - sizeof(struct ip6_hdr) + sizeof(struct ip6_frag) + payload_len - frag_pos); - pkt2[last_header_type - pkt] = IPPROTO_FRAGMENT; - frag = (struct ip6_frag*)(pkt2 + unfragmentable); - frag->ip6f_nxt = proto; - frag->ip6f_reserved = 0; - frag->ip6f_offlg = htons(frag_pos); - frag->ip6f_ident = ident; - memcpy(frag+1, pkt + unfragmentable + frag_pos, payload_len - frag_pos); - *pkt2_size = unfragmentable + sizeof(struct ip6_frag) + payload_len - frag_pos; - - return true; -} -bool ip_frag( - const uint8_t *pkt, size_t pkt_size, - size_t frag_pos, uint32_t ident, - uint8_t *pkt1, size_t *pkt1_size, - uint8_t *pkt2, size_t *pkt2_size) -{ - if (proto_check_ipv4(pkt,pkt_size)) - return ip_frag4(pkt,pkt_size,frag_pos,ident,pkt1,pkt1_size,pkt2,pkt2_size); - else if (proto_check_ipv6(pkt,pkt_size)) - return ip_frag6(pkt,pkt_size,frag_pos,ident,pkt1,pkt1_size,pkt2,pkt2_size); - else - return false; -} - -bool rewrite_ttl(struct ip *ip, struct ip6_hdr *ip6, uint8_t ttl) -{ - if (ttl) - { - if (ip) - { - if (ip->ip_ttl!=ttl) - { - ip->ip_ttl = ttl; - ip4_fix_checksum(ip); - return true; - } - } - else if (ip6) - { - if (ip6->ip6_ctlun.ip6_un1.ip6_un1_hlim!=ttl) - { - ip6->ip6_ctlun.ip6_un1.ip6_un1_hlim = ttl; - return true; - } - } - } - return false; -} - -void apply_tcp_flags(struct tcphdr *tcp, uint16_t fl) -{ - if (tcp) - { - tcp->th_flags = (uint8_t)fl; - tcp->th_x2 = (fl>>8) & 0xF; - } -} -uint16_t get_tcp_flags(const struct tcphdr *tcp) -{ - return tcp->th_flags | (tcp->th_x2<<8); -} - - - -void extract_ports(const struct tcphdr *tcphdr, const struct udphdr *udphdr, uint8_t *proto, uint16_t *sport, uint16_t *dport) -{ - if (sport) *sport = htons(tcphdr ? tcphdr->th_sport : udphdr ? udphdr->uh_sport : 0); - if (dport) *dport = htons(tcphdr ? tcphdr->th_dport : udphdr ? udphdr->uh_dport : 0); - if (proto) *proto = tcphdr ? IPPROTO_TCP : udphdr ? IPPROTO_UDP : -1; -} - -void extract_endpoints(const struct ip *ip,const struct ip6_hdr *ip6hdr,const struct tcphdr *tcphdr,const struct udphdr *udphdr, struct sockaddr_storage *src, struct sockaddr_storage *dst) -{ - if (ip) - { - struct sockaddr_in *si; - - if (dst) - { - si = (struct sockaddr_in*)dst; - si->sin_family = AF_INET; - si->sin_port = tcphdr ? tcphdr->th_dport : udphdr ? udphdr->uh_dport : 0; - si->sin_addr = ip->ip_dst; - } - - if (src) - { - si = (struct sockaddr_in*)src; - si->sin_family = AF_INET; - si->sin_port = tcphdr ? tcphdr->th_sport : udphdr ? udphdr->uh_sport : 0; - si->sin_addr = ip->ip_src; - } - } - else if (ip6hdr) - { - struct sockaddr_in6 *si; - - if (dst) - { - si = (struct sockaddr_in6*)dst; - si->sin6_family = AF_INET6; - si->sin6_port = tcphdr ? tcphdr->th_dport : udphdr ? udphdr->uh_dport : 0; - si->sin6_addr = ip6hdr->ip6_dst; - si->sin6_flowinfo = 0; - si->sin6_scope_id = 0; - } - - if (src) - { - si = (struct sockaddr_in6*)src; - si->sin6_family = AF_INET6; - si->sin6_port = tcphdr ? tcphdr->th_sport : udphdr ? udphdr->uh_sport : 0; - si->sin6_addr = ip6hdr->ip6_src; - si->sin6_flowinfo = 0; - si->sin6_scope_id = 0; - } - } -} - -const char *proto_name(uint8_t proto) -{ - switch(proto) - { - case IPPROTO_TCP: - return "tcp"; - case IPPROTO_UDP: - return "udp"; - case IPPROTO_ICMP: - return "icmp"; - case IPPROTO_ICMPV6: - return "icmp6"; - case IPPROTO_IGMP: - return "igmp"; - case IPPROTO_ESP: - return "esp"; - case IPPROTO_AH: - return "ah"; - case IPPROTO_IPV6: - return "6in4"; - case IPPROTO_IPIP: - return "4in4"; -#ifdef IPPROTO_GRE - case IPPROTO_GRE: - return "gre"; -#endif -#ifdef IPPROTO_SCTP - case IPPROTO_SCTP: - return "sctp"; -#endif - default: - return NULL; - } -} -static void str_proto_name(char *s, size_t s_len, uint8_t proto) -{ - const char *name = proto_name(proto); - if (name) - snprintf(s,s_len,"%s",name); - else - snprintf(s,s_len,"%u",proto); -} -uint16_t family_from_proto(uint8_t l3proto) -{ - switch(l3proto) - { - case IPPROTO_IP: return AF_INET; - case IPPROTO_IPV6: return AF_INET6; - default: return -1; - } -} - -static void str_srcdst_ip(char *s, size_t s_len, const void *saddr,const void *daddr) -{ - char s_ip[16],d_ip[16]; - *s_ip=*d_ip=0; - inet_ntop(AF_INET, saddr, s_ip, sizeof(s_ip)); - inet_ntop(AF_INET, daddr, d_ip, sizeof(d_ip)); - snprintf(s,s_len,"%s => %s",s_ip,d_ip); -} -void str_ip(char *s, size_t s_len, const struct ip *ip) -{ - char ss[35],s_proto[16]; - str_srcdst_ip(ss,sizeof(ss),&ip->ip_src,&ip->ip_dst); - str_proto_name(s_proto,sizeof(s_proto),ip->ip_p); - snprintf(s,s_len,"%s proto=%s ttl=%u",ss,s_proto,ip->ip_ttl); -} -void print_ip(const struct ip *ip) -{ - char s[66]; - str_ip(s,sizeof(s),ip); - printf("%s",s); -} -void str_srcdst_ip6(char *s, size_t s_len, const void *saddr,const void *daddr) -{ - char s_ip[40],d_ip[40]; - *s_ip=*d_ip=0; - inet_ntop(AF_INET6, saddr, s_ip, sizeof(s_ip)); - inet_ntop(AF_INET6, daddr, d_ip, sizeof(d_ip)); - snprintf(s,s_len,"%s => %s",s_ip,d_ip); -} -void str_ip6hdr(char *s, size_t s_len, const struct ip6_hdr *ip6hdr, uint8_t proto) -{ - char ss[83],s_proto[16]; - str_srcdst_ip6(ss,sizeof(ss),&ip6hdr->ip6_src,&ip6hdr->ip6_dst); - str_proto_name(s_proto,sizeof(s_proto),proto); - snprintf(s,s_len,"%s proto=%s ttl=%u",ss,s_proto,ip6hdr->ip6_hlim); -} -void print_ip6hdr(const struct ip6_hdr *ip6hdr, uint8_t proto) -{ - char s[128]; - str_ip6hdr(s,sizeof(s),ip6hdr,proto); - printf("%s",s); -} -void str_tcphdr(char *s, size_t s_len, const struct tcphdr *tcphdr) -{ - char flags[7],*f=flags; - if (tcphdr->th_flags & TH_SYN) *f++='S'; - if (tcphdr->th_flags & TH_ACK) *f++='A'; - if (tcphdr->th_flags & TH_RST) *f++='R'; - if (tcphdr->th_flags & TH_FIN) *f++='F'; - if (tcphdr->th_flags & TH_PUSH) *f++='P'; - if (tcphdr->th_flags & TH_URG) *f++='U'; - *f=0; - snprintf(s,s_len,"sport=%u dport=%u flags=%s seq=%u ack_seq=%u",htons(tcphdr->th_sport),htons(tcphdr->th_dport),flags,htonl(tcphdr->th_seq),htonl(tcphdr->th_ack)); -} -void print_tcphdr(const struct tcphdr *tcphdr) -{ - char s[80]; - str_tcphdr(s,sizeof(s),tcphdr); - printf("%s",s); -} -void str_udphdr(char *s, size_t s_len, const struct udphdr *udphdr) -{ - snprintf(s,s_len,"sport=%u dport=%u",htons(udphdr->uh_sport),htons(udphdr->uh_dport)); -} -void print_udphdr(const struct udphdr *udphdr) -{ - char s[30]; - str_udphdr(s,sizeof(s),udphdr); - printf("%s",s); -} - - - - -bool proto_check_ipv4(const uint8_t *data, size_t len) -{ - return len >= 20 && (data[0] & 0xF0) == 0x40 && - len >= ((data[0] & 0x0F) << 2); -} -// move to transport protocol -void proto_skip_ipv4(uint8_t **data, size_t *len) -{ - size_t l; - - l = (**data & 0x0F) << 2; - *data += l; - *len -= l; -} -bool proto_check_tcp(const uint8_t *data, size_t len) -{ - return len >= 20 && len >= ((data[12] & 0xF0) >> 2); -} -void proto_skip_tcp(uint8_t **data, size_t *len) -{ - size_t l; - l = ((*data)[12] & 0xF0) >> 2; - *data += l; - *len -= l; -} -bool proto_check_udp(const uint8_t *data, size_t len) -{ - return len >= 8 && len>=(data[4]<<8 | data[5]); -} -void proto_skip_udp(uint8_t **data, size_t *len) -{ - *data += 8; - *len -= 8; -} - -bool proto_check_ipv6(const 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 -void proto_skip_ipv6(uint8_t **data, size_t *len, uint8_t *proto_type, uint8_t **last_header_type) -{ - size_t hdrlen; - uint8_t HeaderType; - - if (proto_type) *proto_type = 0; // put error in advance - - HeaderType = (*data)[6]; // NextHeader field - if (last_header_type) *last_header_type = (*data)+6; - *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 43: // routing - case 60: // Destination Options - case 135: // mobility - case 139: // Host Identity Protocol Version v2 - case 140: // Shim6 - if (*len < 2) return; // error - hdrlen = 8 + ((*data)[1] << 3); - break; - case 44: // fragment. length fixed to 8, hdrlen field defined as reserved - hdrlen = 8; - break; - case 51: // authentication - // special case. length in ah header is in 32-bit words minus 2 - if (*len < 2) return; // error - hdrlen = 8 + ((*data)[1] << 2); - 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 - if (proto_type) *proto_type = HeaderType; - return; - } - if (*len < hdrlen) return; // error - HeaderType = **data; - if (last_header_type) *last_header_type = *data; - // advance to the next header location - *len -= hdrlen; - *data += hdrlen; - } - // we have garbage -} - -void proto_dissect_l3l4(uint8_t *data, size_t len,struct dissect *dis) -{ - memset(dis,0,sizeof(*dis)); - - dis->data_pkt = data; - dis->len_pkt = len; - - if (proto_check_ipv4(data, len)) - { - dis->ip = (struct ip *) data; - dis->proto = dis->ip->ip_p; - proto_skip_ipv4(&data, &len); - } - else if (proto_check_ipv6(data, len)) - { - dis->ip6 = (struct ip6_hdr *) data; - proto_skip_ipv6(&data, &len, &dis->proto, NULL); - } - else - { - return; - } - - if (dis->proto==IPPROTO_TCP && proto_check_tcp(data, len)) - { - dis->tcp = (struct tcphdr *) data; - dis->transport_len = len; - - proto_skip_tcp(&data, &len); - - dis->data_payload = data; - dis->len_payload = len; - - } - else if (dis->proto==IPPROTO_UDP && proto_check_udp(data, len)) - { - dis->udp = (struct udphdr *) data; - dis->transport_len = len; - - proto_skip_udp(&data, &len); - - dis->data_payload = data; - dis->len_payload = len; - } -} - - -bool tcp_synack_segment(const struct tcphdr *tcphdr) -{ - /* check for set bits in TCP hdr */ - return ((tcphdr->th_flags & (TH_URG|TH_ACK|TH_PUSH|TH_RST|TH_SYN|TH_FIN)) == (TH_ACK|TH_SYN)); -} -bool tcp_syn_segment(const struct tcphdr *tcphdr) -{ - /* check for set bits in TCP hdr */ - return ((tcphdr->th_flags & (TH_URG|TH_ACK|TH_PUSH|TH_RST|TH_SYN|TH_FIN)) == TH_SYN); -} -bool tcp_ack_segment(const struct tcphdr *tcphdr) -{ - /* check for set bits in TCP hdr */ - return ((tcphdr->th_flags & (TH_URG|TH_ACK|TH_PUSH|TH_RST|TH_SYN|TH_FIN)) == TH_ACK); -} - -void tcp_rewrite_wscale(struct tcphdr *tcp, uint8_t scale_factor) -{ - uint8_t *scale,scale_factor_old; - - if (scale_factor!=SCALE_NONE) - { - scale = tcp_find_option(tcp,3); // tcp option 3 - scale factor - if (scale && scale[1]==3) // length should be 3 - { - scale_factor_old=scale[2]; - // do not allow increasing scale factor - if (scale_factor>=scale_factor_old) - DLOG("Scale factor %u unchanged\n", scale_factor_old); - else - { - scale[2]=scale_factor; - DLOG("Scale factor change %u => %u\n", scale_factor_old, scale_factor); - } - } - } -} -// scale_factor=SCALE_NONE - do not change -void tcp_rewrite_winsize(struct tcphdr *tcp, uint16_t winsize, uint8_t scale_factor) -{ - uint16_t winsize_old; - - winsize_old = htons(tcp->th_win); // << scale_factor; - tcp->th_win = htons(winsize); - DLOG("Window size change %u => %u\n", winsize_old, winsize); - - tcp_rewrite_wscale(tcp, scale_factor); -} - - -#ifdef __CYGWIN__ - -static HANDLE w_filter = NULL; -static OVERLAPPED ovl = { .hEvent = NULL }; -static const struct str_list_head *wlan_filter_ssid = NULL, *nlm_filter_net = NULL; -static DWORD logical_net_filter_tick=0; -uint32_t w_win32_error=0; -INetworkListManager* pNetworkListManager=NULL; - -static void guid2str(const GUID *guid, char *str) -{ - snprintf(str,37, "%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X", guid->Data1, guid->Data2, guid->Data3, guid->Data4[0], guid->Data4[1], guid->Data4[2], guid->Data4[3], guid->Data4[4], guid->Data4[5], guid->Data4[6], guid->Data4[7]); -} -static bool str2guid(const char* str, GUID *guid) -{ - unsigned int u[11],k; - - if (36 != strlen(str) || 11 != sscanf(str, "%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X", u+0, u+1, u+2, u+3, u+4, u+5, u+6, u+7, u+8, u+9, u+10)) - return false; - guid->Data1 = u[0]; - if ((u[1] & 0xFFFF0000) || (u[2] & 0xFFFF0000)) return false; - guid->Data2 = (USHORT)u[1]; - guid->Data3 = (USHORT)u[2]; - for (k = 0; k < 8; k++) - { - if (u[k+3] & 0xFFFFFF00) return false; - guid->Data4[k] = (UCHAR)u[k+3]; - } - return true; -} - -static const char *sNetworkCards="SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\NetworkCards"; -// get adapter name from guid string -static bool AdapterID2Name(const GUID *guid,char *name,DWORD name_len) -{ - char sguid[39],sidx[32],val[256]; - HKEY hkNetworkCards,hkCard; - DWORD dwIndex,dwLen; - bool bRet = false; - WCHAR namew[128]; - DWORD namew_len; - - if (name_len<2) return false; - - if ((w_win32_error = RegOpenKeyExA(HKEY_LOCAL_MACHINE,sNetworkCards,0,KEY_ENUMERATE_SUB_KEYS,&hkNetworkCards)) == ERROR_SUCCESS) - { - guid2str(guid, sguid+1); - sguid[0]='{'; - sguid[37]='}'; - sguid[38]='\0'; - - for (dwIndex=0;;dwIndex++) - { - dwLen=sizeof(sidx)-1; - w_win32_error = RegEnumKeyExA(hkNetworkCards,dwIndex,sidx,&dwLen,NULL,NULL,NULL,NULL); - if (w_win32_error == ERROR_SUCCESS) - { - sidx[dwLen]='\0'; - - if ((w_win32_error = RegOpenKeyExA(hkNetworkCards,sidx,0,KEY_QUERY_VALUE,&hkCard)) == ERROR_SUCCESS) - { - dwLen=sizeof(val)-1; - if ((w_win32_error = RegQueryValueExA(hkCard,"ServiceName",NULL,NULL,val,&dwLen)) == ERROR_SUCCESS) - { - val[dwLen]='\0'; - if (!strcmp(val,sguid)) - { - namew_len = sizeof(namew)-sizeof(WCHAR); - if ((w_win32_error = RegQueryValueExW(hkCard,L"Description",NULL,NULL,(LPBYTE)namew,&namew_len)) == ERROR_SUCCESS) - { - namew[namew_len/sizeof(WCHAR)]=L'\0'; - if (WideCharToMultiByte(CP_UTF8, 0, namew, -1, name, name_len, NULL, NULL)) - bRet = true; - } - } - } - RegCloseKey(hkCard); - } - if (bRet) break; - } - else - break; - } - RegCloseKey(hkNetworkCards); - } - - return bRet; -} - -bool win_dark_init(const struct str_list_head *ssid_filter, const struct str_list_head *nlm_filter) -{ - win_dark_deinit(); - if (LIST_EMPTY(ssid_filter)) ssid_filter=NULL; - if (LIST_EMPTY(nlm_filter)) nlm_filter=NULL; - if (nlm_filter) - { - if (SUCCEEDED(w_win32_error = CoInitialize(NULL))) - { - if (FAILED(w_win32_error = CoCreateInstance(&CLSID_NetworkListManager, NULL, CLSCTX_ALL, &IID_INetworkListManager, (LPVOID*)&pNetworkListManager))) - { - CoUninitialize(); - return false; - } - } - else - return false; - } - nlm_filter_net = nlm_filter; - wlan_filter_ssid = ssid_filter; - return true; -} -bool win_dark_deinit(void) -{ - if (pNetworkListManager) - { - pNetworkListManager->lpVtbl->Release(pNetworkListManager); - pNetworkListManager = NULL; - } - if (nlm_filter_net) CoUninitialize(); - wlan_filter_ssid = nlm_filter_net = NULL; -} - - -bool nlm_list(bool bAll) -{ - bool bRet = true; - - if (SUCCEEDED(w_win32_error = CoInitialize(NULL))) - { - INetworkListManager* pNetworkListManager; - if (SUCCEEDED(w_win32_error = CoCreateInstance(&CLSID_NetworkListManager, NULL, CLSCTX_ALL, &IID_INetworkListManager, (LPVOID*)&pNetworkListManager))) - { - IEnumNetworks* pEnumNetworks; - if (SUCCEEDED(w_win32_error = pNetworkListManager->lpVtbl->GetNetworks(pNetworkListManager, NLM_ENUM_NETWORK_ALL, &pEnumNetworks))) - { - INetwork *pNet; - INetworkConnection *pConn; - IEnumNetworkConnections *pEnumConnections; - VARIANT_BOOL bIsConnected, bIsConnectedInet; - NLM_NETWORK_CATEGORY category; - GUID idNet, idAdapter; - BSTR bstrName; - char Name[128],Name2[128]; - int connected; - for (connected = 1; connected >= !bAll; connected--) - { - for (;;) - { - if (FAILED(w_win32_error = pEnumNetworks->lpVtbl->Next(pEnumNetworks, 1, &pNet, NULL))) - { - bRet = false; - break; - } - if (w_win32_error != S_OK) break; - if (SUCCEEDED(w_win32_error = pNet->lpVtbl->get_IsConnected(pNet, &bIsConnected)) && - SUCCEEDED(w_win32_error = pNet->lpVtbl->get_IsConnectedToInternet(pNet, &bIsConnectedInet)) && - SUCCEEDED(w_win32_error = pNet->lpVtbl->GetNetworkId(pNet, &idNet)) && - SUCCEEDED(w_win32_error = pNet->lpVtbl->GetCategory(pNet, &category)) && - SUCCEEDED(w_win32_error = pNet->lpVtbl->GetName(pNet, &bstrName))) - { - if (!!bIsConnected == connected) - { - if (WideCharToMultiByte(CP_UTF8, 0, bstrName, -1, Name, sizeof(Name), NULL, NULL)) - { - printf("Name : %s", Name); - if (bIsConnected) printf(" (connected)"); - if (bIsConnectedInet) printf(" (inet)"); - printf(" (%s)\n", - category==NLM_NETWORK_CATEGORY_PUBLIC ? "public" : - category==NLM_NETWORK_CATEGORY_PRIVATE ? "private" : - category==NLM_NETWORK_CATEGORY_DOMAIN_AUTHENTICATED ? "domain" : - "unknown"); - guid2str(&idNet, Name); - printf("NetID : %s\n", Name); - if (connected && SUCCEEDED(w_win32_error = pNet->lpVtbl->GetNetworkConnections(pNet, &pEnumConnections))) - { - while ((w_win32_error = pEnumConnections->lpVtbl->Next(pEnumConnections, 1, &pConn, NULL))==S_OK) - { - if (SUCCEEDED(w_win32_error = pConn->lpVtbl->GetAdapterId(pConn,&idAdapter))) - { - guid2str(&idAdapter, Name); - if (AdapterID2Name(&idAdapter,Name2,sizeof(Name2))) - printf("Adapter : %s (%s)\n", Name2, Name); - else - printf("Adapter : %s\n", Name); - } - pConn->lpVtbl->Release(pConn); - } - pEnumConnections->lpVtbl->Release(pEnumConnections); - } - printf("\n"); - } - else - { - w_win32_error = HRESULT_FROM_WIN32(GetLastError()); - bRet = false; - } - } - SysFreeString(bstrName); - } - else - bRet = false; - pNet->lpVtbl->Release(pNet); - if (!bRet) break; - } - if (!bRet) break; - pEnumNetworks->lpVtbl->Reset(pEnumNetworks); - } - pEnumNetworks->lpVtbl->Release(pEnumNetworks); - } - else - bRet = false; - pNetworkListManager->lpVtbl->Release(pNetworkListManager); - } - else - bRet = false; - } - else - bRet = false; - - CoUninitialize(); - return bRet; -} - -static bool nlm_filter_match(const struct str_list_head *nlm_list) -{ - // no filter given. always matches. - if (!nlm_list || LIST_EMPTY(nlm_list)) - { - w_win32_error = 0; - return true; - } - - bool bRet = true, bMatch = false; - IEnumNetworks* pEnum; - - if (SUCCEEDED(w_win32_error = pNetworkListManager->lpVtbl->GetNetworks(pNetworkListManager, NLM_ENUM_NETWORK_CONNECTED, &pEnum))) - { - INetwork* pNet; - GUID idNet,g; - BSTR bstrName; - char Name[128]; - struct str_list *nlm; - for (;;) - { - if (FAILED(w_win32_error = pEnum->lpVtbl->Next(pEnum, 1, &pNet, NULL))) - { - bRet = false; - break; - } - if (w_win32_error != S_OK) break; - if (SUCCEEDED(w_win32_error = pNet->lpVtbl->GetNetworkId(pNet, &idNet)) && - SUCCEEDED(w_win32_error = pNet->lpVtbl->GetName(pNet, &bstrName))) - { - if (WideCharToMultiByte(CP_UTF8, 0, bstrName, -1, Name, sizeof(Name), NULL, NULL)) - { - LIST_FOREACH(nlm, nlm_list, next) - { - bMatch = !strcmp(Name,nlm->str) || str2guid(nlm->str,&g) && !memcmp(&idNet,&g,sizeof(GUID)); - if (bMatch) break; - } - } - else - { - w_win32_error = HRESULT_FROM_WIN32(GetLastError()); - bRet = false; - } - SysFreeString(bstrName); - } - else - bRet = false; - pNet->lpVtbl->Release(pNet); - if (!bRet || bMatch) break; - } - pEnum->lpVtbl->Release(pEnum); - } - else - bRet = false; - return bRet && bMatch; -} - -static bool wlan_filter_match(const struct str_list_head *ssid_list) -{ - DWORD dwCurVersion; - HANDLE hClient = NULL; - PWLAN_INTERFACE_INFO_LIST pIfList = NULL; - PWLAN_INTERFACE_INFO pIfInfo; - PWLAN_CONNECTION_ATTRIBUTES pConnectInfo; - DWORD connectInfoSize, k; - bool bRes; - struct str_list *ssid; - size_t len; - - // no filter given. always matches. - if (!ssid_list || LIST_EMPTY(ssid_list)) - { - w_win32_error = 0; - return true; - } - - w_win32_error = WlanOpenHandle(2, NULL, &dwCurVersion, &hClient); - if (w_win32_error != ERROR_SUCCESS) goto fail; - w_win32_error = WlanEnumInterfaces(hClient, NULL, &pIfList); - if (w_win32_error != ERROR_SUCCESS) goto fail; - for (k = 0; k < pIfList->dwNumberOfItems; k++) - { - pIfInfo = pIfList->InterfaceInfo + k; - if (pIfInfo->isState == wlan_interface_state_connected) - { - w_win32_error = WlanQueryInterface(hClient, - &pIfInfo->InterfaceGuid, - wlan_intf_opcode_current_connection, - NULL, - &connectInfoSize, - (PVOID *)&pConnectInfo, - NULL); - if (w_win32_error != ERROR_SUCCESS) goto fail; - -// printf("%s\n", pConnectInfo->wlanAssociationAttributes.dot11Ssid.ucSSID); - - LIST_FOREACH(ssid, ssid_list, next) - { - len = strlen(ssid->str); - if (len==pConnectInfo->wlanAssociationAttributes.dot11Ssid.uSSIDLength && !memcmp(ssid->str,pConnectInfo->wlanAssociationAttributes.dot11Ssid.ucSSID,len)) - { - WlanFreeMemory(pConnectInfo); - goto found; - } - } - - WlanFreeMemory(pConnectInfo); - } - } - w_win32_error = 0; -fail: - bRes = false; -ex: - if (pIfList) WlanFreeMemory(pIfList); - if (hClient) WlanCloseHandle(hClient, 0); - return bRes; -found: - w_win32_error = 0; - bRes = true; - goto ex; -} - -bool logical_net_filter_match(void) -{ - return wlan_filter_match(wlan_filter_ssid) && nlm_filter_match(nlm_filter_net); -} - -static bool logical_net_filter_match_rate_limited(void) -{ - DWORD dwTick = GetTickCount() / 1000; - if (logical_net_filter_tick == dwTick) return true; - logical_net_filter_tick = dwTick; - return logical_net_filter_match(); -} - -static HANDLE windivert_init_filter(const char *filter, UINT64 flags) -{ - LPSTR errormessage = NULL; - HANDLE h, hMutex; - const char *mutex_name = "Global\\winws_windivert_mutex"; - - // windivert driver start in windivert.dll has race conditions - hMutex = CreateMutexA(NULL,TRUE,mutex_name); - if (hMutex && GetLastError()==ERROR_ALREADY_EXISTS) - WaitForSingleObject(hMutex,INFINITE); - h = WinDivertOpen(filter, WINDIVERT_LAYER_NETWORK, 0, flags); - w_win32_error = GetLastError(); - - if (hMutex) - { - ReleaseMutex(hMutex); - CloseHandle(hMutex); - SetLastError(w_win32_error); - } - - if (h != INVALID_HANDLE_VALUE) return h; - - FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, - NULL, w_win32_error, MAKELANGID(LANG_ENGLISH, SUBLANG_DEFAULT), (LPSTR)&errormessage, 0, NULL); - DLOG_ERR("windivert: error opening filter: %s", errormessage); - LocalFree(errormessage); - if (w_win32_error == ERROR_INVALID_IMAGE_HASH) - DLOG_ERR("windivert: try to disable secure boot and install OS patches\n"); - - return NULL; -} -void rawsend_cleanup(void) -{ - if (w_filter) - { - CancelIoEx(w_filter,&ovl); - WinDivertClose(w_filter); - w_filter=NULL; - } - if (ovl.hEvent) - { - CloseHandle(ovl.hEvent); - ovl.hEvent=NULL; - } -} -bool windivert_init(const char *filter) -{ - rawsend_cleanup(); - w_filter = windivert_init_filter(filter, 0); - if (w_filter) - { - ovl.hEvent = CreateEventW(NULL,FALSE,FALSE,NULL); - if (!ovl.hEvent) - { - w_win32_error = GetLastError(); - rawsend_cleanup(); - return false; - } - return true; - } - return false; -} - -static bool windivert_recv_filter(HANDLE hFilter, uint8_t *packet, size_t *len, WINDIVERT_ADDRESS *wa) -{ - UINT recv_len; - DWORD err; - DWORD rd; - char c; - - if (bQuit) - { - errno=EINTR; - return false; - } - if (!logical_net_filter_match_rate_limited()) - { - errno=ENODEV; - return false; - } - usleep(0); - if (WinDivertRecvEx(hFilter, packet, *len, &recv_len, 0, wa, NULL, &ovl)) - { - *len = recv_len; - return true; - } - for(;;) - { - w_win32_error = GetLastError(); - switch(w_win32_error) - { - case ERROR_IO_PENDING: - // make signals working - while (WaitForSingleObject(ovl.hEvent,50)==WAIT_TIMEOUT) - { - if (bQuit) - { - errno=EINTR; - return false; - } - if (!logical_net_filter_match_rate_limited()) - { - errno=ENODEV; - return false; - } - usleep(0); - } - if (!GetOverlappedResult(hFilter,&ovl,&rd,TRUE)) - continue; - *len = rd; - return true; - case ERROR_INSUFFICIENT_BUFFER: - errno = ENOBUFS; - break; - case ERROR_NO_DATA: - errno = ESHUTDOWN; - break; - default: - errno = EIO; - } - break; - } - return false; -} -bool windivert_recv(uint8_t *packet, size_t *len, WINDIVERT_ADDRESS *wa) -{ - return windivert_recv_filter(w_filter,packet,len,wa); -} - -static bool windivert_send_filter(HANDLE hFilter, const uint8_t *packet, size_t len, const WINDIVERT_ADDRESS *wa) -{ - bool b = WinDivertSend(hFilter,packet,(UINT)len,NULL,wa); - w_win32_error = GetLastError(); - return b; -} -bool windivert_send(const uint8_t *packet, size_t len, const WINDIVERT_ADDRESS *wa) -{ - return windivert_send_filter(w_filter,packet,len,wa); -} - -bool rawsend(const struct sockaddr* dst,uint32_t fwmark,const char *ifout,const void *data,size_t len) -{ - WINDIVERT_ADDRESS wa; - - memset(&wa,0,sizeof(wa)); - // pseudo interface id IfIdx.SubIfIdx - if (sscanf(ifout,"%u.%u",&wa.Network.IfIdx,&wa.Network.SubIfIdx)!=2) - { - errno = EINVAL; - return false; - } - wa.Outbound=1; - wa.IPChecksum=1; - wa.TCPChecksum=1; - wa.UDPChecksum=1; - wa.IPv6 = (dst->sa_family==AF_INET6); - - return windivert_send(data,len,&wa); -} - -#else // *nix - -static int rawsend_sock4=-1, rawsend_sock6=-1; -static bool b_bind_fix4=false, b_bind_fix6=false; -static void rawsend_clean_sock(int *sock) -{ - if (sock && *sock!=-1) - { - close(*sock); - *sock=-1; - } -} -void rawsend_cleanup(void) -{ - rawsend_clean_sock(&rawsend_sock4); - rawsend_clean_sock(&rawsend_sock6); -} -static int *rawsend_family_sock(sa_family_t family) -{ - switch(family) - { - case AF_INET: return &rawsend_sock4; - case AF_INET6: return &rawsend_sock6; - default: return NULL; - } -} - -#ifdef BSD -int socket_divert(sa_family_t family) -{ - int fd; - -#ifdef __FreeBSD__ - // freebsd14+ way - // don't want to use ifdefs with os version to make binaries compatible with all versions - fd = socket(PF_DIVERT, SOCK_RAW, 0); - if (fd==-1 && (errno==EPROTONOSUPPORT || errno==EAFNOSUPPORT || errno==EPFNOSUPPORT)) -#endif - // freebsd13- or openbsd way - fd = socket(family, SOCK_RAW, IPPROTO_DIVERT); - return fd; -} -static int rawsend_socket_divert(sa_family_t family) -{ - // HACK HACK HACK HACK HACK HACK HACK HACK - // FreeBSD doesnt allow IP_HDRINCL for IPV6 - // OpenBSD doesnt allow rawsending tcp frames - // we either have to go to the link layer (its hard, possible problems arise, compat testing, ...) or use some HACKING - // from my point of view disabling direct ability to send ip frames is not security. its SHIT - - int fd = socket_divert(family); - if (fd!=-1 && !set_socket_buffers(fd,4096,RAW_SNDBUF)) - { - close(fd); - return -1; - } - return fd; -} -static int rawsend_sendto_divert(sa_family_t family, int sock, const void *buf, size_t len) -{ - struct sockaddr_storage sa; - socklen_t slen; - -#ifdef __FreeBSD__ - // since FreeBSD 14 it requires hardcoded ipv4 values, although can also send ipv6 frames - family = AF_INET; - slen = sizeof(struct sockaddr_in); -#else - // OpenBSD requires correct family and size - switch(family) - { - case AF_INET: - slen = sizeof(struct sockaddr_in); - break; - case AF_INET6: - slen = sizeof(struct sockaddr_in6); - break; - default: - return -1; - } -#endif - memset(&sa,0,slen); - sa.ss_family = family; - return sendto(sock, buf, len, 0, (struct sockaddr*)&sa, slen); -} -#endif - -static int rawsend_socket_raw(int domain, int proto) -{ - int fd = socket(domain, SOCK_RAW, proto); - if (fd!=-1) - { - #ifdef __linux__ - int s=RAW_SNDBUF/2; - int r=2048; - #else - int s=RAW_SNDBUF; - int r=4096; - #endif - if (!set_socket_buffers(fd,r,s)) - { - close(fd); - return -1; - } - } - return fd; -} - -static bool set_socket_fwmark(int sock, uint32_t fwmark) -{ -#ifdef BSD -#ifdef SO_USER_COOKIE - if (setsockopt(sock, SOL_SOCKET, SO_USER_COOKIE, &fwmark, sizeof(fwmark)) == -1) - { - DLOG_PERROR("rawsend: setsockopt(SO_USER_COOKIE)"); - return false; - } -#endif -#elif defined(__linux__) - if (setsockopt(sock, SOL_SOCKET, SO_MARK, &fwmark, sizeof(fwmark)) == -1) - { - DLOG_PERROR("rawsend: setsockopt(SO_MARK)"); - return false; - } - -#endif - return true; -} - -static int rawsend_socket(sa_family_t family) -{ - int *sock = rawsend_family_sock(family); - if (!sock) return -1; - - if (*sock==-1) - { - int yes=1,pri=6; - //printf("rawsend_socket: family %d",family); - -#ifdef __FreeBSD__ - // IPPROTO_RAW with ipv6 in FreeBSD always returns EACCES on sendto. - // must use IPPROTO_TCP for ipv6. IPPROTO_RAW works for ipv4 - // divert sockets are always v4 but accept both v4 and v6 - *sock = rawsend_socket_divert(AF_INET); -#elif defined(__OpenBSD__) || defined (__APPLE__) - // OpenBSD does not allow sending TCP frames through raw sockets - // I dont know about macos. They have dropped ipfw in recent versions and their PF does not support divert-packet - *sock = rawsend_socket_divert(family); -#else - *sock = rawsend_socket_raw(family, IPPROTO_RAW); -#endif - if (*sock==-1) - { - DLOG_PERROR("rawsend: socket()"); - return -1; - } -#ifdef __linux__ - if (setsockopt(*sock, SOL_SOCKET, SO_PRIORITY, &pri, sizeof(pri)) == -1) - { - DLOG_PERROR("rawsend: setsockopt(SO_PRIORITY)"); - goto exiterr; - } - if (family==AF_INET && setsockopt(*sock, IPPROTO_IP, IP_NODEFRAG, &yes, sizeof(yes)) == -1) - { - DLOG_PERROR("rawsend: setsockopt(IP_NODEFRAG)"); - goto exiterr; - } - if (family==AF_INET && setsockopt(*sock, IPPROTO_IP, IP_FREEBIND, &yes, sizeof(yes)) == -1) - { - DLOG_PERROR("rawsend: setsockopt(IP_FREEBIND)"); - goto exiterr; - } - if (family==AF_INET6 && setsockopt(*sock, SOL_IPV6, IPV6_FREEBIND, &yes, sizeof(yes)) == -1) - { - //DLOG_PERROR("rawsend: setsockopt(IPV6_FREEBIND)"); - // dont error because it's supported only from kernel 4.15 - } -#endif - } - return *sock; -exiterr: - rawsend_clean_sock(sock); - return -1; -} -bool rawsend_preinit(bool bind_fix4, bool bind_fix6) -{ - b_bind_fix4 = bind_fix4; - b_bind_fix6 = bind_fix6; - // allow ipv6 disabled systems - return rawsend_socket(AF_INET)!=-1 && (rawsend_socket(AF_INET6)!=-1 || errno==EAFNOSUPPORT); -} -bool rawsend(const struct sockaddr* dst,uint32_t fwmark,const char *ifout,const void *data,size_t len) -{ - ssize_t bytes; - int sock=rawsend_socket(dst->sa_family); - if (sock==-1) return false; - if (!set_socket_fwmark(sock,fwmark)) 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 in linux - -#if defined(BSD) - bytes = rawsend_sendto_divert(dst->sa_family,sock,data,len); - if (bytes==-1) - { - DLOG_PERROR("rawsend: sendto_divert"); - return false; - } - return true; - -#else - -#ifdef __linux__ - struct sockaddr_storage sa_src; - switch(dst->sa_family) - { - case AF_INET: - if (!b_bind_fix4) goto nofix; - extract_endpoints(data,NULL,NULL,NULL, &sa_src, NULL); - break; - case AF_INET6: - if (!b_bind_fix6) goto nofix; - extract_endpoints(NULL,data,NULL,NULL, &sa_src, NULL); - break; - default: - return false; // should not happen - } - //printf("family %u dev %s bind : ", dst->sa_family, ifout); print_sockaddr((struct sockaddr *)&sa_src); printf("\n"); - if (setsockopt(sock, SOL_SOCKET, SO_BINDTODEVICE, ifout, ifout ? strlen(ifout)+1 : 0) == -1) - { - DLOG_PERROR("rawsend: setsockopt(SO_BINDTODEVICE)"); - return false; - } - if (bind(sock, (const struct sockaddr*)&sa_src, dst->sa_family==AF_INET ? sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6))) - { - DLOG_PERROR("rawsend: bind (ignoring)"); - // do not fail. this can happen regardless of IP_FREEBIND - // rebind to any address - memset(&sa_src,0,sizeof(sa_src)); - sa_src.ss_family = dst->sa_family; - if (bind(sock, (const struct sockaddr*)&sa_src, dst->sa_family==AF_INET ? sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6))) - { - DLOG_PERROR("rawsend: bind to any"); - return false; - } - } -nofix: -#endif - - // normal raw socket sendto - bytes = sendto(sock, data, len, 0, (struct sockaddr*)&dst2, salen); - if (bytes==-1) - { - char s[40]; - snprintf(s,sizeof(s),"rawsend: sendto (%zu)",len); - DLOG_PERROR(s); - return false; - } - return true; -#endif -} - -#endif // not CYGWIN - -bool rawsend_rp(const struct rawpacket *rp) -{ - return rawsend((struct sockaddr*)&rp->dst,rp->fwmark,rp->ifout,rp->packet,rp->len); -} -bool rawsend_queue(struct rawpacket_tailhead *q) -{ - struct rawpacket *rp; - bool b; - for (b=true; (rp=rawpacket_dequeue(q)) ; rawpacket_free(rp)) - b &= rawsend_rp(rp); - return b; -} - - - -#if defined(HAS_FILTER_SSID) && defined(__linux__) - -// linux-specific wlan retrieval implementation - -typedef void netlink_prepare_nlh_cb_t(struct nlmsghdr *nlh, void *param); - -static bool netlink_genl_simple_transact(struct mnl_socket* nl, uint16_t type, uint16_t flags, uint8_t cmd, uint8_t version, netlink_prepare_nlh_cb_t cb_prepare_nlh, void *prepare_data, mnl_cb_t cb_data, void *data) -{ - char buf[MNL_SOCKET_BUFFER_SIZE]; - struct nlmsghdr *nlh; - struct genlmsghdr *genl; - ssize_t rd; - - nlh = mnl_nlmsg_put_header(buf); - nlh->nlmsg_type = type; - nlh->nlmsg_flags = flags; - - genl = mnl_nlmsg_put_extra_header(nlh, sizeof(struct genlmsghdr)); - genl->cmd = cmd; - genl->version = version; - - if (cb_prepare_nlh) cb_prepare_nlh(nlh, prepare_data); - - if (mnl_socket_sendto(nl, nlh, nlh->nlmsg_len) < 0) - { - DLOG_PERROR("mnl_socket_sendto"); - return false; - } - - while ((rd=mnl_socket_recvfrom(nl, buf, sizeof(buf))) > 0) - { - switch(mnl_cb_run(buf, rd, 0, 0, cb_data, data)) - { - case MNL_CB_STOP: - return true; - case MNL_CB_OK: - break; - default: - return false; - } - } - - return false; -} - -static void wlan_id_prepare(struct nlmsghdr *nlh, void *param) -{ - mnl_attr_put_strz(nlh, CTRL_ATTR_FAMILY_NAME, "nl80211"); -} -static int wlan_id_attr_cb(const struct nlattr *attr, void *data) -{ - if (mnl_attr_type_valid(attr, CTRL_ATTR_MAX) < 0) - { - DLOG_PERROR("mnl_attr_type_valid"); - return MNL_CB_ERROR; - } - - switch(mnl_attr_get_type(attr)) - { - case CTRL_ATTR_FAMILY_ID: - if (mnl_attr_validate(attr, MNL_TYPE_U16) < 0) - { - DLOG_PERROR("mnl_attr_validate(family_id)"); - return MNL_CB_ERROR; - } - *((uint16_t*)data) = mnl_attr_get_u16(attr); - break; - } - return MNL_CB_OK; -} -static int wlan_id_cb(const struct nlmsghdr *nlh, void *data) -{ - return mnl_attr_parse(nlh, sizeof(struct genlmsghdr), wlan_id_attr_cb, data); -} -static uint16_t wlan_get_family_id(struct mnl_socket* nl) -{ - uint16_t id; - return netlink_genl_simple_transact(nl, GENL_ID_CTRL, NLM_F_REQUEST | NLM_F_ACK, CTRL_CMD_GETFAMILY, 1, wlan_id_prepare, NULL, wlan_id_cb, &id) ? id : 0; -} - -static int wlan_info_attr_cb(const struct nlattr *attr, void *data) -{ - struct wlan_interface *wlan = (struct wlan_interface *)data; - switch(mnl_attr_get_type(attr)) - { - case NL80211_ATTR_IFINDEX: - if (mnl_attr_validate(attr, MNL_TYPE_U32) < 0) - { - DLOG_PERROR("mnl_attr_validate(ifindex)"); - return MNL_CB_ERROR; - } - wlan->ifindex = mnl_attr_get_u32(attr); - break; - case NL80211_ATTR_SSID: - if (mnl_attr_validate(attr, MNL_TYPE_STRING) < 0) - { - DLOG_PERROR("mnl_attr_validate(ssid)"); - return MNL_CB_ERROR; - } - snprintf(wlan->ssid,sizeof(wlan->ssid),"%s",mnl_attr_get_str(attr)); - break; - case NL80211_ATTR_IFNAME: - if (mnl_attr_validate(attr, MNL_TYPE_STRING) < 0) - { - DLOG_PERROR("mnl_attr_validate(ifname)"); - return MNL_CB_ERROR; - } - snprintf(wlan->ifname,sizeof(wlan->ifname),"%s",mnl_attr_get_str(attr)); - break; - } - return MNL_CB_OK; -} -struct wlan_info_req -{ - struct wlan_interface_collection *wc; - bool bReqSSID; -}; -static int wlan_info_cb(const struct nlmsghdr *nlh, void *data) -{ - int ret; - struct wlan_info_req *wr = (struct wlan_info_req*)data; - if (wr->wc->count>=WLAN_INTERFACE_MAX) return MNL_CB_OK; - memset(wr->wc->wlan + wr->wc->count,0,sizeof(struct wlan_interface)); - ret = mnl_attr_parse(nlh, sizeof(struct genlmsghdr), wlan_info_attr_cb, wr->wc->wlan + wr->wc->count); - if (ret>=0 && (!wr->bReqSSID || *wr->wc->wlan[wr->wc->count].ssid) && *wr->wc->wlan[wr->wc->count].ifname && wr->wc->wlan[wr->wc->count].ifindex) - wr->wc->count++; - return ret; -} -static bool wlan_info(struct mnl_socket* nl, uint16_t wlan_family_id, struct wlan_interface_collection* w, bool bReqSSID) -{ - struct wlan_info_req req = { .bReqSSID = bReqSSID, .wc = w }; - return netlink_genl_simple_transact(nl, wlan_family_id, NLM_F_REQUEST | NLM_F_ACK | NLM_F_DUMP, NL80211_CMD_GET_INTERFACE, 0, NULL, NULL, wlan_info_cb, &req); -} - - -static void scan_prepare(struct nlmsghdr *nlh, void *param) -{ - mnl_attr_put_u32(nlh, NL80211_ATTR_IFINDEX, *(int*)param); -} -static uint8_t *find_ie(uint8_t *buf, size_t len, uint8_t ie) -{ - while (len>=2) - { - if (len<(2+buf[1])) break; - if (buf[0]==ie) return buf; - buf+=buf[1]+2; - len-=buf[1]+2; - } - return NULL; -} -static int scan_info_attr_cb(const struct nlattr *attr, void *data) -{ - struct wlan_interface *wlan = (struct wlan_interface *)data; - const struct nlattr *nested; - uint8_t *payload, *ie; - uint16_t payload_len; - bool ok; - - switch(mnl_attr_get_type(attr)) - { - case NL80211_ATTR_IFINDEX: - if (mnl_attr_validate(attr, MNL_TYPE_U32) < 0) - { - DLOG_PERROR("mnl_attr_validate"); - return MNL_CB_ERROR; - } - wlan->ifindex = mnl_attr_get_u32(attr); - if (!if_indextoname(wlan->ifindex, wlan->ifname)) - DLOG_PERROR("if_indextoname"); - break; - case NL80211_ATTR_BSS: - if (mnl_attr_validate(attr, MNL_TYPE_NESTED) < 0) - { - DLOG_PERROR("mnl_attr_validate"); - return MNL_CB_ERROR; - } - ok = false; - mnl_attr_for_each_nested(nested, attr) - { - if (mnl_attr_get_type(nested)==NL80211_BSS_STATUS) - { - uint32_t status = mnl_attr_get_u32(nested); - if (status==NL80211_BSS_STATUS_ASSOCIATED || status==NL80211_BSS_STATUS_AUTHENTICATED || status==NL80211_BSS_STATUS_IBSS_JOINED) - { - ok=1; - break; - } - } - } - if (!ok) break; - mnl_attr_for_each_nested(nested, attr) - { - switch(mnl_attr_get_type(nested)) - { - case NL80211_BSS_INFORMATION_ELEMENTS: - payload_len = mnl_attr_get_payload_len(nested); - payload = mnl_attr_get_payload(nested); - ie = find_ie(payload,payload_len,0); - if (ie) - { - uint8_t l = ie[1]; - if (l>=(sizeof(wlan->ssid))) l=sizeof(wlan->ssid)-1; - memcpy(wlan->ssid,ie+2,l); - wlan->ssid[l]=0; - } - break; - } - } - break; - } - return MNL_CB_OK; -} -static int scan_info_cb(const struct nlmsghdr *nlh, void *data) -{ - int ret; - struct wlan_interface_collection *wc = (struct wlan_interface_collection*)data; - if (wc->count>=WLAN_INTERFACE_MAX) return MNL_CB_OK; - memset(wc->wlan+wc->count,0,sizeof(wc->wlan[0])); - ret = mnl_attr_parse(nlh, sizeof(struct genlmsghdr), scan_info_attr_cb, wc->wlan+wc->count); - if (ret>=0 && *wc->wlan[wc->count].ssid && *wc->wlan[wc->count].ifname && wc->wlan[wc->count].ifindex) - wc->count++; - return ret; -} -static bool scan_info(struct mnl_socket* nl, uint16_t wlan_family_id, struct wlan_interface_collection* w) -{ - struct wlan_interface_collection wc_all = { .count = 0 }; - // wlan_info does not return ssid since kernel 5.19 - // it's used to enumerate all wifi interfaces then call scan_info on each - if (!wlan_info(nl, wlan_family_id, &wc_all, false)) return false; - for(int i=0;icount;i++) - if (!strcmp(w->wlan[i].ifname,ifname)) - return w->wlan[i].ssid; - } - return NULL; -} -const char *wlan_ifidx2ssid(const struct wlan_interface_collection *w,int ifidx) -{ - int i; - for (i=0;icount;i++) - if (w->wlan[i].ifindex == ifidx) - return w->wlan[i].ssid; - return NULL; -} -const char *wlan_ssid_search_ifname(const char *ifname) -{ - return wlan_ifname2ssid(&wlans,ifname); -} -const char *wlan_ssid_search_ifidx(int ifidx) -{ - return wlan_ifidx2ssid(&wlans,ifidx); -} - -#endif - - - -uint8_t hop_count_guess(uint8_t ttl) -{ - // 18.65.168.125 ( cloudfront ) 255 - // 157.254.246.178 128 - // 1.1.1.1 64 - // guess original ttl. consider path lengths less than 32 hops - - uint8_t orig; - - if (ttl>223) - orig=255; - else if (ttl<128 && ttl>96) - orig=128; - else if (ttl<64 && ttl>32) - orig=64; - else - return 0; - - return orig - ttl; -} -// return guessed fake ttl value. 0 means unsuccessfull, should not perform autottl fooling -uint8_t autottl_eval(uint8_t hop_count, const autottl *attl) -{ - uint8_t fake; - int d; - - d = (int)hop_count + attl->delta; - if (dmin) fake=attl->min; - else if (d>attl->max) fake=attl->max; - else fake=(uint8_t)d; - - if (attl->delta<0 && fake>=hop_count || attl->delta>=0 && fakesin_port; - if (bOutbound) - ip->ip_dst = target4->sin_addr; - else - ip->ip_src = target4->sin_addr; - ip4_fix_checksum(ip); - } - else if (ip6 && target6) - { - nport = target6->sin6_port; - if (bOutbound) - ip6->ip6_dst = target6->sin6_addr; - else - ip6->ip6_src = target6->sin6_addr; - } - else - return; - if (nport) - { - if (tcphdr) - { - if (bOutbound) - tcphdr->th_dport = nport; - else - tcphdr->th_sport = nport; - } - if (udphdr) - { - if (bOutbound) - udphdr->uh_dport = nport; - else - udphdr->uh_sport = nport; - } - } -} - - -void verdict_tcp_csum_fix(uint8_t verdict, struct tcphdr *tcphdr, size_t transport_len, struct ip *ip, struct ip6_hdr *ip6hdr) -{ - if (!(verdict & VERDICT_NOCSUM)) - { - #ifdef __CYGWIN__ - // always fix csum for windivert. original can be partial or bad - if ((verdict & VERDICT_MASK)!=VERDICT_DROP) - #elif defined(__FreeBSD__) - // FreeBSD tend to pass ipv6 frames with wrong checksum - if ((verdict & VERDICT_MASK)==VERDICT_MODIFY || ip6hdr) - #else - // if original packet was tampered earlier it needs checksum fixed - if ((verdict & VERDICT_MASK)==VERDICT_MODIFY) - #endif - tcp_fix_checksum(tcphdr,transport_len,ip,ip6hdr); - } -} -void verdict_udp_csum_fix(uint8_t verdict, struct udphdr *udphdr, size_t transport_len, struct ip *ip, struct ip6_hdr *ip6hdr) -{ - if (!(verdict & VERDICT_NOCSUM)) - { - #ifdef __CYGWIN__ - // always fix csum for windivert. original can be partial or bad - if ((verdict & VERDICT_MASK)!=VERDICT_DROP) - #elif defined(__FreeBSD__) - // FreeBSD tend to pass ipv6 frames with wrong checksum - if ((verdict & VERDICT_MASK)==VERDICT_MODIFY || ip6hdr) - #else - // if original packet was tampered earlier it needs checksum fixed - if ((verdict & VERDICT_MASK)==VERDICT_MODIFY) - #endif - udp_fix_checksum(udphdr,transport_len,ip,ip6hdr); - } -} - -void dbgprint_socket_buffers(int fd) -{ - if (params.debug) - { - int v; - socklen_t sz; - sz = sizeof(int); - if (!getsockopt(fd, SOL_SOCKET, SO_RCVBUF, &v, &sz)) - DLOG("fd=%d SO_RCVBUF=%d\n", fd, v); - sz = sizeof(int); - if (!getsockopt(fd, SOL_SOCKET, SO_SNDBUF, &v, &sz)) - DLOG("fd=%d SO_SNDBUF=%d\n", fd, v); - } -} -bool set_socket_buffers(int fd, int rcvbuf, int sndbuf) -{ - DLOG("set_socket_buffers fd=%d rcvbuf=%d sndbuf=%d\n", fd, rcvbuf, sndbuf); - if (rcvbuf && setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &rcvbuf, sizeof(int)) < 0) - { - DLOG_PERROR("setsockopt (SO_RCVBUF)"); - return false; - } - if (sndbuf && setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &sndbuf, sizeof(int)) < 0) - { - DLOG_PERROR("setsockopt (SO_SNDBUF)"); - return false; - } - dbgprint_socket_buffers(fd); - return true; -} diff --git a/nfq/darkmagic.h b/nfq/darkmagic.h deleted file mode 100644 index f8a0296d..00000000 --- a/nfq/darkmagic.h +++ /dev/null @@ -1,305 +0,0 @@ -#pragma once - -#include "nfqws.h" -#include "checksum.h" -#include "packet_queue.h" -#include "pools.h" - -#include -#include -#include -#include -#include -#include - -#define __FAVOR_BSD -#include -#include -#include -#include - -#ifndef IPV6_FREEBIND -#define IPV6_FREEBIND 78 -#endif - -#ifdef __CYGWIN__ -#define INITGUID -#include "windivert/windivert.h" -#endif - -#ifndef IPPROTO_DIVERT -#define IPPROTO_DIVERT 258 -#endif - -#ifndef AF_DIVERT -#define AF_DIVERT 44 /* divert(4) */ -#endif -#ifndef PF_DIVERT -#define PF_DIVERT AF_DIVERT -#endif - -// returns netorder value -uint32_t net32_add(uint32_t netorder_value, uint32_t cpuorder_increment); -uint32_t net16_add(uint16_t netorder_value, uint16_t cpuorder_increment); - -#define FOOL_NONE 0x00 -#define FOOL_MD5SIG 0x01 -#define FOOL_BADSUM 0x02 -#define FOOL_TS 0x04 -#define FOOL_BADSEQ 0x08 -#define FOOL_HOPBYHOP 0x10 -#define FOOL_HOPBYHOP2 0x20 -#define FOOL_DESTOPT 0x40 -#define FOOL_IPFRAG1 0x80 -#define FOOL_DATANOACK 0x100 - -#define SCALE_NONE ((uint8_t)-1) - -#define VERDICT_PASS 0 -#define VERDICT_MODIFY 1 -#define VERDICT_DROP 2 -#define VERDICT_MASK 3 -#define VERDICT_NOCSUM 4 -#define VERDICT_GARBAGE 8 - -#define IP4_TOS(ip_header) (ip_header ? ip_header->ip_tos : 0) -#define IP4_IP_ID(ip_header) (ip_header ? ip_header->ip_id : 0) -#define IP6_FLOW(ip6_header) (ip6_header ? ip6_header->ip6_ctlun.ip6_un1.ip6_un1_flow : 0) - -// seq and wsize have network byte order -bool prepare_tcp_segment4( - const struct sockaddr_in *src, const struct sockaddr_in *dst, - uint16_t tcp_flags, - bool sack, - uint16_t nmss, - uint32_t nseq, uint32_t nack_seq, - uint16_t nwsize, - uint8_t scale_factor, - uint32_t *timestamps, - bool DF, - uint8_t ttl, - uint8_t tos, - uint16_t ip_id, - uint32_t fooling, - uint32_t ts_increment, - uint32_t badseq_increment, - uint32_t badseq_ack_increment, - const void *data, uint16_t len, - uint8_t *buf, size_t *buflen); -bool prepare_tcp_segment6( - const struct sockaddr_in6 *src, const struct sockaddr_in6 *dst, - uint16_t tcp_flags, - bool sack, - uint16_t nmss, - uint32_t nseq, uint32_t nack_seq, - uint16_t nwsize, - uint8_t scale_factor, - uint32_t *timestamps, - uint8_t ttl, - uint32_t flow_label, - uint32_t fooling, - uint32_t ts_increment, - uint32_t badseq_increment, - uint32_t badseq_ack_increment, - const void *data, uint16_t len, - uint8_t *buf, size_t *buflen); -bool prepare_tcp_segment( - const struct sockaddr *src, const struct sockaddr *dst, - uint16_t tcp_flags, - bool sack, - uint16_t nmss, - uint32_t nseq, uint32_t nack_seq, - uint16_t nwsize, - uint8_t scale_factor, - uint32_t *timestamps, - bool DF, - uint8_t ttl, - uint8_t tos, - uint16_t ip_id, - uint32_t flow_label, - uint32_t fooling, - uint32_t ts_increment, - uint32_t badseq_increment, - uint32_t badseq_ack_increment, - const void *data, uint16_t len, - uint8_t *buf, size_t *buflen); - - -bool prepare_udp_segment4( - const struct sockaddr_in *src, const struct sockaddr_in *dst, - bool DF, - uint8_t ttl, - uint8_t tos, - uint16_t ip_id, - uint32_t fooling, - const uint8_t *padding, size_t padding_size, - int padlen, - const void *data, uint16_t len, - uint8_t *buf, size_t *buflen); -bool prepare_udp_segment6( - const struct sockaddr_in6 *src, const struct sockaddr_in6 *dst, - uint8_t ttl, - uint32_t flow_label, - uint32_t fooling, - const uint8_t *padding, size_t padding_size, - int padlen, - const void *data, uint16_t len, - uint8_t *buf, size_t *buflen); -bool prepare_udp_segment( - const struct sockaddr *src, const struct sockaddr *dst, - bool DF, - uint8_t ttl, - uint8_t tos, - uint16_t ip_id, - uint32_t flow_label, - uint32_t fooling, - const uint8_t *padding, size_t padding_size, - int padlen, - const void *data, uint16_t len, - uint8_t *buf, size_t *buflen); - -bool ip6_insert_simple_hdr(uint8_t type, uint8_t *data_pkt, size_t len_pkt, uint8_t *buf, size_t *buflen); - -// ipv4: ident==-1 - copy ip_id from original ipv4 packet -bool ip_frag4( - const uint8_t *pkt, size_t pkt_size, - size_t frag_pos, uint32_t ident, - uint8_t *pkt1, size_t *pkt1_size, - uint8_t *pkt2, size_t *pkt2_size); -bool ip_frag6( - const uint8_t *pkt, size_t pkt_size, - size_t frag_pos, uint32_t ident, - uint8_t *pkt1, size_t *pkt1_size, - uint8_t *pkt2, size_t *pkt2_size); -bool ip_frag( - const uint8_t *pkt, size_t pkt_size, - size_t frag_pos, uint32_t ident, - uint8_t *pkt1, size_t *pkt1_size, - uint8_t *pkt2, size_t *pkt2_size); - -bool rewrite_ttl(struct ip *ip, struct ip6_hdr *ip6, uint8_t ttl); -uint16_t get_tcp_flags(const struct tcphdr *tcp); -void apply_tcp_flags(struct tcphdr *tcp, uint16_t fl); - -void extract_ports(const struct tcphdr *tcphdr, const struct udphdr *udphdr, uint8_t *proto, uint16_t *sport, uint16_t *dport); -void extract_endpoints(const struct ip *ip,const struct ip6_hdr *ip6hdr,const struct tcphdr *tcphdr,const struct udphdr *udphdr, struct sockaddr_storage *src, struct sockaddr_storage *dst); -uint8_t *tcp_find_option(struct tcphdr *tcp, uint8_t kind); -uint32_t *tcp_find_timestamps(struct tcphdr *tcp); -uint8_t tcp_find_scale_factor(const struct tcphdr *tcp); -uint16_t tcp_find_mss(struct tcphdr *tcp); -bool tcp_has_sack(struct tcphdr *tcp); - -bool tcp_has_fastopen(const struct tcphdr *tcp); - -bool ip_has_df(const struct ip *ip); - -#ifdef __CYGWIN__ -extern uint32_t w_win32_error; - -bool win_dark_init(const struct str_list_head *ssid_filter, const struct str_list_head *nlm_filter); -bool win_dark_deinit(void); -bool logical_net_filter_match(void); -bool nlm_list(bool bAll); -bool windivert_init(const char *filter); -bool windivert_recv(uint8_t *packet, size_t *len, WINDIVERT_ADDRESS *wa); -bool windivert_send(const uint8_t *packet, size_t len, const WINDIVERT_ADDRESS *wa); -#else -// should pre-do it if dropping privileges. otherwise its not necessary -bool rawsend_preinit(bool bind_fix4, bool bind_fix6); -#endif - -// auto creates internal socket and uses it for subsequent calls -bool rawsend(const struct sockaddr* dst,uint32_t fwmark,const char *ifout,const void *data,size_t len); -bool rawsend_rp(const struct rawpacket *rp); -// return trues if all packets were send successfully -bool rawsend_queue(struct rawpacket_tailhead *q); -// cleans up socket autocreated by rawsend -void rawsend_cleanup(void); - -#ifdef BSD -int socket_divert(sa_family_t family); -#endif - -const char *proto_name(uint8_t proto); -uint16_t family_from_proto(uint8_t l3proto); -void print_ip(const struct ip *ip); -void print_ip6hdr(const struct ip6_hdr *ip6hdr, uint8_t proto); -void print_tcphdr(const struct tcphdr *tcphdr); -void print_udphdr(const struct udphdr *udphdr); -void str_ip(char *s, size_t s_len, const struct ip *ip); -void str_ip6hdr(char *s, size_t s_len, const struct ip6_hdr *ip6hdr, uint8_t proto); -void str_srcdst_ip6(char *s, size_t s_len, const void *saddr,const void *daddr); -void str_tcphdr(char *s, size_t s_len, const struct tcphdr *tcphdr); -void str_udphdr(char *s, size_t s_len, const struct udphdr *udphdr); - -bool proto_check_ipv4(const uint8_t *data, size_t len); -void proto_skip_ipv4(uint8_t **data, size_t *len); -bool proto_check_ipv6(const uint8_t *data, size_t len); -void proto_skip_ipv6(uint8_t **data, size_t *len, uint8_t *proto_type, uint8_t **last_header_type); -bool proto_check_tcp(const uint8_t *data, size_t len); -void proto_skip_tcp(uint8_t **data, size_t *len); -bool proto_check_udp(const uint8_t *data, size_t len); -void proto_skip_udp(uint8_t **data, size_t *len); -struct dissect -{ - uint8_t *data_pkt; - size_t len_pkt; - struct ip *ip; - struct ip6_hdr *ip6; - uint8_t proto; - struct tcphdr *tcp; - struct udphdr *udp; - size_t transport_len; - uint8_t *data_payload; - size_t len_payload; -}; -void proto_dissect_l3l4(uint8_t *data, size_t len,struct dissect *dis); - -bool tcp_synack_segment(const struct tcphdr *tcphdr); -bool tcp_syn_segment(const struct tcphdr *tcphdr); -bool tcp_ack_segment(const struct tcphdr *tcphdr); -// scale_factor=SCALE_NONE - do not change -void tcp_rewrite_wscale(struct tcphdr *tcp, uint8_t scale_factor); -void tcp_rewrite_winsize(struct tcphdr *tcp, uint16_t winsize, uint8_t scale_factor); - -typedef struct -{ - int8_t delta; - uint8_t min, max; -} autottl; -#define AUTOTTL_ENABLED(a) ((a).delta || (a).min || (a).max) - -uint8_t hop_count_guess(uint8_t ttl); -uint8_t autottl_eval(uint8_t hop_count, const autottl *attl); -void do_nat(bool bOutbound, struct ip *ip, struct ip6_hdr *ip6, struct tcphdr *tcphdr, struct udphdr *udphdr, const struct sockaddr_in *target4, const struct sockaddr_in6 *target6); - -void verdict_tcp_csum_fix(uint8_t verdict, struct tcphdr *tcphdr, size_t transport_len, struct ip *ip, struct ip6_hdr *ip6hdr); -void verdict_udp_csum_fix(uint8_t verdict, struct udphdr *udphdr, size_t transport_len, struct ip *ip, struct ip6_hdr *ip6hdr); - -void dbgprint_socket_buffers(int fd); -bool set_socket_buffers(int fd, int rcvbuf, int sndbuf); - - -#ifdef HAS_FILTER_SSID - -struct wlan_interface -{ - int ifindex; - char ifname[IFNAMSIZ], ssid[33]; -}; -#define WLAN_INTERFACE_MAX 16 -struct wlan_interface_collection -{ - int count; - struct wlan_interface wlan[WLAN_INTERFACE_MAX]; -}; - -extern struct wlan_interface_collection wlans; - -void wlan_info_deinit(void); -bool wlan_info_init(void); -bool wlan_info_get_rate_limited(void); -const char *wlan_ssid_search_ifname(const char *ifname); -const char *wlan_ssid_search_ifidx(int ifidx); - -#endif diff --git a/nfq/desync.c b/nfq/desync.c deleted file mode 100644 index 0fdccd15..00000000 --- a/nfq/desync.c +++ /dev/null @@ -1,3504 +0,0 @@ -#define _GNU_SOURCE - -#include -#include - -#include "desync.h" -#include "protocol.h" -#include "params.h" -#include "helpers.h" -#include "hostlist.h" -#include "ipset.h" -#include "conntrack.h" - -const char *fake_http_request_default = "GET / HTTP/1.1\r\nHost: www.iana.org\r\n" -"User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/109.0\r\n" -"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8\r\n" -"Accept-Encoding: gzip, deflate, br\r\n\r\n"; - -// SNI - www.microsoft.com -const uint8_t fake_tls_clienthello_default[680] = { - 0x16, 0x03, 0x01, 0x02, 0xa3, 0x01, 0x00, 0x02, 0x9f, 0x03, 0x03, 0x41, - 0x88, 0x82, 0x2d, 0x4f, 0xfd, 0x81, 0x48, 0x9e, 0xe7, 0x90, 0x65, 0x1f, - 0xba, 0x05, 0x7b, 0xff, 0xa7, 0x5a, 0xf9, 0x5b, 0x8a, 0x8f, 0x45, 0x8b, - 0x41, 0xf0, 0x3d, 0x1b, 0xdd, 0xe3, 0xf8, 0x20, 0x9b, 0x23, 0xa5, 0xd2, - 0x21, 0x1e, 0x9f, 0xe7, 0x85, 0x6c, 0xfc, 0x61, 0x80, 0x3a, 0x3f, 0xba, - 0xb9, 0x60, 0xba, 0xb3, 0x0e, 0x98, 0x27, 0x6c, 0xf7, 0x38, 0x28, 0x65, - 0x80, 0x5d, 0x40, 0x38, 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, 0x9c, 0x00, 0x9d, - 0x00, 0x2f, 0x00, 0x35, 0x01, 0x00, 0x02, 0x34, 0x00, 0x00, 0x00, 0x16, - 0x00, 0x14, 0x00, 0x00, 0x11, 0x77, 0x77, 0x77, 0x2e, 0x6d, 0x69, 0x63, - 0x72, 0x6f, 0x73, 0x6f, 0x66, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 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, 0x22, 0x00, 0x0a, 0x00, 0x08, 0x04, 0x03, 0x05, 0x03, - 0x06, 0x03, 0x02, 0x03, 0x00, 0x12, 0x00, 0x00, 0x00, 0x33, 0x00, 0x6b, - 0x00, 0x69, 0x00, 0x1d, 0x00, 0x20, 0x69, 0x15, 0x16, 0x29, 0x6d, 0xad, - 0xd5, 0x68, 0x88, 0x27, 0x2f, 0xde, 0xaf, 0xac, 0x3c, 0x4c, 0xa4, 0xe4, - 0xd8, 0xc8, 0xfb, 0x41, 0x87, 0xf4, 0x76, 0x4e, 0x0e, 0xfa, 0x64, 0xc4, - 0xe9, 0x29, 0x00, 0x17, 0x00, 0x41, 0x04, 0xfe, 0x62, 0xb9, 0x08, 0xc8, - 0xc3, 0x2a, 0xb9, 0x87, 0x37, 0x84, 0x42, 0x6b, 0x5c, 0xcd, 0xc9, 0xca, - 0x62, 0x38, 0xd3, 0xd9, 0x99, 0x8a, 0xc4, 0x2d, 0xc6, 0xd0, 0xa3, 0x60, - 0xb2, 0x12, 0x54, 0x41, 0x8e, 0x52, 0x5e, 0xe3, 0xab, 0xf9, 0xc2, 0x07, - 0x81, 0xdc, 0xf8, 0xf2, 0x6a, 0x91, 0x40, 0x2f, 0xcb, 0xa4, 0xff, 0x6f, - 0x24, 0xc7, 0x4d, 0x77, 0x77, 0x2d, 0x6f, 0xe0, 0x77, 0xaa, 0x92, 0x00, - 0x2b, 0x00, 0x05, 0x04, 0x03, 0x04, 0x03, 0x03, 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, 0x1b, 0x00, 0x07, 0x06, 0x00, 0x01, 0x00, 0x02, 0x00, 0x03, 0xfe, - 0x0d, 0x01, 0x19, 0x00, 0x00, 0x01, 0x00, 0x03, 0x21, 0x00, 0x20, 0x62, - 0xe8, 0x83, 0xd8, 0x97, 0x05, 0x8a, 0xbe, 0xa1, 0xf2, 0x63, 0x4e, 0xce, - 0x93, 0x84, 0x8e, 0xcf, 0xe7, 0xdd, 0xb2, 0xe4, 0x87, 0x06, 0xac, 0x11, - 0x19, 0xbe, 0x0e, 0x71, 0x87, 0xf1, 0xa6, 0x00, 0xef, 0xd8, 0x6b, 0x27, - 0x5e, 0xc0, 0xa7, 0x5d, 0x42, 0x4e, 0x8c, 0xdc, 0xf3, 0x9f, 0x1c, 0x51, - 0x62, 0xef, 0xff, 0x5b, 0xed, 0xc8, 0xfd, 0xee, 0x6f, 0xbb, 0x88, 0x9b, - 0xb1, 0x30, 0x9c, 0x66, 0x42, 0xab, 0x0f, 0x66, 0x89, 0x18, 0x8b, 0x11, - 0xc1, 0x6d, 0xe7, 0x2a, 0xeb, 0x96, 0x3b, 0x7f, 0x52, 0x78, 0xdb, 0xf8, - 0x6d, 0x04, 0xf7, 0x95, 0x1a, 0xa8, 0xf0, 0x64, 0x52, 0x07, 0x39, 0xf0, - 0xa8, 0x1d, 0x0d, 0x16, 0x36, 0xb7, 0x18, 0x0e, 0xc8, 0x44, 0x27, 0xfe, - 0xf3, 0x31, 0xf0, 0xde, 0x8c, 0x74, 0xf5, 0xa1, 0xd8, 0x8f, 0x6f, 0x45, - 0x97, 0x69, 0x79, 0x5e, 0x2e, 0xd4, 0xb0, 0x2c, 0x0c, 0x1a, 0x6f, 0xcc, - 0xce, 0x90, 0xc7, 0xdd, 0xc6, 0x60, 0x95, 0xf3, 0xc2, 0x19, 0xde, 0x50, - 0x80, 0xbf, 0xde, 0xf2, 0x25, 0x63, 0x15, 0x26, 0x63, 0x09, 0x1f, 0xc5, - 0xdf, 0x32, 0xf5, 0xea, 0x9c, 0xd2, 0xff, 0x99, 0x4e, 0x67, 0xa2, 0xe5, - 0x1a, 0x94, 0x85, 0xe3, 0xdf, 0x36, 0xa5, 0x83, 0x4b, 0x0a, 0x1c, 0xaf, - 0xd7, 0x48, 0xc9, 0x4b, 0x8a, 0x27, 0xdd, 0x58, 0x7f, 0x95, 0xf2, 0x6b, - 0xde, 0x2b, 0x12, 0xd3, 0xec, 0x4d, 0x69, 0x37, 0x9c, 0x13, 0x9b, 0x16, - 0xb0, 0x45, 0x52, 0x38, 0x77, 0x69, 0xef, 0xaa, 0x65, 0x19, 0xbc, 0xc2, - 0x93, 0x4d, 0xb0, 0x1b, 0x7f, 0x5b, 0x41, 0xff, 0xaf, 0xba, 0x50, 0x51, - 0xc3, 0xf1, 0x27, 0x09, 0x25, 0xf5, 0x60, 0x90, 0x09, 0xb1, 0xe5, 0xc0, - 0xc7, 0x42, 0x78, 0x54, 0x3b, 0x23, 0x19, 0x7d, 0x8e, 0x72, 0x13, 0xb4, - 0xd3, 0xcd, 0x63, 0xb6, 0xc4, 0x4a, 0x28, 0x3d, 0x45, 0x3e, 0x8b, 0xdb, - 0x84, 0x4f, 0x78, 0x64, 0x30, 0x69, 0xe2, 0x1b -}; - -#define PKTDATA_MAXDUMP 32 -#define IP_MAXDUMP 80 - -#define TCP_MAX_REASM 16384 -#define UDP_MAX_REASM 16384 - -static void TLSDebugHandshake(const uint8_t *tls, size_t sz) -{ - if (!params.debug) return; - - if (sz < 6) return; - - const uint8_t *ext; - size_t len, len2; - - uint16_t v_handshake = pntoh16(tls + 4), v, v2; - DLOG("TLS handshake version : %s\n", TLSVersionStr(v_handshake)); - - if (TLSFindExtInHandshake(tls, sz, 43, &ext, &len, false)) - { - if (len) - { - len2 = ext[0]; - if (len2 < len) - { - for (ext++, len2 &= ~1; len2; len2 -= 2, ext += 2) - { - v = pntoh16(ext); - DLOG("TLS supported versions ext : %s\n", TLSVersionStr(v)); - } - } - } - } - else - DLOG("TLS supported versions ext : not present\n"); - - if (TLSFindExtInHandshake(tls, sz, 16, &ext, &len, false)) - { - if (len >= 2) - { - len2 = pntoh16(ext); - if (len2 <= (len - 2)) - { - char s[32]; - for (ext += 2; len2;) - { - v = *ext; ext++; len2--; - if (v <= len2) - { - v2 = v < sizeof(s) ? v : sizeof(s) - 1; - memcpy(s, ext, v2); - s[v2] = 0; - DLOG("TLS ALPN ext : %s\n", s); - len2 -= v; - ext += v; - } - else - break; - } - } - } - } - else - DLOG("TLS ALPN ext : not present\n"); - - DLOG("TLS ECH ext : %s\n", TLSFindExtInHandshake(tls, sz, 65037, NULL, NULL, false) ? "present" : "not present"); -} -static void TLSDebug(const uint8_t *tls, size_t sz) -{ - if (!params.debug) return; - - if (sz < 11) return; - - DLOG("TLS record layer version : %s\n", TLSVersionStr(pntoh16(tls + 1))); - - size_t reclen = TLSRecordLen(tls); - if (reclen < sz) sz = reclen; // correct len if it has more data than the first tls record has - - TLSDebugHandshake(tls + 5, sz - 5); -} - -bool desync_valid_zero_stage(enum dpi_desync_mode mode) -{ - return mode == DESYNC_SYNACK || mode == DESYNC_SYNDATA; -} -bool desync_valid_first_stage(enum dpi_desync_mode mode) -{ - return mode == DESYNC_FAKE || mode == DESYNC_FAKE_KNOWN || mode == DESYNC_RST || mode == DESYNC_RSTACK || mode == DESYNC_HOPBYHOP || mode == DESYNC_DESTOPT || mode == DESYNC_IPFRAG1; -} -bool desync_only_first_stage(enum dpi_desync_mode mode) -{ - return false; -} -bool desync_valid_second_stage_tcp(enum dpi_desync_mode mode) -{ - return - mode == DESYNC_NONE || mode == DESYNC_FAKEDDISORDER || mode == DESYNC_FAKEDSPLIT || mode == DESYNC_MULTISPLIT || mode == DESYNC_MULTIDISORDER || mode == DESYNC_HOSTFAKESPLIT || - mode == DESYNC_IPFRAG2; -} -bool desync_valid_second_stage_udp(enum dpi_desync_mode mode) -{ - return mode == DESYNC_NONE || mode == DESYNC_UDPLEN || mode == DESYNC_TAMPER || mode == DESYNC_IPFRAG2; -} -bool desync_valid_second_stage(enum dpi_desync_mode mode) -{ - return desync_valid_second_stage_tcp(mode) || desync_valid_second_stage_udp(mode); -} -enum dpi_desync_mode desync_mode_from_string(const char *s) -{ - if (!s) - return DESYNC_NONE; - else if (!strcmp(s, "fake")) - return DESYNC_FAKE; - else if (!strcmp(s, "fakeknown")) - return DESYNC_FAKE_KNOWN; - else if (!strcmp(s, "rst")) - return DESYNC_RST; - else if (!strcmp(s, "rstack")) - return DESYNC_RSTACK; - else if (!strcmp(s, "synack")) - return DESYNC_SYNACK; - else if (!strcmp(s, "syndata")) - return DESYNC_SYNDATA; - else if (!strcmp(s, "fakeddisorder") || !strcmp(s, "disorder")) - return DESYNC_FAKEDDISORDER; - else if (!strcmp(s, "fakedsplit") || !strcmp(s, "split")) - return DESYNC_FAKEDSPLIT; - else if (!strcmp(s, "multisplit") || !strcmp(s, "split2")) - return DESYNC_MULTISPLIT; - else if (!strcmp(s, "multidisorder") || !strcmp(s, "disorder2")) - return DESYNC_MULTIDISORDER; - else if (!strcmp(s, "hostfakesplit")) - return DESYNC_HOSTFAKESPLIT; - else if (!strcmp(s, "ipfrag2")) - return DESYNC_IPFRAG2; - else if (!strcmp(s, "hopbyhop")) - return DESYNC_HOPBYHOP; - else if (!strcmp(s, "destopt")) - return DESYNC_DESTOPT; - else if (!strcmp(s, "ipfrag1")) - return DESYNC_IPFRAG1; - else if (!strcmp(s, "udplen")) - return DESYNC_UDPLEN; - else if (!strcmp(s, "tamper")) - return DESYNC_TAMPER; - return DESYNC_INVALID; -} - -static bool dp_match( - struct desync_profile *dp, - uint8_t l3proto, const struct sockaddr *dest, const char *hostname, bool bNoSubdom, t_l7proto l7proto, const char *ssid, - bool *bCheckDone, bool *bCheckResult, bool *bExcluded) -{ - bool bHostlistsEmpty; - - if (bCheckDone) *bCheckDone = false; - - if (!HostlistsReloadCheckForProfile(dp)) return false; - - if ((dest->sa_family == AF_INET && !dp->filter_ipv4) || (dest->sa_family == AF_INET6 && !dp->filter_ipv6)) - // L3 filter does not match - return false; - if ((l3proto == IPPROTO_TCP && !port_filters_in_range(&dp->pf_tcp, saport(dest))) || (l3proto == IPPROTO_UDP && !port_filters_in_range(&dp->pf_udp, saport(dest)))) - // L4 filter does not match - return false; - if (dp->filter_l7 && !l7_proto_match(l7proto, dp->filter_l7)) - // L7 filter does not match - return false; -#ifdef HAS_FILTER_SSID - if (!LIST_EMPTY(&dp->filter_ssid) && !strlist_search(&dp->filter_ssid, ssid)) - return false; -#endif - - bHostlistsEmpty = PROFILE_HOSTLISTS_EMPTY(dp); - if (!dp->hostlist_auto && !hostname && !bHostlistsEmpty) - // avoid cpu consuming ipset check. profile cannot win if regular hostlists are present without auto hostlist and hostname is unknown. - return false; - if (!IpsetCheck(dp, dest->sa_family == AF_INET ? &((struct sockaddr_in*)dest)->sin_addr : NULL, dest->sa_family == AF_INET6 ? &((struct sockaddr_in6*)dest)->sin6_addr : NULL)) - // target ip does not match - return false; - - // autohostlist profile matching l3/l4/l7 filter always win - if (dp->hostlist_auto) return true; - - if (bHostlistsEmpty) - // profile without hostlist filter wins - return true; - else - { - // if hostlists are present profile matches only if hostname is known and satisfy profile hostlists - if (hostname) - { - if (bCheckDone) *bCheckDone = true; - bool b; - b = HostlistCheck(dp, hostname, bNoSubdom, bExcluded, true); - if (bCheckResult) *bCheckResult = b; - return b; - } - } - return false; -} -static struct desync_profile *dp_find( - struct desync_profile_list_head *head, - uint8_t l3proto, const struct sockaddr *dest, const char *hostname, bool bNoSubdom, t_l7proto l7proto, const char *ssid, - bool *bCheckDone, bool *bCheckResult, bool *bExcluded) -{ - struct desync_profile_list *dpl; - if (params.debug) - { - char ip_port[48]; - ntop46_port(dest, ip_port, sizeof(ip_port)); - DLOG("desync profile search for %s target=%s l7proto=%s ssid='%s' hostname='%s'\n", proto_name(l3proto), ip_port, l7proto_str(l7proto), ssid ? ssid : "", hostname ? hostname : ""); - } - if (bCheckDone) *bCheckDone = false; - LIST_FOREACH(dpl, head, next) - { - if (dp_match(&dpl->dp, l3proto, dest, hostname, bNoSubdom, l7proto, ssid, bCheckDone, bCheckResult, bExcluded)) - { - DLOG("desync profile %d matches\n", dpl->dp.n); - return &dpl->dp; - } - } - DLOG("desync profile not found\n"); - return NULL; -} - -// auto creates internal socket and uses it for subsequent calls -static bool rawsend_rep(int repeats, const struct sockaddr* dst, uint32_t fwmark, const char *ifout, const void *data, size_t len) -{ - for (int i = 0; i < repeats; i++) - if (!rawsend(dst, fwmark, ifout, data, len)) - return false; - return true; -} - - -static uint64_t cutoff_get_limit(const t_ctrack *ctrack, char mode) -{ - switch (mode) - { - case 'n': return ctrack->pcounter_orig; - case 'd': return ctrack->pdcounter_orig; - case 's': return ctrack->seq_last - ctrack->seq0; - default: return 0; - } -} -static bool cutoff_test(const t_ctrack *ctrack, uint64_t cutoff, char mode) -{ - return cutoff && cutoff_get_limit(ctrack, mode) >= cutoff; -} -static void maybe_cutoff(t_ctrack *ctrack, uint8_t proto) -{ - if (ctrack && ctrack->dp) - { - if (proto == IPPROTO_TCP) - ctrack->b_wssize_cutoff |= cutoff_test(ctrack, ctrack->dp->wssize_cutoff, ctrack->dp->wssize_cutoff_mode); - ctrack->b_desync_cutoff |= cutoff_test(ctrack, ctrack->dp->desync_cutoff, ctrack->dp->desync_cutoff_mode); - ctrack->b_dup_cutoff |= cutoff_test(ctrack, ctrack->dp->dup_cutoff, ctrack->dp->dup_cutoff_mode); - ctrack->b_orig_mod_cutoff |= cutoff_test(ctrack, ctrack->dp->orig_mod_cutoff, ctrack->dp->orig_mod_cutoff_mode); - - // in MULTI STRATEGY concept conntrack entry holds desync profile - // we do not want to remove conntrack entries ASAP anymore - - /* - // we do not need conntrack entry anymore if all cutoff conditions are either not defined or reached - // do not drop udp entry because it will be recreated when next packet arrives - if (proto==IPPROTO_TCP) - ctrack->b_cutoff |= \ - (!ctrack->dp->wssize || ctrack->b_wssize_cutoff) && - (!ctrack->dp->desync_cutoff || ctrack->b_desync_cutoff) && - (!ctrack->hostname_ah_check || ctrack->req_retrans_counter==RETRANS_COUNTER_STOP) && - ReasmIsEmpty(&ctrack->reasm_orig); - */ - } -} -static void wssize_cutoff(t_ctrack *ctrack) -{ - if (ctrack) - { - ctrack->b_wssize_cutoff = true; - maybe_cutoff(ctrack, IPPROTO_TCP); - } -} -static void forced_wssize_cutoff(t_ctrack *ctrack) -{ - if (ctrack && ctrack->dp && !ctrack->b_wssize_cutoff) - { - DLOG("forced wssize-cutoff\n"); - wssize_cutoff(ctrack); - } -} - -static void ctrack_stop_retrans_counter(t_ctrack *ctrack) -{ - if (ctrack && ctrack->hostname_ah_check) - { - ctrack->req_retrans_counter = RETRANS_COUNTER_STOP; - maybe_cutoff(ctrack, IPPROTO_TCP); - } -} - -static void auto_hostlist_reset_fail_counter(struct desync_profile *dp, const char *hostname, const char *client_ip_port, t_l7proto l7proto) -{ - if (hostname) - { - hostfail_pool *fail_counter; - - fail_counter = HostFailPoolFind(dp->hostlist_auto_fail_counters, hostname); - if (fail_counter) - { - HostFailPoolDel(&dp->hostlist_auto_fail_counters, fail_counter); - DLOG("auto hostlist (profile %d) : %s : fail counter reset. website is working.\n", dp->n, hostname); - HOSTLIST_DEBUGLOG_APPEND("%s : profile %d : client %s : proto %s : fail counter reset. website is working.", hostname, dp->n, client_ip_port, l7proto_str(l7proto)); - } - } -} - -// return true if retrans trigger fires -static bool auto_hostlist_retrans(t_ctrack *ctrack, uint8_t l4proto, int threshold, const char *client_ip_port, t_l7proto l7proto) -{ - if (ctrack && ctrack->dp && ctrack->hostname_ah_check && ctrack->req_retrans_counter != RETRANS_COUNTER_STOP) - { - if (l4proto == IPPROTO_TCP) - { - if (!ctrack->req_seq_finalized || ctrack->req_seq_abandoned) - return false; - if (!seq_within(ctrack->seq_last, ctrack->req_seq_start, ctrack->req_seq_end)) - { - DLOG("req retrans : tcp seq %u not within the req range %u-%u. stop tracking.\n", ctrack->seq_last, ctrack->req_seq_start, ctrack->req_seq_end); - ctrack_stop_retrans_counter(ctrack); - auto_hostlist_reset_fail_counter(ctrack->dp, ctrack->hostname, client_ip_port, l7proto); - return false; - } - } - ctrack->req_retrans_counter++; - if (ctrack->req_retrans_counter >= threshold) - { - DLOG("req retrans threshold reached : %u/%u\n", ctrack->req_retrans_counter, threshold); - ctrack_stop_retrans_counter(ctrack); - return true; - } - DLOG("req retrans counter : %u/%u\n", ctrack->req_retrans_counter, threshold); - } - return false; -} -static void auto_hostlist_failed(struct desync_profile *dp, const char *hostname, bool bNoSubdom, const char *client_ip_port, t_l7proto l7proto) -{ - hostfail_pool *fail_counter; - - fail_counter = HostFailPoolFind(dp->hostlist_auto_fail_counters, hostname); - if (!fail_counter) - { - fail_counter = HostFailPoolAdd(&dp->hostlist_auto_fail_counters, hostname, dp->hostlist_auto_fail_time); - if (!fail_counter) - { - DLOG_ERR("HostFailPoolAdd: out of memory\n"); - return; - } - } - fail_counter->counter++; - DLOG("auto hostlist (profile %d) : %s : fail counter %d/%d\n", dp->n, hostname, fail_counter->counter, dp->hostlist_auto_fail_threshold); - HOSTLIST_DEBUGLOG_APPEND("%s : profile %d : client %s : proto %s : fail counter %d/%d", hostname, dp->n, client_ip_port, l7proto_str(l7proto), fail_counter->counter, dp->hostlist_auto_fail_threshold); - if (fail_counter->counter >= dp->hostlist_auto_fail_threshold) - { - DLOG("auto hostlist (profile %d) : fail threshold reached. about to add %s to auto hostlist\n", dp->n, hostname); - HostFailPoolDel(&dp->hostlist_auto_fail_counters, fail_counter); - - DLOG("auto hostlist (profile %d) : rechecking %s to avoid duplicates\n", dp->n, hostname); - bool bExcluded = false; - if (!HostlistCheck(dp, hostname, bNoSubdom, &bExcluded, false) && !bExcluded) - { - DLOG("auto hostlist (profile %d) : adding %s to %s\n", dp->n, hostname, dp->hostlist_auto->filename); - HOSTLIST_DEBUGLOG_APPEND("%s : profile %d : client %s : proto %s : adding to %s", hostname, dp->n, client_ip_port, l7proto_str(l7proto), dp->hostlist_auto->filename); - if (!HostlistPoolAddStr(&dp->hostlist_auto->hostlist, hostname, 0)) - { - DLOG_ERR("StrPoolAddStr out of memory\n"); - return; - } - if (!append_to_list_file(dp->hostlist_auto->filename, hostname)) - { - DLOG_PERROR("write to auto hostlist"); - return; - } - if (!file_mod_signature(dp->hostlist_auto->filename, &dp->hostlist_auto->mod_sig)) - DLOG_PERROR("file_mod_signature"); - } - else - { - DLOG("auto hostlist (profile %d) : NOT adding %s\n", dp->n, hostname); - HOSTLIST_DEBUGLOG_APPEND("%s : profile %d : client %s : proto %s : NOT adding, duplicate detected", hostname, dp->n, client_ip_port, l7proto_str(l7proto)); - } - } -} - -static void process_retrans_fail(t_ctrack *ctrack, uint8_t proto, const struct sockaddr *client) -{ - char client_ip_port[48]; - if (*params.hostlist_auto_debuglog) - ntop46_port((struct sockaddr*)client, client_ip_port, sizeof(client_ip_port)); - else - *client_ip_port = 0; - if (ctrack && ctrack->dp && ctrack->hostname && auto_hostlist_retrans(ctrack, proto, ctrack->dp->hostlist_auto_retrans_threshold, client_ip_port, ctrack->l7proto)) - { - HOSTLIST_DEBUGLOG_APPEND("%s : profile %d : client %s : proto %s : retrans threshold reached", ctrack->hostname, ctrack->dp->n, client_ip_port, l7proto_str(ctrack->l7proto)); - auto_hostlist_failed(ctrack->dp, ctrack->hostname, ctrack->hostname_is_ip, client_ip_port, ctrack->l7proto); - } -} - -static bool send_delayed(t_ctrack *ctrack) -{ - if (!rawpacket_queue_empty(&ctrack->delayed)) - { - DLOG("SENDING %u delayed packets\n", rawpacket_queue_count(&ctrack->delayed)); - return rawsend_queue(&ctrack->delayed); - } - return true; -} - - -static bool reasm_start(t_ctrack *ctrack, t_reassemble *reasm, uint8_t proto, size_t sz, size_t szMax, const uint8_t *data_payload, size_t len_payload) -{ - ReasmClear(reasm); - if (sz <= szMax) - { - uint32_t seq = (proto == IPPROTO_TCP) ? ctrack->seq_last : 0; - if (ReasmInit(reasm, sz, seq)) - { - ReasmFeed(reasm, seq, data_payload, len_payload); - DLOG("starting reassemble. now we have %zu/%zu\n", reasm->size_present, reasm->size); - return true; - } - else - DLOG("reassemble init failed. out of memory\n"); - } - else - DLOG("unexpected large payload for reassemble: size=%zu\n", sz); - return false; -} -static bool reasm_orig_start(t_ctrack *ctrack, uint8_t proto, size_t sz, size_t szMax, const uint8_t *data_payload, size_t len_payload) -{ - return reasm_start(ctrack, &ctrack->reasm_orig, proto, sz, szMax, data_payload, len_payload); -} -static bool reasm_feed(t_ctrack *ctrack, t_reassemble *reasm, uint8_t proto, const uint8_t *data_payload, size_t len_payload) -{ - if (ctrack && !ReasmIsEmpty(reasm)) - { - uint32_t seq = (proto == IPPROTO_TCP) ? ctrack->seq_last : (uint32_t)reasm->size_present; - if (ReasmFeed(reasm, seq, data_payload, len_payload)) - { - DLOG("reassemble : feeding data payload size=%zu. now we have %zu/%zu\n", len_payload, reasm->size_present, reasm->size); - return true; - } - else - { - ReasmClear(reasm); - DLOG("reassemble session failed\n"); - send_delayed(ctrack); - } - } - return false; -} -static bool reasm_orig_feed(t_ctrack *ctrack, uint8_t proto, const uint8_t *data_payload, size_t len_payload) -{ - return reasm_feed(ctrack, &ctrack->reasm_orig, proto, data_payload, len_payload); -} -static void reasm_orig_stop(t_ctrack *ctrack, const char *dlog_msg) -{ - if (ctrack) - { - if (!ReasmIsEmpty(&ctrack->reasm_orig)) - { - DLOG("%s", dlog_msg); - ReasmClear(&ctrack->reasm_orig); - } - send_delayed(ctrack); - } -} -static void reasm_orig_cancel(t_ctrack *ctrack) -{ - reasm_orig_stop(ctrack, "reassemble session cancelled\n"); -} -static void reasm_orig_fin(t_ctrack *ctrack) -{ - reasm_orig_stop(ctrack, "reassemble session finished\n"); -} - - -static uint8_t ct_new_postnat_fix(const t_ctrack *ctrack, struct ip *ip, struct ip6_hdr *ip6, const struct tcphdr *tcp) -{ -#ifdef __linux__ - // if used in postnat chain, dropping initial packet will cause conntrack connection teardown - // so we need to workaround this. - // SYN and SYN,ACK checks are for conntrack-less mode - if (ctrack && ctrack->pcounter_orig == 1 || tcp && (tcp_syn_segment(tcp) || tcp_synack_segment(tcp))) - { - DLOG("applying linux postnat conntrack workaround\n"); - // make ip protocol invalid and low TTL - if (ip6) - { - ip6->ip6_ctlun.ip6_un1.ip6_un1_nxt = 255; - ip6->ip6_ctlun.ip6_un1.ip6_un1_hlim = 1; - } - if (ip) - { - // this likely also makes ipv4 header checksum invalid - ip->ip_p = 255; - ip->ip_ttl = 1; - } - return VERDICT_MODIFY | VERDICT_NOCSUM; - } -#endif - return VERDICT_DROP; -} - - -static bool check_desync_interval(const struct desync_profile *dp, const t_ctrack *ctrack) -{ - if (dp) - { - if (dp->desync_start) - { - if (ctrack) - { - if (!cutoff_test(ctrack, dp->desync_start, dp->desync_start_mode)) - { - DLOG("desync-start not reached (mode %c): %llu/%u . not desyncing\n", dp->desync_start_mode, (unsigned long long)cutoff_get_limit(ctrack, dp->desync_start_mode), dp->desync_start); - return false; - } - DLOG("desync-start reached (mode %c): %llu/%u\n", dp->desync_start_mode, (unsigned long long)cutoff_get_limit(ctrack, dp->desync_start_mode), dp->desync_start); - } - else - { - DLOG("not desyncing. desync-start is set but conntrack entry is missing\n"); - return false; - } - } - if (dp->desync_cutoff) - { - if (ctrack) - { - if (ctrack->b_desync_cutoff) - { - DLOG("desync-cutoff reached (mode %c): %llu/%u . not desyncing\n", dp->desync_cutoff_mode, (unsigned long long)cutoff_get_limit(ctrack, dp->desync_cutoff_mode), dp->desync_cutoff); - return false; - } - DLOG("desync-cutoff not reached (mode %c): %llu/%u\n", dp->desync_cutoff_mode, (unsigned long long)cutoff_get_limit(ctrack, dp->desync_cutoff_mode), dp->desync_cutoff); - } - else - { - DLOG("not desyncing. desync-cutoff is set but conntrack entry is missing\n"); - return false; - } - } - } - return true; -} -static bool process_desync_interval(const struct desync_profile *dp, t_ctrack *ctrack) -{ - if (check_desync_interval(dp, ctrack)) - return true; - else - { - reasm_orig_cancel(ctrack); - return false; - } -} -static bool check_dup_interval(const struct desync_profile *dp, const t_ctrack *ctrack) -{ - if (dp) - { - if (dp->dup_start) - { - if (ctrack) - { - if (!cutoff_test(ctrack, dp->dup_start, dp->dup_start_mode)) - { - DLOG("dup-start not reached (mode %c): %llu/%u . not duping\n", dp->dup_start_mode, (unsigned long long)cutoff_get_limit(ctrack, dp->dup_start_mode), dp->dup_start); - return false; - } - DLOG("dup-start reached (mode %c): %llu/%u\n", dp->dup_start_mode, (unsigned long long)cutoff_get_limit(ctrack, dp->dup_start_mode), dp->dup_start); - } - else - { - DLOG("not duping. dup-start is set but conntrack entry is missing\n"); - return false; - } - } - if (dp->dup_cutoff) - { - if (ctrack) - { - if (ctrack->b_dup_cutoff) - { - DLOG("dup-cutoff reached (mode %c): %llu/%u . not duping\n", dp->dup_cutoff_mode, (unsigned long long)cutoff_get_limit(ctrack, dp->dup_cutoff_mode), dp->dup_cutoff); - return false; - } - DLOG("dup-cutoff not reached (mode %c): %llu/%u\n", dp->dup_cutoff_mode, (unsigned long long)cutoff_get_limit(ctrack, dp->dup_cutoff_mode), dp->dup_cutoff); - } - else - { - DLOG("not duping. dup-cutoff is set but conntrack entry is missing\n"); - return false; - } - } - } - return true; -} -static bool check_orig_mod_interval(const struct desync_profile *dp, const t_ctrack *ctrack) -{ - if (dp) - { - if (dp->orig_mod_start) - { - if (ctrack) - { - if (!cutoff_test(ctrack, dp->orig_mod_start, dp->orig_mod_start_mode)) - { - DLOG("orig-mod-start not reached (mode %c): %llu/%u . not modding original\n", dp->orig_mod_start_mode, (unsigned long long)cutoff_get_limit(ctrack, dp->orig_mod_start_mode), dp->orig_mod_start); - return false; - } - DLOG("orig-mod-start reached (mode %c): %llu/%u\n", dp->orig_mod_start_mode, (unsigned long long)cutoff_get_limit(ctrack, dp->orig_mod_start_mode), dp->orig_mod_start); - } - else - { - DLOG("not modding original. orig-mod-start is set but conntrack entry is missing\n"); - return false; - } - } - if (dp->orig_mod_cutoff) - { - if (ctrack) - { - if (ctrack->b_orig_mod_cutoff) - { - DLOG("orig-mod-cutoff reached (mode %c): %llu/%u . not modding original\n", dp->orig_mod_cutoff_mode, (unsigned long long)cutoff_get_limit(ctrack, dp->orig_mod_cutoff_mode), dp->orig_mod_cutoff); - return false; - } - DLOG("orig-mod-cutoff not reached (mode %c): %llu/%u\n", dp->orig_mod_cutoff_mode, (unsigned long long)cutoff_get_limit(ctrack, dp->orig_mod_cutoff_mode), dp->orig_mod_cutoff); - } - else - { - DLOG("not modding original. orig-mod-cutoff is set but conntrack entry is missing\n"); - return false; - } - } - } - return true; -} - -static bool replay_queue(struct rawpacket_tailhead *q); - -static size_t pos_normalize(size_t split_pos, size_t reasm_offset, size_t len_payload) -{ - return (split_pos > reasm_offset && (split_pos - reasm_offset) < len_payload) ? split_pos - reasm_offset : 0; -} - -static uint8_t autottl_guess(autottl *attl, uint8_t hop_count, const char *attl_kind) -{ - if (AUTOTTL_ENABLED(*attl)) - { - uint8_t autottl = autottl_eval(hop_count, attl); - if (autottl) - DLOG("%s autottl: guessed %u\n", attl_kind, autottl); - else - DLOG("%s autottl: could not guess\n", attl_kind); - return autottl; - } - else - return 0; -} -static void autottl_discover(t_ctrack *ctrack, const struct in_addr *a4, const struct in6_addr *a6, const char *iface) -{ - if (ctrack && params.autottl_present && !ctrack->b_autottl_discovered) - { - ip_cache_item *ipc = ipcacheTouch(¶ms.ipcache, a4, a6, iface); - if (!ipc) - { - DLOG_ERR("ipcache: out of memory\n"); - return; - } - if (ctrack->incoming_ttl) - { - uint8_t old_hops = ipc->hops; - ipc->hops = hop_count_guess(ctrack->incoming_ttl); - DLOG("incoming hops guessed %u\n", ipc->hops); - if (old_hops != ipc->hops) - DLOG("updated autottl cache\n"); - } - else if (ipc->hops) - DLOG("using cached hops %u\n", ipc->hops); - else - DLOG("hop count unknown\n"); - if (ipc->hops) - { - ctrack->desync_autottl = autottl_guess(a6 ? &ctrack->dp->desync_autottl6 : &ctrack->dp->desync_autottl, ipc->hops, "desync"); - ctrack->orig_autottl = autottl_guess(a6 ? &ctrack->dp->orig_autottl6 : &ctrack->dp->orig_autottl, ipc->hops, "orig"); - ctrack->dup_autottl = autottl_guess(a6 ? &ctrack->dp->dup_autottl6 : &ctrack->dp->dup_autottl, ipc->hops, "dup"); - } - ctrack->b_autottl_discovered = true; - } -} -static void autottl_rediscover(t_ctrack *ctrack, const struct in_addr *a4, const struct in6_addr *a6, const char *iface) -{ - if (ctrack) - { - ctrack->b_autottl_discovered = false; - autottl_discover(ctrack, a4, a6, iface); - } -} - -static bool ipcache_put_hostname(const struct in_addr *a4, const struct in6_addr *a6, const char *hostname, bool hostname_is_ip) -{ - if (!params.cache_hostname) return true; - - ip_cache_item *ipc = ipcacheTouch(¶ms.ipcache, a4, a6, NULL); - if (!ipc) - { - DLOG_ERR("ipcache_put_hostname: out of memory\n"); - return false; - } - if (!ipc->hostname || strcmp(ipc->hostname, hostname)) - { - free(ipc->hostname); - if (!(ipc->hostname = strdup(hostname))) - { - DLOG_ERR("ipcache_put_hostname: out of memory\n"); - return false; - } - ipc->hostname_is_ip = hostname_is_ip; - DLOG("hostname cached (is_ip=%u): %s\n", hostname_is_ip, hostname); - } - return true; -} -static bool ipcache_get_hostname(const struct in_addr *a4, const struct in6_addr *a6, char *hostname, size_t hostname_buf_len, bool *hostname_is_ip) -{ - if (!params.cache_hostname) - { - *hostname = 0; - return true; - } - ip_cache_item *ipc = ipcacheTouch(¶ms.ipcache, a4, a6, NULL); - if (!ipc) - { - DLOG_ERR("ipcache_get_hostname: out of memory\n"); - return false; - } - if (ipc->hostname) - { - DLOG("got cached hostname (is_ip=%u): %s\n", ipc->hostname_is_ip, ipc->hostname); - snprintf(hostname, hostname_buf_len, "%s", ipc->hostname); - if (hostname_is_ip) *hostname_is_ip = ipc->hostname_is_ip; - } - else - *hostname = 0; - return true; -} - - -static uint16_t IP4_IP_ID_FIX(const struct ip *ip, t_ip_id_mode mode) -{ - if (ip) - { - switch(mode) - { - case IPID_SEQ: - case IPID_SEQ_GROUP: - return ip->ip_id ? ip->ip_id : (uint16_t)random(); - case IPID_SAME: - return ip->ip_id; - case IPID_RND: - return (uint16_t)(random()%0xFFFF + 1); - default: - break; - } - } - return 0; -} -static uint16_t IP4_IP_ID_ADD(uint16_t ip_id, uint16_t inc, t_ip_id_mode mode) -{ - switch(mode) - { - case IPID_SEQ_GROUP: - case IPID_SEQ: - if (ip_id) - { - ip_id = net16_add(ip_id, inc); - if (!ip_id) ip_id = net16_add(ip_id, ((int16_t)inc) < 0 ? -1 : 1); // do not allow zero - } - case IPID_SAME: - return ip_id; - case IPID_RND: - return (uint16_t)(random()%0xFFFF + 1);; - default: - return 0; - } -} -#define IP4_IP_ID_NEXT(ip_id,mode) IP4_IP_ID_ADD(ip_id,+1,mode) -#define IP4_IP_ID_PREV(ip_id,mode) IP4_IP_ID_ADD(ip_id,-1,mode) - - -// fake_mod buffer must at least sizeof(desync_profile->fake_tls) -// size does not change -// return : true - altered, false - not altered -static bool runtime_tls_mod(int fake_n, const struct fake_tls_mod_cache *modcache, const struct fake_tls_mod *tls_mod, const uint8_t *fake_data, size_t fake_data_size, const uint8_t *payload, size_t payload_len, uint8_t *fake_mod) -{ - bool b = false; - if (modcache) // it's filled only if it's TLS - { - if (tls_mod->mod & FAKE_TLS_MOD_PADENCAP) - { - size_t sz_rec = pntoh16(fake_data + 3) + payload_len; - size_t sz_handshake = pntoh24(fake_data + 6) + payload_len; - size_t sz_ext = pntoh16(fake_data + modcache->extlen_offset) + payload_len; - size_t sz_pad = pntoh16(fake_data + modcache->padlen_offset) + payload_len; - if ((sz_rec & ~0xFFFF) || (sz_handshake & ~0xFFFFFF) || (sz_ext & ~0xFFFF) || (sz_pad & ~0xFFFF)) - DLOG("fake[%d] cannot apply padencap tls mod. length overflow.\n", fake_n); - else - { - memcpy(fake_mod, fake_data, fake_data_size); - phton16(fake_mod + 3, (uint16_t)sz_rec); - phton24(fake_mod + 6, (uint32_t)sz_handshake); - phton16(fake_mod + modcache->extlen_offset, (uint16_t)sz_ext); - phton16(fake_mod + modcache->padlen_offset, (uint16_t)sz_pad); - b = true; - DLOG("fake[%d] applied padencap tls mod. sizes increased by %zu bytes.\n", fake_n, payload_len); - } - } - if (tls_mod->mod & FAKE_TLS_MOD_RND) - { - if (!b) memcpy(fake_mod, fake_data, fake_data_size); - fill_random_bytes(fake_mod + 11, 32); // random - fill_random_bytes(fake_mod + 44, fake_mod[43]); // session id - b = true; - DLOG("fake[%d] applied rnd tls mod\n", fake_n); - } - if (tls_mod->mod & FAKE_TLS_MOD_DUP_SID) - { - if (payload_len < 44) - DLOG("fake[%d] cannot apply dupsid tls mod. data payload is too short.\n", fake_n); - else if (fake_data[43] != payload[43]) - DLOG("fake[%d] cannot apply dupsid tls mod. fake and orig session id length mismatch : %u!=%u.\n", fake_n, fake_data[43], payload[43]); - else if (payload_len < (44 + payload[43])) - DLOG("fake[%d] cannot apply dupsid tls mod. data payload is not valid.\n", fake_n); - else - { - if (!b) memcpy(fake_mod, fake_data, fake_data_size); - memcpy(fake_mod + 44, payload + 44, fake_mod[43]); // session id - b = true; - DLOG("fake[%d] applied dupsid tls mod\n", fake_n); - } - } - } - return b; -} - -static bool rewrite_tcp_flags(uint16_t *flags, uint16_t unset, uint16_t set, const char *what) -{ - if (set || unset) - { - uint16_t fl_new = *flags & ~unset | set; - if (fl_new!=*flags) - { - DLOG("rewrite %s tcp flags 0x%03X => 0x%03X\n", what, *flags, fl_new); - *flags = fl_new; - return true; - } - } - return false; -} - -static uint8_t orig_mod(const struct desync_profile *dp, const t_ctrack *ctrack, struct dissect *dis) -{ - uint8_t ttl, ttl_orig; - bool bModded = false; - - if (check_orig_mod_interval(dp, ctrack)) - { - ttl = (ctrack && ctrack->orig_autottl) ? ctrack->orig_autottl : dis->ip6 ? dp->orig_mod_ttl6 : dp->orig_mod_ttl; - if (ttl) - { - ttl_orig = dis->ip ? dis->ip->ip_ttl : dis->ip6->ip6_ctlun.ip6_un1.ip6_un1_hlim; - if (ttl_orig != ttl) - { - DLOG("rewrite original packet ttl %u => %u\n", ttl_orig, ttl); - rewrite_ttl(dis->ip, dis->ip6, ttl); - bModded = true; - } - } - if (dis->tcp) - { - uint16_t flags = get_tcp_flags(dis->tcp); - if (rewrite_tcp_flags(&flags, dp->orig_tcp_flags_unset, dp->orig_tcp_flags_set, "original")) - { - apply_tcp_flags(dis->tcp,flags); - bModded = true; - } - } - } - return bModded; -} - -static bool orig_send_rewrite( - uint32_t fwmark, const char *ifout, const struct sockaddr *dst, - uint8_t ttl_orig, uint8_t ttl_fake, const struct desync_profile *dp, const struct dissect *dis) -{ - unsigned int k; - - // here we avoid heavy ops and preserve exact tcp options structure - - if (ttl_fake == ttl_orig) - DLOG("sending %u dups\n", dp->dup_repeats); - else - DLOG("sending %u dups with ttl rewrite %u => %u\n", dp->dup_repeats, ttl_orig, ttl_fake); - rewrite_ttl(dis->ip, dis->ip6, ttl_fake); - - // send dups - for (k = 0; k < dp->dup_repeats; k++) - { - if (!rawsend(dst, fwmark, ifout, dis->data_pkt, dis->len_pkt)) - { - rewrite_ttl(dis->ip, dis->ip6, ttl_orig); - return false; - } - } - rewrite_ttl(dis->ip, dis->ip6, ttl_orig); - return true; -} - -// return : true - orig was sent completely, false - should send orig another way -static bool tcp_orig_send(uint8_t verdict, uint32_t fwmark, const char *ifout, const struct desync_profile *dp, const t_ctrack *ctrack, struct dissect *dis, bool bForceSend) -{ - if (dp->dup_repeats || bForceSend) - { - unsigned int k; - uint8_t pkt[DPI_DESYNC_MAX_FAKE_LEN + 100]; - size_t len; - uint16_t ip_id, nmss; - struct sockaddr_storage src, dst; - uint8_t ttl_orig, ttl_dup, scale_factor; - uint16_t flags_dup; - uint32_t *timestamps; - bool sack, DF, bTF; - - extract_endpoints(dis->ip, dis->ip6, dis->tcp, NULL, &src, &dst); - ttl_orig = dis->ip ? dis->ip->ip_ttl : dis->ip6->ip6_ctlun.ip6_un1.ip6_un1_hlim; - - verdict_tcp_csum_fix(verdict, dis->tcp, dis->transport_len, dis->ip, dis->ip6); - - if (dp->dup_repeats && check_dup_interval(dp, ctrack)) - { - ttl_dup = (ctrack && ctrack->dup_autottl) ? ctrack->dup_autottl : (dis->ip6 ? (dp->dup_ttl6 ? dp->dup_ttl6 : ttl_orig) : (dp->dup_ttl ? dp->dup_ttl : ttl_orig)); - - flags_dup = dis->tcp->th_flags; - bTF = rewrite_tcp_flags(&flags_dup, dp->dup_tcp_flags_unset, dp->dup_tcp_flags_set, "dup"); - if (bTF || dp->dup_fooling_mode || (dis->ip && dp->dup_ip_id_mode!=IPID_SAME)) - { - scale_factor = tcp_find_scale_factor(dis->tcp); - timestamps = tcp_find_timestamps(dis->tcp); - sack = tcp_has_sack(dis->tcp); - nmss = tcp_find_mss(dis->tcp); - ip_id = IP4_IP_ID_FIX(dis->ip,dp->ip_id_mode); - - len = sizeof(pkt); - if (!prepare_tcp_segment((struct sockaddr *)&src, (struct sockaddr *)&dst, - flags_dup, sack, nmss, - dis->tcp->th_seq, dis->tcp->th_ack, dis->tcp->th_win, scale_factor, timestamps, - ip_has_df(dis->ip), ttl_dup, IP4_TOS(dis->ip), ip_id, IP6_FLOW(dis->ip6), - dp->dup_fooling_mode, dp->dup_ts_increment, dp->dup_badseq_increment, dp->dup_badseq_ack_increment, - dis->data_payload, dis->len_payload, pkt, &len)) - { - DLOG_ERR("dup: packet reconstruct failed\n"); - return false; - } - DLOG("sending %u dups with packet reconstruct. ttl %u => %u\n", dp->dup_repeats, ttl_orig, ttl_dup); - - // send dups - for (k = 0; k < dp->dup_repeats; k++) - { - if (!rawsend((struct sockaddr *)&dst, fwmark, ifout, pkt, len)) - return false; - ip_id = IP4_IP_ID_NEXT(ip_id,dp->dup_ip_id_mode); - if (dis->ip) ((struct ip*)pkt)->ip_id = ip_id; - } - } - else - { - if (!orig_send_rewrite(fwmark, ifout, (struct sockaddr *)&dst, ttl_orig, ttl_dup, dp, dis)) - return false; - } - if (dp->dup_replace) - DLOG("NOT sending original because of dup_replace\n"); - else - { - DLOG("sending original ttl %u\n", ttl_orig); - if (!rawsend((struct sockaddr *)&dst, fwmark, ifout, dis->data_pkt, dis->len_pkt)) - return false; - } - return true; - } - if (bForceSend) - { - DLOG("sending original ttl %u\n", ttl_orig); - if (!rawsend((struct sockaddr *)&dst, fwmark, ifout, dis->data_pkt, dis->len_pkt)) - return false; - return true; - } - } - return false; -} -// return : true - orig was sent completely, false - should send orig another way -static bool udp_orig_send(uint8_t verdict, uint32_t fwmark, const char *ifout, const struct desync_profile *dp, const t_ctrack *ctrack, struct dissect *dis, bool bForceSend) -{ - if (dp->dup_repeats || bForceSend) - { - unsigned int k; - uint8_t pkt[DPI_DESYNC_MAX_FAKE_LEN + 100]; - size_t len; - uint16_t ip_id; - struct sockaddr_storage src, dst; - uint8_t ttl_orig, ttl_fake; - - extract_endpoints(dis->ip, dis->ip6, NULL, dis->udp, &src, &dst); - ttl_orig = dis->ip ? dis->ip->ip_ttl : dis->ip6->ip6_ctlun.ip6_un1.ip6_un1_hlim; - - verdict_udp_csum_fix(verdict, dis->udp, dis->transport_len, dis->ip, dis->ip6); - - if (dp->dup_repeats && check_dup_interval(dp, ctrack)) - { - ttl_fake = (ctrack && ctrack->dup_autottl) ? ctrack->dup_autottl : (dis->ip6 ? (dp->dup_ttl6 ? dp->dup_ttl6 : ttl_orig) : (dp->dup_ttl ? dp->dup_ttl : ttl_orig)); - - if (dp->dup_fooling_mode || (dis->ip && dp->dup_ip_id_mode!=IPID_SAME)) - { - ip_id = IP4_IP_ID_FIX(dis->ip,dp->ip_id_mode); - - len = sizeof(pkt); - if (!prepare_udp_segment((struct sockaddr *)&src, (struct sockaddr *)&dst, - ip_has_df(dis->ip), ttl_fake, IP4_TOS(dis->ip), ip_id, IP6_FLOW(dis->ip6), - dp->dup_fooling_mode, NULL, 0, 0, - dis->data_payload, dis->len_payload, pkt, &len)) - { - DLOG_ERR("dup: packet reconstruct failed\n"); - return false; - } - - DLOG("sending %u dups with packet reconstruct. ttl %u => %u\n", dp->dup_repeats, ttl_orig, ttl_fake); - - // send dups - for (k = 0; k < dp->dup_repeats; k++) - { - if (!rawsend((struct sockaddr *)&dst, fwmark, ifout, pkt, len)) - return false; - ip_id = IP4_IP_ID_NEXT(ip_id,dp->dup_ip_id_mode); - if (dis->ip) ((struct ip*)pkt)->ip_id = ip_id; - } - } - else - { - if (!orig_send_rewrite(fwmark, ifout, (struct sockaddr *)&dst, ttl_orig, ttl_fake, dp, dis)) - return false; - } - if (dp->dup_replace) - DLOG("NOT sending original because of dup_replace\n"); - else - { - DLOG("sending original ttl %u\n", ttl_orig); - if (!rawsend((struct sockaddr *)&dst, fwmark, ifout, dis->data_pkt, dis->len_pkt)) - return false; - } - return true; - } - if (bForceSend) - { - DLOG("sending original ttl %u\n", ttl_orig); - if (!rawsend((struct sockaddr *)&dst, fwmark, ifout, dis->data_pkt, dis->len_pkt)) - return false; - return true; - } - } - return false; -} - -static uint8_t dpi_desync_tcp_packet_play(bool replay, size_t reasm_offset, uint32_t fwmark, const char *ifin, const char *ifout, struct dissect *dis) -{ - uint8_t verdict = VERDICT_PASS; - - // additional safety check - if (!!dis->ip == !!dis->ip6) return verdict; - - struct desync_profile *dp = NULL; - - t_ctrack *ctrack = NULL, *ctrack_replay = NULL; - bool bReverse = false; - bool bFake = false; - - struct sockaddr_storage src, dst; - uint8_t pkt1[DPI_DESYNC_MAX_FAKE_LEN + 100], pkt2[DPI_DESYNC_MAX_FAKE_LEN + 100], pkt3[DPI_DESYNC_MAX_FAKE_LEN + 100]; - size_t pkt1_len, pkt2_len, pkt3_len; - uint8_t ttl_orig, ttl_fake, scale_factor; - uint32_t *timestamps; - bool bSack, DF; - uint16_t nmss; - char host[256]; - const char *ifname = NULL, *ssid = NULL; - - uint32_t desync_fwmark = fwmark | params.desync_fwmark; - extract_endpoints(dis->ip, dis->ip6, dis->tcp, NULL, &src, &dst); - timestamps = tcp_find_timestamps(dis->tcp); - DF = ip_has_df(dis->ip); - ttl_orig = dis->ip ? dis->ip->ip_ttl : dis->ip6->ip6_ctlun.ip6_un1.ip6_un1_hlim; - - if (replay) - { - // in replay mode conntrack_replay is not NULL and ctrack is NULL - - //ConntrackPoolDump(¶ms.conntrack); - if (!ConntrackPoolDoubleSearch(¶ms.conntrack, dis->ip, dis->ip6, dis->tcp, NULL, &ctrack_replay, &bReverse) || bReverse) - return verdict; - - ifname = bReverse ? ifin : ifout; -#ifdef HAS_FILTER_SSID - ssid = wlan_ssid_search_ifname(ifname); - if (ssid) DLOG("found ssid for %s : %s\n", ifname, ssid); -#endif - dp = ctrack_replay->dp; - if (dp) - DLOG("using cached desync profile %d\n", dp->n); - else if (!ctrack_replay->dp_search_complete) - { - dp = ctrack_replay->dp = dp_find(¶ms.desync_profiles, IPPROTO_TCP, (struct sockaddr *)&dst, ctrack_replay->hostname, ctrack_replay->hostname_is_ip, ctrack_replay->l7proto, ssid, NULL, NULL, NULL); - ctrack_replay->dp_search_complete = true; - } - if (!dp) - { - DLOG("matching desync profile not found\n"); - return verdict; - } - } - else - { - // in real mode ctrack may be NULL or not NULL, conntrack_replay is equal to ctrack - - if (!params.ctrack_disable) - { - ConntrackPoolPurge(¶ms.conntrack); - if (ConntrackPoolFeed(¶ms.conntrack, dis->ip, dis->ip6, dis->tcp, NULL, dis->len_payload, &ctrack, &bReverse)) - { - dp = ctrack->dp; - ctrack_replay = ctrack; - } - } - ifname = bReverse ? ifin : ifout; -#ifdef HAS_FILTER_SSID - ssid = wlan_ssid_search_ifname(ifname); - if (ssid) DLOG("found ssid for %s : %s\n", ifname, ssid); -#endif - if (dp) - DLOG("using cached desync profile %d\n", dp->n); - else if (!ctrack || !ctrack->dp_search_complete) - { - const char *hostname = NULL; - bool hostname_is_ip = false; - if (ctrack) - { - hostname = ctrack->hostname; - hostname_is_ip = ctrack->hostname_is_ip; - if (!hostname && !bReverse) - { - if (ipcache_get_hostname(dis->ip ? &dis->ip->ip_dst : NULL, dis->ip6 ? &dis->ip6->ip6_dst : NULL, host, sizeof(host), &hostname_is_ip) && *host) - if (!(hostname = ctrack_replay->hostname = strdup(host))) - DLOG_ERR("strdup(host): out of memory\n"); - } - } - dp = dp_find(¶ms.desync_profiles, IPPROTO_TCP, (struct sockaddr *)&dst, hostname, hostname_is_ip, ctrack ? ctrack->l7proto : UNKNOWN, ssid, NULL, NULL, NULL); - if (ctrack) - { - ctrack->dp = dp; - ctrack->dp_search_complete = true; - } - } - if (!dp) - { - DLOG("matching desync profile not found\n"); - return verdict; - } - maybe_cutoff(ctrack, IPPROTO_TCP); - - HostFailPoolPurgeRateLimited(&dp->hostlist_auto_fail_counters); - - //ConntrackPoolDump(¶ms.conntrack); - - if (tcp_synack_segment(dis->tcp)) - { - if (dp->wsize) - { - tcp_rewrite_winsize(dis->tcp, dp->wsize, dp->wscale); - verdict = VERDICT_MODIFY; - } - if (dp->synack_split == SS_SYN) - { - DLOG("split SYNACK : clearing ACK bit\n"); - dis->tcp->th_flags &= ~TH_ACK; - verdict = VERDICT_MODIFY; - } - } - - if (bReverse) - { - if (ctrack && !ctrack->incoming_ttl) - { - ctrack->incoming_ttl = ttl_orig; - DLOG("incoming TTL %u\n", ttl_orig); - autottl_rediscover(ctrack, dis->ip ? &dis->ip->ip_src : NULL, dis->ip6 ? &dis->ip6->ip6_src : NULL, ifin); - } - - // process reply packets for auto hostlist mode - // by looking at RSTs or HTTP replies we decide whether original request looks like DPI blocked - // we only process first-sequence replies. do not react to subsequent redirects or RSTs - if (ctrack && ctrack->hostname && ctrack->hostname_ah_check && (ctrack->ack_last - ctrack->ack0) == 1) - { - bool bFail = false; - - char client_ip_port[48]; - if (*params.hostlist_auto_debuglog) - ntop46_port((struct sockaddr*)&dst, client_ip_port, sizeof(client_ip_port)); - else - *client_ip_port = 0; - - if (dis->tcp->th_flags & TH_RST) - { - DLOG("incoming RST detected for hostname %s\n", ctrack->hostname); - HOSTLIST_DEBUGLOG_APPEND("%s : profile %d : client %s : proto %s : incoming RST", ctrack->hostname, ctrack->dp->n, client_ip_port, l7proto_str(ctrack->l7proto)); - bFail = true; - } - else if (dis->len_payload && ctrack->l7proto == HTTP) - { - if (IsHttpReply(dis->data_payload, dis->len_payload)) - { - DLOG("incoming HTTP reply detected for hostname %s\n", ctrack->hostname); - bFail = HttpReplyLooksLikeDPIRedirect(dis->data_payload, dis->len_payload, ctrack->hostname); - if (bFail) - { - DLOG("redirect to another domain detected. possibly DPI redirect.\n"); - HOSTLIST_DEBUGLOG_APPEND("%s : profile %d : client %s : proto %s : redirect to another domain", ctrack->hostname, ctrack->dp->n, client_ip_port, l7proto_str(ctrack->l7proto)); - } - else - DLOG("local or in-domain redirect detected. it's not a DPI redirect.\n"); - } - else - { - // received not http reply. do not monitor this connection anymore - DLOG("incoming unknown HTTP data detected for hostname %s\n", ctrack->hostname); - } - } - if (bFail) - auto_hostlist_failed(dp, ctrack->hostname, ctrack->hostname_is_ip, client_ip_port, ctrack->l7proto); - else - if (dis->len_payload) - auto_hostlist_reset_fail_counter(dp, ctrack->hostname, client_ip_port, ctrack->l7proto); - if (dis->tcp->th_flags & TH_RST) - ConntrackClearHostname(ctrack); // do not react to further dup RSTs - } - - return verdict; // nothing to do. do not waste cpu - } - - autottl_discover(ctrack, dis->ip ? &dis->ip->ip_dst : NULL, dis->ip6 ? &dis->ip6->ip6_dst : NULL, ifout); - - if (orig_mod(dp, ctrack, dis)) // ttl can change ! - verdict = VERDICT_MODIFY; - - if (dp->wssize) - { - if (ctrack) - { - if (ctrack->b_wssize_cutoff) - { - DLOG("wssize-cutoff reached (mode %c): %llu/%u . not changing wssize.\n", dp->wssize_cutoff_mode, (unsigned long long)cutoff_get_limit(ctrack, dp->wssize_cutoff_mode), dp->wssize_cutoff); - } - else - { - if (dp->wssize_cutoff) DLOG("wssize-cutoff not reached (mode %c): %llu/%u\n", dp->wssize_cutoff_mode, (unsigned long long)cutoff_get_limit(ctrack, dp->wssize_cutoff_mode), dp->wssize_cutoff); - tcp_rewrite_winsize(dis->tcp, dp->wssize, dp->wsscale); - verdict = VERDICT_MODIFY; - } - } - else - { - DLOG("not changing wssize. wssize is set but conntrack entry is missing\n"); - } - } - - if ((dp->synack_split == SS_SYNACK || dp->synack_split == SS_ACKSYN) && tcp_synack_segment(dis->tcp)) - { - // reconstruct required - - dis->tcp->th_flags &= ~TH_ACK; - tcp_fix_checksum(dis->tcp, dis->transport_len, dis->ip, dis->ip6); - - char ss[2], i; - if (dp->synack_split == SS_SYNACK) - { - ss[0] = 'S'; - ss[1] = 'A'; - } - else - { - ss[0] = 'A'; - ss[1] = 'S'; - } - pkt1_len = sizeof(pkt1); - if (!prepare_tcp_segment((struct sockaddr *)&src, (struct sockaddr *)&dst, TH_ACK, false, 0, dis->tcp->th_seq, dis->tcp->th_ack, dis->tcp->th_win, SCALE_NONE, timestamps, - DF, ttl_orig, IP4_TOS(dis->ip), IP4_IP_ID_FIX(dis->ip,dp->ip_id_mode), IP6_FLOW(dis->ip6), - FOOL_NONE, 0, 0, 0, NULL, 0, pkt1, &pkt1_len)) - { - DLOG_ERR("cannot prepare split SYNACK ACK part\n"); - goto send_orig; - } - for (int i = 0; i < 2; i++) - { - switch (ss[i]) - { - case 'S': - DLOG("split SYNACK : SYN\n"); - if (!rawsend_rep(dp->desync_repeats, (struct sockaddr *)&dst, desync_fwmark, ifout, dis->data_pkt, dis->len_pkt)) - goto send_orig; - break; - case 'A': - DLOG("split SYNACK : ACK\n"); - if (!rawsend_rep(dp->desync_repeats, (struct sockaddr *)&dst, desync_fwmark, ifout, pkt1, pkt1_len)) - goto send_orig; - break; - } - } - return VERDICT_DROP; - } - - // start and cutoff limiters - if (!process_desync_interval(dp, ctrack)) goto send_orig; - } // !replay - - ttl_orig = dis->ip ? dis->ip->ip_ttl : dis->ip6->ip6_ctlun.ip6_un1.ip6_un1_hlim; - ttl_fake = (ctrack_replay && ctrack_replay->desync_autottl) ? ctrack_replay->desync_autottl : (dis->ip6 ? (dp->desync_ttl6 ? dp->desync_ttl6 : ttl_orig) : (dp->desync_ttl ? dp->desync_ttl : ttl_orig)); - uint16_t flags_orig = get_tcp_flags(dis->tcp); - scale_factor = tcp_find_scale_factor(dis->tcp); - bSack = tcp_has_sack(dis->tcp); - nmss = tcp_find_mss(dis->tcp); - - uint16_t ip_id=0; - if (replay && ctrack_replay->ip_id) ip_id = ctrack_replay->ip_id; - if (!ip_id) ip_id = IP4_IP_ID_FIX(dis->ip,dp->ip_id_mode); - - if (!replay) - { - if (tcp_syn_segment(dis->tcp)) - { - switch (dp->desync_mode0) - { - case DESYNC_SYNACK: - pkt1_len = sizeof(pkt1); - if (!prepare_tcp_segment((struct sockaddr *)&src, (struct sockaddr *)&dst, TH_SYN | TH_ACK, false, 0, dis->tcp->th_seq, dis->tcp->th_ack, dis->tcp->th_win, scale_factor, timestamps, - DF, ttl_fake, IP4_TOS(dis->ip), ip_id, IP6_FLOW(dis->ip6), - dp->desync_fooling_mode, dp->desync_ts_increment, dp->desync_badseq_increment, dp->desync_badseq_ack_increment, - NULL, 0, pkt1, &pkt1_len)) - { - goto send_orig; - } - DLOG("sending fake SYNACK\n"); - if (!rawsend_rep(dp->desync_repeats, (struct sockaddr *)&dst, desync_fwmark, ifout, pkt1, pkt1_len)) - goto send_orig; - break; - case DESYNC_SYNDATA: - // make sure we are not breaking TCP fast open - if (tcp_has_fastopen(dis->tcp)) - { - DLOG("received SYN with TCP fast open option. syndata desync is not applied.\n"); - break; - } - if (dis->len_payload) - { - DLOG("received SYN with data payload. syndata desync is not applied.\n"); - break; - } - pkt1_len = sizeof(pkt1); - if (!prepare_tcp_segment((struct sockaddr *)&src, (struct sockaddr *)&dst, flags_orig, bSack, nmss, dis->tcp->th_seq, dis->tcp->th_ack, dis->tcp->th_win, scale_factor, timestamps, - DF, ttl_orig, IP4_TOS(dis->ip), ip_id, IP6_FLOW(dis->ip6), - 0, 0, 0, 0, dp->fake_syndata, dp->fake_syndata_size, pkt1, &pkt1_len)) - { - goto send_orig; - } - ip_id = IP4_IP_ID_NEXT(ip_id,dp->ip_id_mode); - DLOG("sending SYN with fake data : "); - hexdump_limited_dlog(dp->fake_syndata, dp->fake_syndata_size, PKTDATA_MAXDUMP); DLOG("\n"); - if (!rawsend_rep(dp->desync_repeats, (struct sockaddr *)&dst, desync_fwmark, ifout, pkt1, pkt1_len)) - goto send_orig; - verdict = VERDICT_DROP; - break; - default: - break; - } - // can do nothing else with SYN packet - goto send_orig; - } - - } // !replay - - if (!(dis->tcp->th_flags & TH_SYN) && dis->len_payload) - { - struct blob_collection_head *fake; - - uint8_t *p, *phost = NULL; - const uint8_t *rdata_payload = dis->data_payload; - size_t rlen_payload = dis->len_payload; - size_t split_pos, seqovl_pos; - size_t multisplit_pos[MAX_SPLITS]; - int multisplit_count; - int i; - - bool bHaveHost = false, bHostIsIp = false; - t_l7proto l7proto = UNKNOWN; - - if (replay) - { - rdata_payload = ctrack_replay->reasm_orig.packet; - rlen_payload = ctrack_replay->reasm_orig.size_present; - } - else if (reasm_orig_feed(ctrack, IPPROTO_TCP, dis->data_payload, dis->len_payload)) - { - rdata_payload = ctrack->reasm_orig.packet; - rlen_payload = ctrack->reasm_orig.size_present; - } - - process_retrans_fail(ctrack, IPPROTO_TCP, (struct sockaddr*)&src); - - if (IsHttp(rdata_payload, rlen_payload)) - { - DLOG("packet contains HTTP request\n"); - l7proto = HTTP; - if (ctrack && ctrack->l7proto == UNKNOWN) ctrack->l7proto = l7proto; - - // we do not reassemble http - reasm_orig_cancel(ctrack); - if (!dp->wssize_no_forced_cutoff) forced_wssize_cutoff(ctrack); - - bHaveHost = HttpExtractHost(rdata_payload, rlen_payload, host, sizeof(host)); - if (!bHaveHost) - { - DLOG("not applying tampering to HTTP without Host:\n"); - goto send_orig; - } - if (ctrack) - { - // we do not reassemble http - if (!ctrack->req_seq_present) - { - ctrack->req_seq_start = ctrack->seq_last; - ctrack->req_seq_end = ctrack->pos_orig - 1; - ctrack->req_seq_present = ctrack->req_seq_finalized = true; - DLOG("req retrans : tcp seq interval %u-%u\n", ctrack->req_seq_start, ctrack->req_seq_end); - } - } - } - else if (IsTLSClientHello(rdata_payload, rlen_payload, TLS_PARTIALS_ENABLE)) - { - bool bReqFull = IsTLSRecordFull(rdata_payload, rlen_payload); - DLOG(bReqFull ? "packet contains full TLS ClientHello\n" : "packet contains partial TLS ClientHello\n"); - l7proto = TLS; - - if (bReqFull) TLSDebug(rdata_payload, rlen_payload); - - bHaveHost = TLSHelloExtractHost(rdata_payload, rlen_payload, host, sizeof(host), TLS_PARTIALS_ENABLE); - - if (ctrack) - { - if (!ctrack->l7proto) ctrack->l7proto = l7proto; - // do not reasm retransmissions - if (!bReqFull && ReasmIsEmpty(&ctrack->reasm_orig) && !ctrack->req_seq_abandoned && - !(ctrack->req_seq_finalized && seq_within(ctrack->seq_last, ctrack->req_seq_start, ctrack->req_seq_end))) - { - // do not reconstruct unexpected large payload (they are feeding garbage ?) - if (!reasm_orig_start(ctrack, IPPROTO_TCP, TLSRecordLen(dis->data_payload), TCP_MAX_REASM, dis->data_payload, dis->len_payload)) - { - reasm_orig_cancel(ctrack); - goto send_orig; - } - } - if (!ctrack->req_seq_finalized) - { - if (!ctrack->req_seq_present) - { - // lower bound of request seq interval - ctrack->req_seq_start = ctrack->seq_last; - ctrack->req_seq_present = true; - } - // upper bound of request seq interval - // it can grow on every packet until request is complete. then interval is finalized and never touched again. - ctrack->req_seq_end = ctrack->pos_orig - 1; - DLOG("req retrans : seq interval %u-%u\n", ctrack->req_seq_start, ctrack->req_seq_end); - ctrack->req_seq_finalized |= bReqFull; - } - if (!dp->wssize_no_forced_cutoff && (bReqFull || ReasmIsEmpty(&ctrack->reasm_orig))) forced_wssize_cutoff(ctrack); - - if (!ReasmIsEmpty(&ctrack->reasm_orig)) - { - verdict_tcp_csum_fix(verdict, dis->tcp, dis->transport_len, dis->ip, dis->ip6); - if (rawpacket_queue(&ctrack->delayed, &dst, desync_fwmark, ifin, ifout, dis->data_pkt, dis->len_pkt, dis->len_payload)) - { - DLOG("DELAY desync until reasm is complete (#%u)\n", rawpacket_queue_count(&ctrack->delayed)); - } - else - { - DLOG_ERR("rawpacket_queue failed !\n"); - reasm_orig_cancel(ctrack); - goto send_orig; - } - if (ReasmIsFull(&ctrack->reasm_orig)) - { - replay_queue(&ctrack->delayed); - reasm_orig_fin(ctrack); - } - return VERDICT_DROP; - } - } - - if (dp->desync_skip_nosni && !bHaveHost) - { - DLOG("not applying tampering to TLS ClientHello without hostname in the SNI\n"); - reasm_orig_cancel(ctrack); - goto send_orig; - } - } - - if (ctrack && ctrack->req_seq_finalized) - { - uint32_t dseq = ctrack->seq_last - ctrack->req_seq_end; - // do not react to 32-bit overflowed sequence numbers. allow 16 Mb grace window then cutoff. - if (dseq >= 0x1000000 && !(dseq & 0x80000000)) ctrack->req_seq_abandoned = true; - } - - if (bHaveHost) - { - bHostIsIp = strip_host_to_ip(host); - DLOG("hostname: %s\n", host); - } - - bool bDiscoveredL7; - if (ctrack_replay) - { - if ((bDiscoveredL7 = !ctrack_replay->l7proto_discovered && ctrack_replay->l7proto != UNKNOWN)) - ctrack_replay->l7proto_discovered = true; - } - else - bDiscoveredL7 = !ctrack_replay && l7proto != UNKNOWN; - if (bDiscoveredL7) DLOG("discovered l7 protocol\n"); - - bool bDiscoveredHostname = bHaveHost && !(ctrack_replay && ctrack_replay->hostname_discovered); - if (bDiscoveredHostname) - { - DLOG("discovered hostname\n"); - if (ctrack_replay) - { - free(ctrack_replay->hostname); - ctrack_replay->hostname = strdup(host); - ctrack_replay->hostname_is_ip = bHostIsIp; - if (!ctrack_replay->hostname) - { - DLOG_ERR("hostname dup : out of memory"); - reasm_orig_cancel(ctrack); - goto send_orig; - } - ctrack_replay->hostname_discovered = true; - if (!ipcache_put_hostname(dis->ip ? &dis->ip->ip_dst : NULL, dis->ip6 ? &dis->ip6->ip6_dst : NULL, host, bHostIsIp)) - { - reasm_orig_cancel(ctrack); - goto send_orig; - } - - } - } - - bool bCheckDone = false, bCheckResult = false, bCheckExcluded = false; - if (bDiscoveredL7 || bDiscoveredHostname) - { - struct desync_profile *dp_prev = dp; - - dp = dp_find(¶ms.desync_profiles, IPPROTO_TCP, (struct sockaddr *)&dst, - ctrack_replay ? ctrack_replay->hostname : bHaveHost ? host : NULL, - ctrack_replay ? ctrack_replay->hostname_is_ip : bHostIsIp, - ctrack_replay ? ctrack_replay->l7proto : l7proto, ssid, - &bCheckDone, &bCheckResult, &bCheckExcluded); - if (ctrack_replay) - { - ctrack_replay->dp = dp; - ctrack_replay->dp_search_complete = true; - ctrack_replay->bCheckDone = bCheckDone; - ctrack_replay->bCheckResult = bCheckResult; - ctrack_replay->bCheckExcluded = bCheckExcluded; - } - if (!dp) - { - reasm_orig_cancel(ctrack); - goto send_orig; - } - if (dp != dp_prev) - { - DLOG("desync profile changed by revealed l7 protocol or hostname !\n"); - autottl_rediscover(ctrack_replay, dis->ip ? &dis->ip->ip_dst : NULL, dis->ip6 ? &dis->ip6->ip6_dst : NULL, ifout); - ip_id = IP4_IP_ID_FIX(dis->ip,dp->ip_id_mode); - // re-evaluate start/cutoff limiters - if (replay) - { - if (orig_mod(dp, ctrack_replay, dis)) // ttl can change ! - verdict = VERDICT_MODIFY; - } - else - { - maybe_cutoff(ctrack, IPPROTO_TCP); - if (orig_mod(dp, ctrack, dis)) // ttl can change ! - verdict = VERDICT_MODIFY; - if (!process_desync_interval(dp, ctrack)) - { - reasm_orig_cancel(ctrack); - goto send_orig; - } - } - ttl_orig = dis->ip ? dis->ip->ip_ttl : dis->ip6->ip6_ctlun.ip6_un1.ip6_un1_hlim; - ttl_fake = (ctrack_replay && ctrack_replay->desync_autottl) ? ctrack_replay->desync_autottl : (dis->ip6 ? (dp->desync_ttl6 ? dp->desync_ttl6 : ttl_orig) : (dp->desync_ttl ? dp->desync_ttl : ttl_orig)); - } - } - else if (ctrack_replay) - { - bCheckDone = ctrack_replay->bCheckDone; - bCheckResult = ctrack_replay->bCheckResult; - bCheckExcluded = ctrack_replay->bCheckExcluded; - } - - if (bHaveHost && !PROFILE_HOSTLISTS_EMPTY(dp)) - { - if (!bCheckDone) - bCheckResult = HostlistCheck(dp, host, bHostIsIp, &bCheckExcluded, false); - if (bCheckResult) - ctrack_stop_retrans_counter(ctrack_replay); - else - { - if (ctrack_replay) - { - ctrack_replay->hostname_ah_check = dp->hostlist_auto && !bCheckExcluded; - if (!ctrack_replay->hostname_ah_check) - ctrack_stop_retrans_counter(ctrack_replay); - } - DLOG("not applying tampering to this request\n"); - reasm_orig_cancel(ctrack); - goto send_orig; - } - } - - if (l7proto == UNKNOWN) - { - if (!dp->desync_any_proto) - { - DLOG("not applying tampering to unknown protocol\n"); - reasm_orig_cancel(ctrack); - goto send_orig; - } - DLOG("applying tampering to unknown protocol\n"); - } - - if ((l7proto == HTTP) && (dp->hostcase || dp->hostnospace || dp->domcase || dp->methodeol) && HttpFindHost(&phost, dis->data_payload, dis->len_payload)) - { - if (dp->hostcase) - { - DLOG("modifying Host: => %c%c%c%c:\n", dp->hostspell[0], dp->hostspell[1], dp->hostspell[2], dp->hostspell[3]); - memcpy(phost, dp->hostspell, 4); - verdict = VERDICT_MODIFY; - } - if (dp->domcase) - { - DLOG("mixing domain case\n"); - for (p = phost + 5; p < (dis->data_payload + dis->len_payload) && *p != '\r' && *p != '\n'; p++) - *p = (((size_t)p) & 1) ? tolower(*p) : toupper(*p); - verdict = VERDICT_MODIFY; - } - uint8_t *pua; - if (dp->hostnospace) - { - if ((pua = (uint8_t*)memmem(dis->data_payload, dis->len_payload, "\r\nUser-Agent: ", 14)) && - (pua = (uint8_t*)memmem(pua + 1, dis->len_payload - (pua - dis->data_payload) - 1, "\r\n", 2))) - { - DLOG("removing space after Host: and adding it to User-Agent:\n"); - if (pua > phost) - { - memmove(phost + 5, phost + 6, pua - phost - 6); - pua[-1] = ' '; - } - else - { - memmove(pua + 1, pua, phost - pua + 5); - *pua = ' '; - } - verdict = VERDICT_MODIFY; - } - else - DLOG("cannot do hostnospace because valid User-Agent: not found\n"); - } - else if (dp->methodeol) - { - if (phost[5] == ' ' || phost[5] == '\t') - { - DLOG("removing space after Host: and adding '\\n' before method\n"); - memmove(dis->data_payload + 1, dis->data_payload, phost - dis->data_payload + 5); - dis->data_payload[0] = '\n'; - verdict = VERDICT_MODIFY; - } - else - DLOG("cannot do methodeol because there's no space or tab after Host:\n"); - } - - } - - if (dp->desync_mode == DESYNC_NONE) - { - reasm_orig_cancel(ctrack); - goto send_orig; - } - - if (params.debug) - { - char s1[48], s2[48]; - ntop46_port((struct sockaddr *)&src, s1, sizeof(s1)); - ntop46_port((struct sockaddr *)&dst, s2, sizeof(s2)); - DLOG("dpi desync src=%s dst=%s\n", s1, s2); - } - - switch (l7proto) - { - case HTTP: - fake = &dp->fake_http; - break; - case TLS: - fake = &dp->fake_tls; - break; - default: - fake = &dp->fake_unknown; - break; - } - if (dp->desync_mode == DESYNC_MULTISPLIT || dp->desync_mode == DESYNC_MULTIDISORDER || dp->desync_mode2 == DESYNC_MULTISPLIT || dp->desync_mode2 == DESYNC_MULTIDISORDER) - { - split_pos = 0; - ResolveMultiPos(rdata_payload, rlen_payload, l7proto, dp->splits, dp->split_count, multisplit_pos, &multisplit_count); - if (params.debug) - { - if (multisplit_count) - { - DLOG("multisplit pos: "); - for (i = 0; i < multisplit_count; i++) DLOG("%zu ", multisplit_pos[i]); - DLOG("\n"); - } - else - DLOG("all multisplit pos are outside of this packet\n"); - } - if (multisplit_count) - { - int j; - for (i = j = 0; i < multisplit_count; i++) - { - multisplit_pos[j] = pos_normalize(multisplit_pos[i], reasm_offset, dis->len_payload); - if (multisplit_pos[j]) j++; - } - multisplit_count = j; - if (params.debug) - { - if (multisplit_count) - { - DLOG("normalized multisplit pos: "); - for (i = 0; i < multisplit_count; i++) DLOG("%zu ", multisplit_pos[i]); - DLOG("\n"); - } - else - DLOG("all multisplit pos are outside of this packet\n"); - } - } - } - else if (dp->desync_mode == DESYNC_FAKEDSPLIT || dp->desync_mode == DESYNC_FAKEDDISORDER || dp->desync_mode2 == DESYNC_FAKEDSPLIT || dp->desync_mode2 == DESYNC_FAKEDDISORDER) - { - multisplit_count = 0; - // first look for non-abs split - for (i = 0, split_pos = 0; i < dp->split_count && !split_pos; i++) - if (dp->splits[i].marker != PM_ABS) - split_pos = ResolvePos(rdata_payload, rlen_payload, l7proto, dp->splits + i); - // second look for abs split - if (!split_pos) - for (i = 0, split_pos = 0; i < dp->split_count && !split_pos; i++) - if (dp->splits[i].marker == PM_ABS) - split_pos = ResolvePos(rdata_payload, rlen_payload, l7proto, dp->splits + i); - if (!split_pos) split_pos = 1; - DLOG("regular split pos: %zu\n", split_pos); - if (!split_pos || split_pos > rlen_payload) split_pos = 1; - split_pos = pos_normalize(split_pos, reasm_offset, dis->len_payload); - if (split_pos) - DLOG("normalized regular split pos : %zu\n", split_pos); - else - DLOG("regular split pos is outside of this packet\n"); - } - else if (dp->desync_mode == DESYNC_HOSTFAKESPLIT || dp->desync_mode2 == DESYNC_HOSTFAKESPLIT) - { - struct proto_pos splits[2] = { - {.marker = PM_HOST,.pos = 0}, - {.marker = PM_HOST_END,.pos = 0} - }; - split_pos = 0; - ResolveMultiPos(rdata_payload, rlen_payload, l7proto, splits, 2, multisplit_pos, &multisplit_count); - if (multisplit_count != 2) - { - DLOG("hostfakesplit: host and endhost positions not found\n"); - multisplit_count = 0; - } - else - { - int j; - for (i = j = 0; i < multisplit_count; i++) - { - multisplit_pos[j] = pos_normalize(multisplit_pos[i], reasm_offset, dis->len_payload); - if (multisplit_pos[j]) j++; - } - multisplit_count = j; - if (multisplit_count != 2) - { - DLOG("hostfakesplit: host or endhost are outside of this packet\n"); - multisplit_count = 0; - } - else - { - DLOG("normalized hostfakesplit pos: "); - for (i = 0; i < multisplit_count; i++) DLOG("%zu ", multisplit_pos[i]); - DLOG("\n"); - multisplit_pos[2] = ResolvePos(rdata_payload, rlen_payload, l7proto, &dp->hostfakesplit_midhost); - if (multisplit_pos[2]) - { - multisplit_pos[2] = pos_normalize(multisplit_pos[2], reasm_offset, dis->len_payload); - if (multisplit_pos[2] > multisplit_pos[0] && multisplit_pos[2] < multisplit_pos[1]) - { - DLOG("normalized hostfakesplit midhost pos: %zu\n", multisplit_pos[2]); - multisplit_count++; - } - } - } - } - } - else - { - multisplit_count = 0; - split_pos = 0; - } - if (dp->desync_mode == DESYNC_FAKEDSPLIT || dp->desync_mode == DESYNC_MULTISPLIT || dp->desync_mode2 == DESYNC_FAKEDSPLIT || dp->desync_mode2 == DESYNC_MULTISPLIT) - { - // split seqovl only uses absolute positive values - seqovl_pos = (dp->seqovl.marker == PM_ABS && dp->seqovl.pos > 0) ? dp->seqovl.pos : 0; - if (seqovl_pos) DLOG("seqovl : %zu\n", seqovl_pos); - } - else if (dp->desync_mode == DESYNC_FAKEDDISORDER || dp->desync_mode == DESYNC_MULTIDISORDER || dp->desync_mode2 == DESYNC_FAKEDDISORDER || dp->desync_mode2 == DESYNC_MULTIDISORDER) - { - seqovl_pos = ResolvePos(rdata_payload, rlen_payload, l7proto, &dp->seqovl); - seqovl_pos = pos_normalize(seqovl_pos, reasm_offset, dis->len_payload); - if (seqovl_pos) DLOG("normalized seqovl : %zu\n", seqovl_pos); - } - else - seqovl_pos = 0; - - uint32_t fooling_orig = FOOL_NONE; - - uint16_t flags_fake = flags_orig; - rewrite_tcp_flags(&flags_fake, dp->desync_tcp_flags_unset, dp->desync_tcp_flags_set, "desync"); - - switch (dp->desync_mode) - { - case DESYNC_FAKE_KNOWN: - if (reasm_offset) break; - if (l7proto == UNKNOWN) - { - DLOG("not applying fake because of unknown protocol\n"); - break; - } - case DESYNC_FAKE: - if (reasm_offset) break; - - { - struct blob_item *fake_item; - size_t fake_size; - uint8_t *fake_data; - uint8_t fake_data_buf[FAKE_MAX_TCP]; - int n = 0; - uint32_t sequence, sequence0; - - sequence = sequence0 = ntohl(dis->tcp->th_seq); - LIST_FOREACH(fake_item, fake, next) - { - n++; - switch (l7proto) - { - case TLS: - if ((fake_item->size <= sizeof(fake_data_buf)) && - runtime_tls_mod(n, (struct fake_tls_mod_cache *)fake_item->extra, (struct fake_tls_mod *)fake_item->extra2, fake_item->data, fake_item->size, rdata_payload, rlen_payload, fake_data_buf)) - { - fake_data = fake_data_buf; - break; - } - default: - fake_data = fake_item->data; - } - fake_data += fake_item->offset; - fake_size = fake_item->size - fake_item->offset; - - pkt1_len = sizeof(pkt1); - if (!prepare_tcp_segment((struct sockaddr *)&src, (struct sockaddr *)&dst, flags_fake, false, 0, htonl(sequence), dis->tcp->th_ack, dis->tcp->th_win, scale_factor, timestamps, - DF, ttl_fake, IP4_TOS(dis->ip), ip_id, IP6_FLOW(dis->ip6), - dp->desync_fooling_mode, dp->desync_ts_increment, dp->desync_badseq_increment, dp->desync_badseq_ack_increment, - fake_data, fake_size, pkt1, &pkt1_len)) - { - reasm_orig_cancel(ctrack); - goto send_orig; - } - DLOG("sending fake[%d] seq=+%u : ", n, sequence - sequence0); - hexdump_limited_dlog(fake_data, fake_size, PKTDATA_MAXDUMP); DLOG("\n"); - if (!rawsend_rep(dp->desync_repeats, (struct sockaddr *)&dst, desync_fwmark, ifout, pkt1, pkt1_len)) - { - reasm_orig_cancel(ctrack); - goto send_orig; - } - ip_id = IP4_IP_ID_NEXT(ip_id,dp->ip_id_mode); - if (dp->tcp_mod.seq) sequence += fake_size; - } - } - bFake = true; - break; - case DESYNC_RST: - case DESYNC_RSTACK: - if (reasm_offset) break; - pkt1_len = sizeof(pkt1); - if (!prepare_tcp_segment((struct sockaddr *)&src, (struct sockaddr *)&dst, TH_RST | (dp->desync_mode == DESYNC_RSTACK ? TH_ACK : 0), false, 0, dis->tcp->th_seq, dis->tcp->th_ack, dis->tcp->th_win, scale_factor, timestamps, - DF, ttl_fake, IP4_TOS(dis->ip), ip_id, IP6_FLOW(dis->ip6), - dp->desync_fooling_mode, dp->desync_ts_increment, dp->desync_badseq_increment, dp->desync_badseq_ack_increment, - NULL, 0, pkt1, &pkt1_len)) - { - reasm_orig_cancel(ctrack); - goto send_orig; - } - DLOG("sending fake RST/RSTACK\n"); - if (!rawsend_rep(dp->desync_repeats, (struct sockaddr *)&dst, desync_fwmark, ifout, pkt1, pkt1_len)) - { - reasm_orig_cancel(ctrack); - goto send_orig; - } - ip_id = IP4_IP_ID_NEXT(ip_id,dp->ip_id_mode); - bFake = true; - break; - case DESYNC_HOPBYHOP: - case DESYNC_DESTOPT: - case DESYNC_IPFRAG1: - fooling_orig = (dp->desync_mode == DESYNC_HOPBYHOP) ? FOOL_HOPBYHOP : (dp->desync_mode == DESYNC_DESTOPT) ? FOOL_DESTOPT : FOOL_IPFRAG1; - if (dis->ip6 && (dp->desync_mode2 == DESYNC_NONE || !desync_valid_second_stage_tcp(dp->desync_mode2) || - (!split_pos && (dp->desync_mode2 == DESYNC_FAKEDSPLIT || dp->desync_mode2 == DESYNC_FAKEDDISORDER)) || - (multisplit_count < 2 && dp->desync_mode2 == DESYNC_HOSTFAKESPLIT) || - (!multisplit_count && (dp->desync_mode2 == DESYNC_MULTISPLIT || dp->desync_mode2 == DESYNC_MULTIDISORDER)))) - { - reasm_orig_cancel(ctrack); - rdata_payload = NULL; - - pkt1_len = sizeof(pkt1); - if (!prepare_tcp_segment((struct sockaddr *)&src, (struct sockaddr *)&dst, flags_orig, false, 0, dis->tcp->th_seq, dis->tcp->th_ack, dis->tcp->th_win, scale_factor, timestamps, - DF, ttl_orig, 0, 0, IP6_FLOW(dis->ip6), - fooling_orig, 0, 0, 0, - dis->data_payload, dis->len_payload, pkt1, &pkt1_len)) - { - goto send_orig; - } - DLOG("resending original packet with extension header\n"); - if (!rawsend((struct sockaddr *)&dst, desync_fwmark, ifout, pkt1, pkt1_len)) - goto send_orig; - // this mode is final, no other options available - return VERDICT_DROP; - } - default: - pkt1_len = 0; - break; - } - - // we do not need reasm buffer anymore - reasm_orig_cancel(ctrack); - rdata_payload = NULL; - - enum dpi_desync_mode desync_mode = dp->desync_mode2 == DESYNC_NONE ? dp->desync_mode : dp->desync_mode2; - switch (desync_mode) - { - case DESYNC_HOSTFAKESPLIT: - // can be 2 or 3 split pos - // if 2 split pos : host, endhost - // if 3 split pos : host, endhost, midhost - if (multisplit_count >= 2) - { - uint8_t *seg; - size_t seg_len, host_size, pos_host, pos_endhost, pos_split_host, sz; - uint8_t *fakehost; - uint16_t ip_id_after_host; - - seg = dis->data_payload; - seg_len = dis->len_payload; - pos_host = multisplit_pos[0]; - pos_endhost = multisplit_pos[1]; - pos_split_host = multisplit_count >= 3 ? multisplit_pos[2] : 0; - host_size = pos_endhost - pos_host; - - if (replay && ctrack_replay->ip_id) ip_id = ctrack_replay->ip_id; - - // before_host segment - pkt1_len = sizeof(pkt1); - if (!prepare_tcp_segment((struct sockaddr *)&src, (struct sockaddr *)&dst, flags_orig, false, 0, - dis->tcp->th_seq, dis->tcp->th_ack, - dis->tcp->th_win, scale_factor, timestamps, - DF, ttl_orig, IP4_TOS(dis->ip), ip_id, IP6_FLOW(dis->ip6), - fooling_orig, 0, 0, 0, - seg, pos_host, pkt1, &pkt1_len)) - goto send_orig; - ip_id = IP4_IP_ID_NEXT(ip_id,dp->ip_id_mode); - DLOG("sending hostfakesplit before_host part 0-%zu len=%zu : ", pos_host - 1, pos_host); - hexdump_limited_dlog(seg, pos_host, PKTDATA_MAXDUMP); DLOG("\n"); - if (!rawsend((struct sockaddr *)&dst, desync_fwmark, ifout, pkt1, pkt1_len)) - goto send_orig; - - if (!(fakehost = malloc(host_size + 1))) - { - DLOG("fakehost out of memory\n"); - goto send_orig; - } - if (*dp->hfs_mod.host) - { - if (host_size <= dp->hfs_mod.host_size) - { - // "google.com" => "gle.com" - // "google.com" => "google.com" - memcpy(fakehost, dp->hfs_mod.host + dp->hfs_mod.host_size - host_size, host_size); - } - else - { - // "google.com" => "nb4auv9.google.com" - // "google.com" => ".google.com" - sz = host_size - dp->hfs_mod.host_size; - memcpy(fakehost + sz, dp->hfs_mod.host, dp->hfs_mod.host_size); - fakehost[--sz] = '.'; - if (sz) - { - fill_random_az(fakehost, 1); - sz--; - } - fill_random_az09(fakehost + 1, sz); - } - } - else - { - fill_random_az(fakehost, 1); - fill_random_az09(fakehost + 1, host_size - 1); - if (host_size >= 7) - { - fakehost[host_size - 4] = '.'; - memcpy(fakehost + host_size - 3, tld[random() % (sizeof(tld) / sizeof(*tld))], 3); - } - } - fakehost[host_size] = 0; - DLOG("generated fake host: %s\n", fakehost); - - // pkt2: fake_host segment - pkt2_len = sizeof(pkt2); - if (!prepare_tcp_segment((struct sockaddr *)&src, (struct sockaddr *)&dst, flags_fake, false, 0, - net32_add(dis->tcp->th_seq, pos_host), dis->tcp->th_ack, dis->tcp->th_win, scale_factor, timestamps, - DF, ttl_fake, IP4_TOS(dis->ip), ip_id, IP6_FLOW(dis->ip6), - dp->desync_fooling_mode, dp->desync_ts_increment, dp->desync_badseq_increment, dp->desync_badseq_ack_increment, - fakehost, host_size, pkt2, &pkt2_len)) - goto send_orig_clean; - if (dp->ip_id_mode!=IPID_SEQ_GROUP) ip_id = IP4_IP_ID_NEXT(ip_id,dp->ip_id_mode); - DLOG("sending hostfakesplit fake host %zu-%zu len=%zu : ", pos_host, pos_endhost - 1, host_size); - hexdump_limited_dlog(fakehost, host_size, PKTDATA_MAXDUMP); DLOG("\n"); - if (!rawsend_rep(dp->desync_repeats, (struct sockaddr *)&dst, desync_fwmark, ifout, pkt2, pkt2_len)) - goto send_orig_clean; - - ip_id_after_host = IP4_IP_ID_NEXT(ip_id,dp->ip_id_mode); - if (pos_split_host) ip_id_after_host = IP4_IP_ID_NEXT(ip_id_after_host,dp->ip_id_mode); - - // pkt3: after_host segment - pkt3_len = sizeof(pkt3); - if (!prepare_tcp_segment((struct sockaddr *)&src, (struct sockaddr *)&dst, flags_orig, false, 0, - net32_add(dis->tcp->th_seq, pos_endhost), dis->tcp->th_ack, - dis->tcp->th_win, scale_factor, timestamps, - DF, ttl_orig, IP4_TOS(dis->ip), ip_id_after_host, IP6_FLOW(dis->ip6), - fooling_orig, 0, 0, 0, - seg + pos_endhost, seg_len - pos_endhost, pkt3, &pkt3_len)) - goto send_orig_clean; - - if (dp->hfs_mod.ordering == 1) - { - DLOG("sending hostfakesplit after_host part %zu-%zu len=%zu : ", pos_endhost, seg_len - 1, seg_len - pos_endhost); - hexdump_limited_dlog(seg + pos_endhost, seg_len - pos_endhost, PKTDATA_MAXDUMP); DLOG("\n"); - if (!rawsend((struct sockaddr *)&dst, desync_fwmark, ifout, pkt3, pkt3_len)) - goto send_orig_clean; - } - - sz = pos_split_host ? pos_split_host - pos_host : host_size; - pkt1_len = sizeof(pkt1); - if (!prepare_tcp_segment((struct sockaddr *)&src, (struct sockaddr *)&dst, flags_orig, false, 0, - net32_add(dis->tcp->th_seq, pos_host), dis->tcp->th_ack, - dis->tcp->th_win, scale_factor, timestamps, - DF, ttl_orig, IP4_TOS(dis->ip), ip_id, IP6_FLOW(dis->ip6), - fooling_orig, 0, 0, 0, - seg + pos_host, sz, pkt1, &pkt1_len)) - goto send_orig_clean; - ip_id = IP4_IP_ID_NEXT(ip_id,dp->ip_id_mode); - - DLOG("sending hostfakesplit real host %s%zu-%zu len=%zu : ", pos_split_host ? "part 1 " : "", pos_host, pos_host + sz - 1, sz); - hexdump_limited_dlog(seg + pos_host, sz, PKTDATA_MAXDUMP); DLOG("\n"); - if (!rawsend((struct sockaddr *)&dst, desync_fwmark, ifout, pkt1, pkt1_len)) - goto send_orig_clean; - - if (pos_split_host) - { - sz = pos_endhost - pos_split_host; - pkt1_len = sizeof(pkt1); - if (!prepare_tcp_segment((struct sockaddr *)&src, (struct sockaddr *)&dst, flags_orig, false, 0, - net32_add(dis->tcp->th_seq, pos_split_host), dis->tcp->th_ack, - dis->tcp->th_win, scale_factor, timestamps, - DF, ttl_orig, IP4_TOS(dis->ip), ip_id, IP6_FLOW(dis->ip6), - fooling_orig, 0, 0, 0, - seg + pos_split_host, sz, pkt1, &pkt1_len)) - goto send_orig_clean; - ip_id = IP4_IP_ID_NEXT(ip_id,dp->ip_id_mode); - DLOG("sending hostfakesplit real host part 2 %zu-%zu len=%zu : ", pos_split_host, pos_endhost - 1, sz); - hexdump_limited_dlog(seg + pos_split_host, sz, PKTDATA_MAXDUMP); DLOG("\n"); - if (!rawsend((struct sockaddr *)&dst, desync_fwmark, ifout, pkt1, pkt1_len)) - goto send_orig_clean; - } - - if (dp->hfs_mod.ordering == 0) - { - if (dp->ip_id_mode!=IPID_SEQ_GROUP) - { - if (dis->ip) ((struct ip*)pkt2)->ip_id = ip_id; - ip_id = IP4_IP_ID_NEXT(ip_id,dp->ip_id_mode); - } - DLOG("sending hostfakesplit fake(2) host %zu-%zu len=%zu : ", pos_host, pos_endhost - 1, host_size); - hexdump_limited_dlog(fakehost, host_size, PKTDATA_MAXDUMP); DLOG("\n"); - if (!rawsend_rep(dp->desync_repeats, (struct sockaddr *)&dst, desync_fwmark, ifout, pkt2, pkt2_len)) - goto send_orig_clean; - - if (dp->ip_id_mode!=IPID_SEQ_GROUP) - { - ip_id_after_host = ip_id; - if (dis->ip) ((struct ip*)pkt3)->ip_id = ip_id; - } - DLOG("sending hostfakesplit after_host part %zu-%zu len=%zu : ", pos_endhost, seg_len - 1, seg_len - pos_endhost); - hexdump_limited_dlog(seg + pos_endhost, seg_len - pos_endhost, PKTDATA_MAXDUMP); DLOG("\n"); - if (!rawsend((struct sockaddr *)&dst, desync_fwmark, ifout, pkt3, pkt3_len)) - goto send_orig_clean; - } - - free(fakehost); - - if (replay) ctrack_replay->ip_id = IP4_IP_ID_NEXT(ip_id_after_host,dp->ip_id_mode); - - return VERDICT_DROP; - send_orig_clean: - free(fakehost); - goto send_orig; - } - else - goto unsplitted_part; - - case DESYNC_MULTISPLIT: - if (multisplit_count) - { - uint8_t ovlseg[DPI_DESYNC_MAX_FAKE_LEN + 100], *seg; - size_t seg_len, from, to; - unsigned int seqovl; - - if (replay && ctrack_replay->ip_id) ip_id = ctrack_replay->ip_id; - - for (i = 0, from = 0; i <= multisplit_count; i++) - { - to = i == multisplit_count ? dis->len_payload : multisplit_pos[i]; - - // do seqovl only to the first packet - // otherwise it's prone to race condition on server side - // what happens first : server pushes socket buffer to process or another packet with seqovl arrives - seqovl = (i == 0 && reasm_offset == 0) ? seqovl_pos : 0; -#ifdef __linux__ - // only linux return error if MTU is exceeded - for (;; seqovl = 0) - { -#endif - if (seqovl) - { - seg_len = to - from + seqovl; - if (seg_len > sizeof(ovlseg)) - { - DLOG("seqovl is too large"); - goto send_orig; - } - fill_pattern(ovlseg, seqovl, dp->seqovl_pattern, sizeof(dp->seqovl_pattern), 0); - memcpy(ovlseg + seqovl, dis->data_payload + from, to - from); - seg = ovlseg; - } - else - { - seqovl = 0; - seg = dis->data_payload + from; - seg_len = to - from; - } - - pkt1_len = sizeof(pkt1); - if (!prepare_tcp_segment((struct sockaddr *)&src, (struct sockaddr *)&dst, flags_orig, false, 0, - net32_add(dis->tcp->th_seq, from - seqovl), dis->tcp->th_ack, - dis->tcp->th_win, scale_factor, timestamps, - DF, ttl_orig, IP4_TOS(dis->ip), ip_id, IP6_FLOW(dis->ip6), - fooling_orig, 0, 0, 0, - seg, seg_len, pkt1, &pkt1_len)) - goto send_orig; - ip_id = IP4_IP_ID_NEXT(ip_id,dp->ip_id_mode); - DLOG("sending multisplit part %d %zu-%zu len=%zu seqovl=%u : ", i + 1, from, to - 1, to - from, seqovl); - hexdump_limited_dlog(seg, seg_len, PKTDATA_MAXDUMP); DLOG("\n"); - if (!rawsend((struct sockaddr *)&dst, desync_fwmark, ifout, pkt1, pkt1_len)) - { -#ifdef __linux__ - if (errno == EMSGSIZE && seqovl) - { - DLOG("MTU exceeded. cancelling seqovl.\n"); - continue; - } -#endif - goto send_orig; - } -#ifdef __linux__ - break; - } -#endif - - from = to; - } - - if (replay) ctrack_replay->ip_id = ip_id; - - return VERDICT_DROP; - } - else - goto unsplitted_part; - - case DESYNC_MULTIDISORDER: - if (multisplit_count) - { - uint8_t ovlseg[DPI_DESYNC_MAX_FAKE_LEN + 100], *seg; - size_t seg_len, from, to; - unsigned int seqovl; - uint16_t ip_id_end; - - if (replay && ctrack_replay->ip_id) ip_id = ctrack_replay->ip_id; - - ip_id_end = ip_id = IP4_IP_ID_ADD(ip_id, (uint16_t)multisplit_count, dp->ip_id_mode); - - for (i = multisplit_count - 1, to = dis->len_payload; i >= -1; i--) - { - from = i >= 0 ? multisplit_pos[i] : 0; - - seg = dis->data_payload + from; - seg_len = to - from; - seqovl = 0; - // do seqovl only to the second packet - // otherwise sequence overlap becomes problematic. overlap algorithm is not too obvious. - // real observations revealed that server can receive overlap junk instead of real data - if (i == 0) - { - if (seqovl_pos >= from) - DLOG("seqovl>=split_pos (%zu>=%zu). cancelling seqovl for part %d.\n", seqovl_pos, from, i + 2); - else - { - seqovl = seqovl_pos; - seg_len = to - from + seqovl; - if (seg_len > sizeof(ovlseg)) - { - DLOG("seqovl is too large"); - goto send_orig; - } - fill_pattern(ovlseg, seqovl, dp->seqovl_pattern, sizeof(dp->seqovl_pattern), 0); - memcpy(ovlseg + seqovl, dis->data_payload + from, to - from); - seg = ovlseg; - } - } - - pkt1_len = sizeof(pkt1); - if (!prepare_tcp_segment((struct sockaddr *)&src, (struct sockaddr *)&dst, flags_orig, false, 0, - net32_add(dis->tcp->th_seq, from - seqovl), dis->tcp->th_ack, - dis->tcp->th_win, scale_factor, timestamps, - DF, ttl_orig, IP4_TOS(dis->ip), ip_id, IP6_FLOW(dis->ip6), - fooling_orig, 0, 0, 0, - seg, seg_len, pkt1, &pkt1_len)) - goto send_orig; - ip_id = IP4_IP_ID_PREV(ip_id,dp->ip_id_mode); - DLOG("sending multisplit part %d %zu-%zu len=%zu seqovl=%u : ", i + 2, from, to - 1, to - from, seqovl); - hexdump_limited_dlog(seg, seg_len, PKTDATA_MAXDUMP); DLOG("\n"); - if (!rawsend((struct sockaddr *)&dst, desync_fwmark, ifout, pkt1, pkt1_len)) - goto send_orig; - - to = from; - } - - if (replay) ctrack_replay->ip_id = IP4_IP_ID_NEXT(ip_id_end,dp->ip_id_mode); - - return VERDICT_DROP; - } - else - goto unsplitted_part; - - case DESYNC_FAKEDDISORDER: - { - uint8_t fakeseg[DPI_DESYNC_MAX_FAKE_LEN + 100], fakeseg2[DPI_DESYNC_MAX_FAKE_LEN + 100], pat[DPI_DESYNC_MAX_FAKE_LEN], *seg; - size_t seg_len, fakeseg2_len; - unsigned int seqovl; - int order; - - if (dis->len_payload > sizeof(pat)) - { - DLOG("packet is too large\n"); - goto send_orig; - } - - if (replay && ctrack_replay->ip_id) ip_id = ctrack_replay->ip_id; - - seqovl = 0; - if (split_pos) - { - order = dp->fs_mod.ordering & 3; - if (seqovl_pos >= split_pos) - DLOG("seqovl>=split_pos (%zu>=%zu). cancelling seqovl.\n", seqovl_pos, split_pos); - else - seqovl = seqovl_pos; - } - else - { - order = (dp->fs_mod.ordering >> 3) & 3; - } - - fill_pattern(pat, dis->len_payload, dp->fsplit_pattern, dp->fsplit_pattern_size, reasm_offset); - - if (seqovl) - { - seg_len = dis->len_payload - split_pos + seqovl; - if (seg_len > sizeof(fakeseg)) - { - DLOG("seqovl is too large\n"); - goto send_orig; - } - fill_pattern(fakeseg, seqovl, dp->seqovl_pattern, sizeof(dp->seqovl_pattern), 0); - memcpy(fakeseg + seqovl, dis->data_payload + split_pos, dis->len_payload - split_pos); - seg = fakeseg; - } - else - { - seg = dis->data_payload + split_pos; - seg_len = dis->len_payload - split_pos; - } - - fakeseg2_len = sizeof(fakeseg2); - if (!prepare_tcp_segment((struct sockaddr *)&src, (struct sockaddr *)&dst, flags_fake, false, 0, net32_add(dis->tcp->th_seq, split_pos), dis->tcp->th_ack, dis->tcp->th_win, scale_factor, timestamps, - DF, ttl_fake, IP4_TOS(dis->ip), ip_id, IP6_FLOW(dis->ip6), - dp->desync_fooling_mode, dp->desync_ts_increment, dp->desync_badseq_increment, dp->desync_badseq_ack_increment, - pat + split_pos, dis->len_payload - split_pos, fakeseg2, &fakeseg2_len)) - goto send_orig; - - if (order == 0) - { - if (dp->ip_id_mode!=IPID_SEQ_GROUP) ip_id = IP4_IP_ID_NEXT(ip_id,dp->ip_id_mode); - DLOG("sending fake(1) 2nd out-of-order tcp segment %zu-%zu len=%zu : ", split_pos, dis->len_payload - 1, dis->len_payload - split_pos); - hexdump_limited_dlog(pat + split_pos, dis->len_payload - split_pos, PKTDATA_MAXDUMP); DLOG("\n"); - if (!rawsend_rep(dp->desync_repeats, (struct sockaddr *)&dst, desync_fwmark, ifout, fakeseg2, fakeseg2_len)) - goto send_orig; - } - - pkt1_len = sizeof(pkt1); - if (!prepare_tcp_segment((struct sockaddr *)&src, (struct sockaddr *)&dst, flags_orig, false, 0, net32_add(dis->tcp->th_seq, split_pos - seqovl), dis->tcp->th_ack, dis->tcp->th_win, scale_factor, timestamps, - DF, ttl_orig, IP4_TOS(dis->ip), ip_id, IP6_FLOW(dis->ip6), - fooling_orig, dp->desync_ts_increment, dp->desync_badseq_increment, dp->desync_badseq_ack_increment, - seg, seg_len, pkt1, &pkt1_len)) - goto send_orig; - ip_id = IP4_IP_ID_NEXT(ip_id,dp->ip_id_mode); - DLOG("sending 2nd out-of-order tcp segment %zu-%zu len=%zu seqovl=%u : ", split_pos, dis->len_payload - 1, dis->len_payload - split_pos, seqovl); - hexdump_limited_dlog(seg, seg_len, PKTDATA_MAXDUMP); DLOG("\n"); - if (!rawsend((struct sockaddr *)&dst, desync_fwmark, ifout, pkt1, pkt1_len)) - goto send_orig; - - if (order <= 1) - { - if (dp->ip_id_mode!=IPID_SEQ_GROUP) - { - if (dis->ip) ((struct ip*)fakeseg2)->ip_id = ip_id; - ip_id = IP4_IP_ID_NEXT(ip_id,dp->ip_id_mode); - } - DLOG("sending fake(2) 2nd out-of-order tcp segment %zu-%zu len=%zu : ", split_pos, dis->len_payload - 1, dis->len_payload - split_pos); - hexdump_limited_dlog(pat + split_pos, dis->len_payload - split_pos, PKTDATA_MAXDUMP); DLOG("\n"); - if (!rawsend_rep(dp->desync_repeats, (struct sockaddr *)&dst, desync_fwmark, ifout, fakeseg2, fakeseg2_len)) - goto send_orig; - } - - if (split_pos) - { - seg_len = sizeof(fakeseg); - if (!prepare_tcp_segment((struct sockaddr *)&src, (struct sockaddr *)&dst, flags_fake, false, 0, dis->tcp->th_seq, dis->tcp->th_ack, dis->tcp->th_win, scale_factor, timestamps, - DF, ttl_fake, IP4_TOS(dis->ip), ip_id, IP6_FLOW(dis->ip6), - dp->desync_fooling_mode, dp->desync_ts_increment, dp->desync_badseq_increment, dp->desync_badseq_ack_increment, - pat, split_pos, fakeseg, &seg_len)) - goto send_orig; - if (dp->ip_id_mode!=IPID_SEQ_GROUP) ip_id = IP4_IP_ID_NEXT(ip_id,dp->ip_id_mode); - DLOG("sending fake(1) 1st out-of-order tcp segment 0-%zu len=%zu : ", split_pos - 1, split_pos); - hexdump_limited_dlog(pat, split_pos, PKTDATA_MAXDUMP); DLOG("\n"); - if (!rawsend_rep(dp->desync_repeats, (struct sockaddr *)&dst, desync_fwmark, ifout, fakeseg, seg_len)) - goto send_orig; - - pkt1_len = sizeof(pkt1); - if (!prepare_tcp_segment((struct sockaddr *)&src, (struct sockaddr *)&dst, flags_orig, false, 0, dis->tcp->th_seq, dis->tcp->th_ack, dis->tcp->th_win, scale_factor, timestamps, - DF, ttl_orig, IP4_TOS(dis->ip), ip_id, IP6_FLOW(dis->ip6), - fooling_orig, dp->desync_ts_increment, dp->desync_badseq_increment, dp->desync_badseq_ack_increment, - dis->data_payload, split_pos, pkt1, &pkt1_len)) - goto send_orig; - ip_id = IP4_IP_ID_NEXT(ip_id,dp->ip_id_mode); - DLOG("sending 1st out-of-order tcp segment 0-%zu len=%zu : ", split_pos - 1, split_pos); - hexdump_limited_dlog(dis->data_payload, split_pos, PKTDATA_MAXDUMP); DLOG("\n"); - if (!rawsend((struct sockaddr *)&dst, desync_fwmark, ifout, pkt1, pkt1_len)) - goto send_orig; - - if (order <= 2) - { - if (dp->ip_id_mode!=IPID_SEQ_GROUP) - { - if (dis->ip) ((struct ip*)fakeseg)->ip_id = ip_id; - ip_id = IP4_IP_ID_NEXT(ip_id,dp->ip_id_mode); - } - DLOG("sending fake(2) 1st out-of-order tcp segment 0-%zu len=%zu : ", split_pos - 1, split_pos); - hexdump_limited_dlog(pat, split_pos, PKTDATA_MAXDUMP); DLOG("\n"); - if (!rawsend_rep(dp->desync_repeats, (struct sockaddr *)&dst, desync_fwmark, ifout, fakeseg, seg_len)) - goto send_orig; - } - } - - if (replay) ctrack_replay->ip_id = ip_id; - - return VERDICT_DROP; - } - - case DESYNC_FAKEDSPLIT: - { - uint8_t fakeseg[DPI_DESYNC_MAX_FAKE_LEN + 100], ovlseg[DPI_DESYNC_MAX_FAKE_LEN + 100], pat[DPI_DESYNC_MAX_FAKE_LEN], *seg; - size_t fakeseg_len, seg_len; - int order; - - if (replay && ctrack_replay->ip_id) ip_id = ctrack_replay->ip_id; - - if (dis->len_payload > sizeof(pat)) - { - DLOG("packet is too large\n"); - goto send_orig; - } - - fill_pattern(pat, dis->len_payload, dp->fsplit_pattern, dp->fsplit_pattern_size, reasm_offset); - - if (split_pos) - { - order = dp->fs_mod.ordering & 3; - } - else - { - order = (dp->fs_mod.ordering >> 3) & 3; - split_pos = dis->len_payload; - } - - fakeseg_len = sizeof(fakeseg); - if (!prepare_tcp_segment((struct sockaddr *)&src, (struct sockaddr *)&dst, flags_fake, false, 0, dis->tcp->th_seq, dis->tcp->th_ack, dis->tcp->th_win, scale_factor, timestamps, - DF, ttl_fake, IP4_TOS(dis->ip), ip_id, IP6_FLOW(dis->ip6), - dp->desync_fooling_mode, dp->desync_ts_increment, dp->desync_badseq_increment, dp->desync_badseq_ack_increment, - pat, split_pos, fakeseg, &fakeseg_len)) - goto send_orig; - - if (order == 0) - { - if (dp->ip_id_mode!=IPID_SEQ_GROUP) ip_id = IP4_IP_ID_NEXT(ip_id,dp->ip_id_mode); - DLOG("sending fake(1) 1st tcp segment 0-%zu len=%zu : ", split_pos - 1, split_pos); - hexdump_limited_dlog(pat, split_pos, PKTDATA_MAXDUMP); DLOG("\n"); - if (!rawsend_rep(dp->desync_repeats, (struct sockaddr *)&dst, desync_fwmark, ifout, fakeseg, fakeseg_len)) - goto send_orig; - } - - unsigned int seqovl = (reasm_offset || split_pos>=dis->len_payload) ? 0 : seqovl_pos; -#ifdef __linux__ - // only linux return error if MTU is exceeded - for (;; seqovl = 0) - { -#endif - if (seqovl) - { - seg_len = split_pos + seqovl; - if (seg_len > sizeof(ovlseg)) - { - DLOG("seqovl is too large\n"); - goto send_orig; - } - fill_pattern(ovlseg, seqovl, dp->seqovl_pattern, sizeof(dp->seqovl_pattern), 0); - memcpy(ovlseg + seqovl, dis->data_payload, split_pos); - seg = ovlseg; - } - else - { - seg = dis->data_payload; - seg_len = split_pos; - } - - pkt1_len = sizeof(pkt1); - if (!prepare_tcp_segment((struct sockaddr *)&src, (struct sockaddr *)&dst, flags_orig, false, 0, net32_add(dis->tcp->th_seq, -seqovl), dis->tcp->th_ack, dis->tcp->th_win, scale_factor, timestamps, - DF, ttl_orig, IP4_TOS(dis->ip), ip_id, IP6_FLOW(dis->ip6), - fooling_orig, dp->desync_ts_increment, dp->desync_badseq_increment, dp->desync_badseq_ack_increment, - seg, seg_len, pkt1, &pkt1_len)) - goto send_orig; - ip_id = IP4_IP_ID_NEXT(ip_id,dp->ip_id_mode); - DLOG("sending 1st tcp segment 0-%zu len=%zu seqovl=%u : ", split_pos - 1, split_pos, seqovl); - hexdump_limited_dlog(seg, seg_len, PKTDATA_MAXDUMP); DLOG("\n"); - if (!rawsend((struct sockaddr *)&dst, desync_fwmark, ifout, pkt1, pkt1_len)) - { -#ifdef __linux__ - if (errno == EMSGSIZE && seqovl) - { - DLOG("MTU exceeded. cancelling seqovl.\n"); - continue; - } -#endif - goto send_orig; - } -#ifdef __linux__ - break; - } -#endif - if (order <= 1) - { - if (dp->ip_id_mode!=IPID_SEQ_GROUP) - { - if (dis->ip) ((struct ip*)fakeseg)->ip_id = ip_id; - ip_id = IP4_IP_ID_NEXT(ip_id,dp->ip_id_mode); - } - DLOG("sending fake(2) 1st tcp segment 0-%zu len=%zu : ", split_pos - 1, split_pos); - hexdump_limited_dlog(pat, split_pos, PKTDATA_MAXDUMP); DLOG("\n"); - if (!rawsend_rep(dp->desync_repeats, (struct sockaddr *)&dst, desync_fwmark, ifout, fakeseg, fakeseg_len)) - goto send_orig; - } - - if (split_pos < dis->len_payload) - { - fakeseg_len = sizeof(fakeseg); - if (!prepare_tcp_segment((struct sockaddr *)&src, (struct sockaddr *)&dst, flags_fake, false, 0, net32_add(dis->tcp->th_seq, split_pos), dis->tcp->th_ack, dis->tcp->th_win, scale_factor, timestamps, - DF, ttl_fake, IP4_TOS(dis->ip), ip_id, IP6_FLOW(dis->ip6), - dp->desync_fooling_mode, dp->desync_ts_increment, dp->desync_badseq_increment, dp->desync_badseq_ack_increment, - pat + split_pos, dis->len_payload - split_pos, fakeseg, &fakeseg_len)) - goto send_orig; - if (dp->ip_id_mode!=IPID_SEQ_GROUP) ip_id = IP4_IP_ID_NEXT(ip_id,dp->ip_id_mode); - DLOG("sending fake(1) 2nd tcp segment %zu-%zu len=%zu : ", split_pos, dis->len_payload - 1, dis->len_payload - split_pos); - hexdump_limited_dlog(pat + split_pos, dis->len_payload - split_pos, PKTDATA_MAXDUMP); DLOG("\n"); - if (!rawsend_rep(dp->desync_repeats, (struct sockaddr *)&dst, desync_fwmark, ifout, fakeseg, fakeseg_len)) - goto send_orig; - - pkt1_len = sizeof(pkt1); - if (!prepare_tcp_segment((struct sockaddr *)&src, (struct sockaddr *)&dst, flags_orig, false, 0, net32_add(dis->tcp->th_seq, split_pos), dis->tcp->th_ack, dis->tcp->th_win, scale_factor, timestamps, - DF, ttl_orig, IP4_TOS(dis->ip), ip_id, IP6_FLOW(dis->ip6), - fooling_orig, dp->desync_ts_increment, dp->desync_badseq_increment, dp->desync_badseq_ack_increment, - dis->data_payload + split_pos, dis->len_payload - split_pos, pkt1, &pkt1_len)) - goto send_orig; - ip_id = IP4_IP_ID_NEXT(ip_id,dp->ip_id_mode); - DLOG("sending 2nd tcp segment %zu-%zu len=%zu : ", split_pos, dis->len_payload - 1, dis->len_payload - split_pos); - hexdump_limited_dlog(dis->data_payload + split_pos, dis->len_payload - split_pos, PKTDATA_MAXDUMP); DLOG("\n"); - if (!rawsend((struct sockaddr *)&dst, desync_fwmark, ifout, pkt1, pkt1_len)) - goto send_orig; - - if (order <= 2) - { - if (dp->ip_id_mode!=IPID_SEQ_GROUP) - { - if (dis->ip) ((struct ip*)fakeseg)->ip_id = ip_id; - ip_id = IP4_IP_ID_NEXT(ip_id,dp->ip_id_mode); - } - DLOG("sending fake(2) 2nd tcp segment %zu-%zu len=%zu : ", split_pos, dis->len_payload - 1, dis->len_payload - split_pos); - hexdump_limited_dlog(pat + split_pos, dis->len_payload - split_pos, PKTDATA_MAXDUMP); DLOG("\n"); - if (!rawsend_rep(dp->desync_repeats, (struct sockaddr *)&dst, desync_fwmark, ifout, fakeseg, fakeseg_len)) - goto send_orig; - } - } - - if (replay) ctrack_replay->ip_id = ip_id; - - return VERDICT_DROP; - } - case DESYNC_IPFRAG2: - if (reasm_offset) - goto unsplitted_part; - else - { - verdict_tcp_csum_fix(verdict, dis->tcp, dis->transport_len, dis->ip, dis->ip6); - - uint8_t *pkt_orig; - size_t pkt_orig_len; - - uint32_t ident = dis->ip ? ip_id ? ip_id : htons(1 + random() % 0xFFFF) : htonl(1 + random() % 0xFFFFFFFF); - size_t ipfrag_pos = (dp->desync_ipfrag_pos_tcp && dp->desync_ipfrag_pos_tcp < dis->transport_len) ? dp->desync_ipfrag_pos_tcp : 24; - - pkt1_len = sizeof(pkt1); - pkt2_len = sizeof(pkt2); - - if (dis->ip6 && (fooling_orig == FOOL_HOPBYHOP || fooling_orig == FOOL_DESTOPT)) - { - pkt_orig_len = sizeof(pkt3); - if (!ip6_insert_simple_hdr(fooling_orig == FOOL_HOPBYHOP ? IPPROTO_HOPOPTS : IPPROTO_DSTOPTS, dis->data_pkt, dis->len_pkt, pkt3, &pkt_orig_len)) - goto send_orig; - pkt_orig = pkt3; - } - else - { - pkt_orig = dis->data_pkt; - pkt_orig_len = dis->len_pkt; - } - - if (!ip_frag(pkt_orig, pkt_orig_len, ipfrag_pos, ident, pkt1, &pkt1_len, pkt2, &pkt2_len)) - goto send_orig; - - DLOG("sending 1st ip fragment 0-%zu ip_payload_len=%zu : ", ipfrag_pos - 1, ipfrag_pos); - hexdump_limited_dlog(pkt1, pkt1_len, IP_MAXDUMP); DLOG("\n"); - if (!rawsend((struct sockaddr *)&dst, desync_fwmark, ifout, pkt1, pkt1_len)) - goto send_orig; - - DLOG("sending 2nd ip fragment %zu-%zu ip_payload_len=%zu : ", ipfrag_pos, dis->transport_len - 1, dis->transport_len - ipfrag_pos); - hexdump_limited_dlog(pkt2, pkt2_len, IP_MAXDUMP); DLOG("\n"); - if (!rawsend((struct sockaddr *)&dst, desync_fwmark, ifout, pkt2, pkt2_len)) - goto send_orig; - - if (replay) ctrack_replay->ip_id = IP4_IP_ID_NEXT(ip_id,dp->ip_id_mode); - - return VERDICT_DROP; - } - default: - break; - } - } - -send_orig: - - if ((verdict & VERDICT_MASK) == VERDICT_DROP) - verdict = ct_new_postnat_fix(ctrack, dis->ip, dis->ip6, dis->tcp); - else - if (tcp_orig_send(verdict, desync_fwmark, ifout, dp, ctrack_replay, dis, bFake)) - verdict = ct_new_postnat_fix(ctrack, dis->ip, dis->ip6, dis->tcp); - return verdict; - -unsplitted_part: - if (replay && dis->ip && ctrack_replay->ip_id) - { - DLOG("changing ip_id of unsplitted part\n"); - dis->ip->ip_id = ctrack_replay->ip_id; - ctrack_replay->ip_id = IP4_IP_ID_NEXT(ctrack_replay->ip_id,dp->ip_id_mode); - return VERDICT_MODIFY; - } - goto send_orig; - -} - -// return : true - should continue, false - should stop with verdict -static bool quic_reasm_cancel(t_ctrack *ctrack, const char *reason) -{ - reasm_orig_cancel(ctrack); - if (ctrack && ctrack->dp && ctrack->dp->desync_any_proto) - { - DLOG("%s. applying tampering because desync_any_proto is set\n", reason); - return true; - } - else - { - DLOG("%s. not applying tampering because desync_any_proto is not set\n", reason); - return false; - } -} - -static uint8_t dpi_desync_udp_packet_play(bool replay, size_t reasm_offset, uint32_t fwmark, const char *ifin, const char *ifout, struct dissect *dis) -{ - uint8_t verdict = VERDICT_PASS; - - // additional safety check - if (!!dis->ip == !!dis->ip6) return verdict; - - struct desync_profile *dp = NULL; - - t_ctrack *ctrack = NULL, *ctrack_replay = NULL; - bool bReverse = false; - bool bFake = false; - - struct sockaddr_storage src, dst; - uint8_t pkt1[DPI_DESYNC_MAX_FAKE_LEN + 100], pkt2[DPI_DESYNC_MAX_FAKE_LEN + 100], pkt3[DPI_DESYNC_MAX_FAKE_LEN + 100]; - size_t pkt1_len, pkt2_len; - uint8_t ttl_orig, ttl_fake; - bool DF; - char host[256]; - t_l7proto l7proto = UNKNOWN; - const char *ifname = NULL, *ssid = NULL; - - extract_endpoints(dis->ip, dis->ip6, NULL, dis->udp, &src, &dst); - - if (replay) - { - // in replay mode conntrack_replay is not NULL and ctrack is NULL - - //ConntrackPoolDump(¶ms.conntrack); - if (!ConntrackPoolDoubleSearch(¶ms.conntrack, dis->ip, dis->ip6, NULL, dis->udp, &ctrack_replay, &bReverse) || bReverse) - return verdict; - - ifname = bReverse ? ifin : ifout; -#ifdef HAS_FILTER_SSID - ssid = wlan_ssid_search_ifname(ifname); - if (ssid) DLOG("found ssid for %s : %s\n", ifname, ssid); -#endif - - dp = ctrack_replay->dp; - if (dp) - DLOG("using cached desync profile %d\n", dp->n); - else if (!ctrack_replay->dp_search_complete) - { - dp = ctrack_replay->dp = dp_find(¶ms.desync_profiles, IPPROTO_UDP, (struct sockaddr *)&dst, ctrack_replay->hostname, ctrack_replay->hostname_is_ip, ctrack_replay->l7proto, ssid, NULL, NULL, NULL); - ctrack_replay->dp_search_complete = true; - } - if (!dp) - { - DLOG("matching desync profile not found\n"); - return verdict; - } - } - else - { - // in real mode ctrack may be NULL or not NULL, conntrack_replay is equal to ctrack - - if (!params.ctrack_disable) - { - ConntrackPoolPurge(¶ms.conntrack); - if (ConntrackPoolFeed(¶ms.conntrack, dis->ip, dis->ip6, NULL, dis->udp, dis->len_payload, &ctrack, &bReverse)) - { - dp = ctrack->dp; - ctrack_replay = ctrack; - } - } - ifname = bReverse ? ifin : ifout; -#ifdef HAS_FILTER_SSID - ssid = wlan_ssid_search_ifname(ifname); - if (ssid) DLOG("found ssid for %s : %s\n", ifname, ssid); -#endif - if (dp) - DLOG("using cached desync profile %d\n", dp->n); - else if (!ctrack || !ctrack->dp_search_complete) - { - const char *hostname = NULL; - bool hostname_is_ip = false; - if (ctrack) - { - hostname = ctrack->hostname; - hostname_is_ip = ctrack->hostname_is_ip; - if (!hostname && !bReverse) - { - if (ipcache_get_hostname(dis->ip ? &dis->ip->ip_dst : NULL, dis->ip6 ? &dis->ip6->ip6_dst : NULL, host, sizeof(host), &hostname_is_ip) && *host) - if (!(hostname = ctrack_replay->hostname = strdup(host))) - DLOG_ERR("strdup(host): out of memory\n"); - } - } - dp = dp_find(¶ms.desync_profiles, IPPROTO_UDP, (struct sockaddr *)&dst, hostname, hostname_is_ip, ctrack ? ctrack->l7proto : UNKNOWN, ssid, NULL, NULL, NULL); - if (ctrack) - { - ctrack->dp = dp; - ctrack->dp_search_complete = true; - } - } - if (!dp) - { - DLOG("matching desync profile not found\n"); - return verdict; - } - maybe_cutoff(ctrack, IPPROTO_UDP); - - HostFailPoolPurgeRateLimited(&dp->hostlist_auto_fail_counters); - //ConntrackPoolDump(¶ms.conntrack); - - if (bReverse) - { - if (ctrack) - { - ttl_orig = dis->ip ? dis->ip->ip_ttl : dis->ip6->ip6_ctlun.ip6_un1.ip6_un1_hlim; - if (!ctrack->incoming_ttl) - { - DLOG("incoming TTL %u\n", ttl_orig); - ctrack->incoming_ttl = ttl_orig; - autottl_rediscover(ctrack, dis->ip ? &dis->ip->ip_src : NULL, dis->ip6 ? &dis->ip6->ip6_src : NULL, ifin); - } - } - return verdict; // nothing to do. do not waste cpu - } - - autottl_discover(ctrack, dis->ip ? &dis->ip->ip_dst : NULL, dis->ip6 ? &dis->ip6->ip6_dst : NULL, ifout); - - if (orig_mod(dp, ctrack, dis)) // ttl can change ! - verdict = VERDICT_MODIFY; - - // start and cutoff limiters - if (!process_desync_interval(dp, ctrack)) goto send_orig; - } - - uint32_t desync_fwmark = fwmark | params.desync_fwmark; - DF = ip_has_df(dis->ip); - - if (dis->len_payload) - { - struct blob_collection_head *fake; - bool bHaveHost = false, bHostIsIp = false; - uint16_t ip_id=0; - - if (replay && ctrack_replay->ip_id) ip_id = ctrack_replay->ip_id; - if (!ip_id) ip_id = IP4_IP_ID_FIX(dis->ip,dp->ip_id_mode); - - if (IsQUICInitial(dis->data_payload, dis->len_payload)) - { - DLOG("packet contains QUIC initial\n"); - l7proto = QUIC; - if (ctrack && ctrack->l7proto == UNKNOWN) ctrack->l7proto = l7proto; - - uint8_t clean[16384], *pclean; - size_t clean_len; - - if (replay) - { - clean_len = ctrack_replay->reasm_orig.size_present; - pclean = ctrack_replay->reasm_orig.packet; - } - else - { - clean_len = sizeof(clean); - pclean = QUICDecryptInitial(dis->data_payload, dis->len_payload, clean, &clean_len) ? clean : NULL; - } - if (pclean) - { - if (ctrack && !ReasmIsEmpty(&ctrack->reasm_orig)) - { - if (ReasmHasSpace(&ctrack->reasm_orig, clean_len)) - { - reasm_orig_feed(ctrack, IPPROTO_UDP, clean, clean_len); - pclean = ctrack->reasm_orig.packet; - clean_len = ctrack->reasm_orig.size_present; - } - else - { - DLOG("QUIC reasm is too long. cancelling.\n"); - reasm_orig_cancel(ctrack); - goto send_orig; // cannot be first packet - } - } - uint8_t defrag[UDP_MAX_REASM]; - size_t hello_offset, hello_len, defrag_len = sizeof(defrag); - bool bFull; - if (QUICDefragCrypto(pclean, clean_len, defrag, &defrag_len, &bFull)) - { - if (bFull) - { - DLOG("QUIC initial contains CRYPTO with full fragment coverage\n"); - - bool bIsHello = IsQUICCryptoHello(defrag, defrag_len, &hello_offset, &hello_len); - bool bReqFull = bIsHello ? IsTLSHandshakeFull(defrag + hello_offset, hello_len) : false; - - DLOG(bIsHello ? bReqFull ? "packet contains full TLS ClientHello\n" : "packet contains partial TLS ClientHello\n" : "packet does not contain TLS ClientHello\n"); - - if (bReqFull) TLSDebugHandshake(defrag + hello_offset, hello_len); - - if (ctrack) - { - if (bIsHello && !bReqFull && ReasmIsEmpty(&ctrack->reasm_orig)) - { - // preallocate max buffer to avoid reallocs that cause memory copy - if (!reasm_orig_start(ctrack, IPPROTO_UDP, UDP_MAX_REASM, UDP_MAX_REASM, clean, clean_len)) - { - reasm_orig_cancel(ctrack); - goto send_orig; - } - } - if (!ReasmIsEmpty(&ctrack->reasm_orig)) - { - verdict_udp_csum_fix(verdict, dis->udp, dis->transport_len, dis->ip, dis->ip6); - if (rawpacket_queue(&ctrack->delayed, &dst, desync_fwmark, ifin, ifout, dis->data_pkt, dis->len_pkt, dis->len_payload)) - { - DLOG("DELAY desync until reasm is complete (#%u)\n", rawpacket_queue_count(&ctrack->delayed)); - } - else - { - DLOG_ERR("rawpacket_queue failed !\n"); - reasm_orig_cancel(ctrack); - goto send_orig; - } - if (bReqFull) - { - replay_queue(&ctrack->delayed); - reasm_orig_fin(ctrack); - } - return ct_new_postnat_fix(ctrack, dis->ip, dis->ip6, NULL); - } - } - - if (bIsHello) - { - bHaveHost = TLSHelloExtractHostFromHandshake(defrag + hello_offset, hello_len, host, sizeof(host), TLS_PARTIALS_ENABLE); - if (!bHaveHost && dp->desync_skip_nosni) - { - reasm_orig_cancel(ctrack); - DLOG("not applying tampering to QUIC ClientHello without hostname in the SNI\n"); - goto send_orig; - } - } - else - { - if (!quic_reasm_cancel(ctrack, "QUIC initial without ClientHello")) goto send_orig; - } - } - else - { - DLOG("QUIC initial contains CRYPTO with partial fragment coverage\n"); - if (ctrack) - { - if (ReasmIsEmpty(&ctrack->reasm_orig)) - { - // preallocate max buffer to avoid reallocs that cause memory copy - if (!reasm_orig_start(ctrack, IPPROTO_UDP, UDP_MAX_REASM, UDP_MAX_REASM, clean, clean_len)) - { - reasm_orig_cancel(ctrack); - goto send_orig; - } - } - verdict_udp_csum_fix(verdict, dis->udp, dis->transport_len, dis->ip, dis->ip6); - if (rawpacket_queue(&ctrack->delayed, &dst, desync_fwmark, ifin, ifout, dis->data_pkt, dis->len_pkt, dis->len_payload)) - { - DLOG("DELAY desync until reasm is complete (#%u)\n", rawpacket_queue_count(&ctrack->delayed)); - } - else - { - DLOG_ERR("rawpacket_queue failed !\n"); - reasm_orig_cancel(ctrack); - goto send_orig; - } - return ct_new_postnat_fix(ctrack, dis->ip, dis->ip6, NULL); - } - if (!quic_reasm_cancel(ctrack, "QUIC initial fragmented CRYPTO")) goto send_orig; - } - } - else - { - // defrag failed - if (!quic_reasm_cancel(ctrack, "QUIC initial defrag CRYPTO failed")) goto send_orig; - } - } - else - { - // decrypt failed - if (!quic_reasm_cancel(ctrack, "QUIC initial decryption failed")) goto send_orig; - } - } - else // not QUIC initial - { - // received payload without host. it means we are out of the request retransmission phase. stop counter - ctrack_stop_retrans_counter(ctrack); - - reasm_orig_cancel(ctrack); - - if (IsWireguardHandshakeInitiation(dis->data_payload, dis->len_payload)) - { - DLOG("packet contains wireguard handshake initiation\n"); - l7proto = WIREGUARD; - if (ctrack && ctrack->l7proto == UNKNOWN) ctrack->l7proto = l7proto; - } - else if (IsDhtD1(dis->data_payload, dis->len_payload)) - { - DLOG("packet contains DHT d1...e\n"); - l7proto = DHT; - if (ctrack && ctrack->l7proto == UNKNOWN) ctrack->l7proto = l7proto; - } - else if (IsDiscordIpDiscoveryRequest(dis->data_payload, dis->len_payload)) - { - DLOG("packet contains discord voice IP discovery\n"); - l7proto = DISCORD; - if (ctrack && ctrack->l7proto == UNKNOWN) ctrack->l7proto = l7proto; - } - else if (IsStunMessage(dis->data_payload, dis->len_payload)) - { - DLOG("packet contains STUN message\n"); - l7proto = STUN; - if (ctrack && ctrack->l7proto == UNKNOWN) ctrack->l7proto = l7proto; - } - else - { - if (!dp->desync_any_proto) - { - DLOG("not applying tampering to unknown protocol\n"); - goto send_orig; - } - DLOG("applying tampering to unknown protocol\n"); - } - } - - if (bHaveHost) - { - bHostIsIp = strip_host_to_ip(host); - DLOG("hostname: %s\n", host); - } - - bool bDiscoveredL7; - if (ctrack_replay) - { - if ((bDiscoveredL7 = !ctrack_replay->l7proto_discovered && ctrack_replay->l7proto != UNKNOWN)) - ctrack_replay->l7proto_discovered = true; - } - else - bDiscoveredL7 = !ctrack_replay && l7proto != UNKNOWN; - if (bDiscoveredL7) DLOG("discovered l7 protocol\n"); - - bool bDiscoveredHostname = bHaveHost && !(ctrack_replay && ctrack_replay->hostname_discovered); - if (bDiscoveredHostname) - { - DLOG("discovered hostname\n"); - if (ctrack_replay) - { - ctrack_replay->hostname_discovered = true; - free(ctrack_replay->hostname); - ctrack_replay->hostname = strdup(host); - ctrack_replay->hostname_is_ip = bHostIsIp; - if (!ctrack_replay->hostname) - { - DLOG_ERR("hostname dup : out of memory"); - goto send_orig; - } - if (!ipcache_put_hostname(dis->ip ? &dis->ip->ip_dst : NULL, dis->ip6 ? &dis->ip6->ip6_dst : NULL, host, bHostIsIp)) - goto send_orig; - } - } - - bool bCheckDone = false, bCheckResult = false, bCheckExcluded = false; - if (bDiscoveredL7 || bDiscoveredHostname) - { - struct desync_profile *dp_prev = dp; - - dp = dp_find(¶ms.desync_profiles, IPPROTO_UDP, (struct sockaddr *)&dst, - ctrack_replay ? ctrack_replay->hostname : bHaveHost ? host : NULL, - ctrack_replay ? ctrack_replay->hostname_is_ip : bHostIsIp, - ctrack_replay ? ctrack_replay->l7proto : l7proto, ssid, - &bCheckDone, &bCheckResult, &bCheckExcluded); - if (ctrack_replay) - { - ctrack_replay->dp = dp; - ctrack_replay->dp_search_complete = true; - ctrack_replay->bCheckDone = bCheckDone; - ctrack_replay->bCheckResult = bCheckResult; - ctrack_replay->bCheckExcluded = bCheckExcluded; - } - if (!dp) - { - reasm_orig_cancel(ctrack); - goto send_orig; - } - if (dp != dp_prev) - { - DLOG("desync profile changed by revealed l7 protocol or hostname !\n"); - autottl_rediscover(ctrack_replay, dis->ip ? &dis->ip->ip_dst : NULL, dis->ip6 ? &dis->ip6->ip6_dst : NULL, ifout); - ip_id = IP4_IP_ID_FIX(dis->ip,dp->ip_id_mode); - // re-evaluate start/cutoff limiters - if (replay) - { - if (orig_mod(dp, ctrack_replay, dis)) // ttl can change ! - verdict = VERDICT_MODIFY; - } - else - { - maybe_cutoff(ctrack, IPPROTO_UDP); - if (orig_mod(dp, ctrack, dis)) // ttl can change ! - verdict = VERDICT_MODIFY; - if (!process_desync_interval(dp, ctrack)) goto send_orig; - } - } - } - else if (ctrack_replay) - { - bCheckDone = ctrack_replay->bCheckDone; - bCheckResult = ctrack_replay->bCheckResult; - bCheckExcluded = ctrack_replay->bCheckExcluded; - } - - if (bHaveHost && !PROFILE_HOSTLISTS_EMPTY(dp)) - { - if (!bCheckDone) - bCheckResult = HostlistCheck(dp, host, bHostIsIp, &bCheckExcluded, false); - if (bCheckResult) - ctrack_stop_retrans_counter(ctrack_replay); - else - { - if (ctrack_replay) - { - ctrack_replay->hostname_ah_check = dp->hostlist_auto && !bCheckExcluded; - if (ctrack_replay->hostname_ah_check) - { - // first request is not retrans - if (!bDiscoveredHostname && !reasm_offset) - process_retrans_fail(ctrack_replay, IPPROTO_UDP, (struct sockaddr*)&src); - } - } - DLOG("not applying tampering to this request\n"); - goto send_orig; - } - } - - // desync profile may have changed after hostname was revealed - switch (l7proto) - { - case QUIC: - fake = &dp->fake_quic; - break; - case WIREGUARD: - fake = &dp->fake_wg; - break; - case DHT: - fake = &dp->fake_dht; - break; - case DISCORD: - fake = &dp->fake_discord; - break; - case STUN: - fake = &dp->fake_stun; - break; - default: - fake = &dp->fake_unknown_udp; - break; - } - - ttl_orig = dis->ip ? dis->ip->ip_ttl : dis->ip6->ip6_ctlun.ip6_un1.ip6_un1_hlim; - ttl_fake = (ctrack_replay && ctrack_replay->desync_autottl) ? ctrack_replay->desync_autottl : (dis->ip6 ? (dp->desync_ttl6 ? dp->desync_ttl6 : ttl_orig) : (dp->desync_ttl ? dp->desync_ttl : ttl_orig)); - - uint32_t fooling_orig = FOOL_NONE; - - if (params.debug) - { - char s1[48], s2[48]; - ntop46_port((struct sockaddr *)&src, s1, sizeof(s1)); - ntop46_port((struct sockaddr *)&dst, s2, sizeof(s2)); - DLOG("dpi desync src=%s dst=%s\n", s1, s2); - } - - switch (dp->desync_mode) - { - case DESYNC_FAKE_KNOWN: - if (l7proto == UNKNOWN) - { - DLOG("not applying fake because of unknown protocol\n"); - break; - } - case DESYNC_FAKE: - if (!reasm_offset) - { - size_t fake_size; - uint8_t *fake_data; - struct blob_item *fake_item; - int n = 0; - - LIST_FOREACH(fake_item, fake, next) - { - n++; - - fake_data = fake_item->data + fake_item->offset; - fake_size = fake_item->size - fake_item->offset; - - pkt1_len = sizeof(pkt1); - if (!prepare_udp_segment((struct sockaddr *)&src, (struct sockaddr *)&dst, - DF, ttl_fake, IP4_TOS(dis->ip), ip_id, IP6_FLOW(dis->ip6), - dp->desync_fooling_mode, NULL, 0, 0, - fake_data, fake_size, pkt1, &pkt1_len)) - { - goto send_orig; - } - DLOG("sending fake[%d] : ", n); - hexdump_limited_dlog(fake_data, fake_size, PKTDATA_MAXDUMP); DLOG("\n"); - if (!rawsend_rep(dp->desync_repeats, (struct sockaddr *)&dst, desync_fwmark, ifout, pkt1, pkt1_len)) - goto send_orig; - ip_id = IP4_IP_ID_NEXT(ip_id,dp->ip_id_mode); - } - bFake = true; - } - break; - case DESYNC_HOPBYHOP: - case DESYNC_DESTOPT: - case DESYNC_IPFRAG1: - fooling_orig = (dp->desync_mode == DESYNC_HOPBYHOP) ? FOOL_HOPBYHOP : (dp->desync_mode == DESYNC_DESTOPT) ? FOOL_DESTOPT : FOOL_IPFRAG1; - if (dis->ip6 && (dp->desync_mode2 == DESYNC_NONE || !desync_valid_second_stage_udp(dp->desync_mode2))) - { - pkt1_len = sizeof(pkt1); - if (!prepare_udp_segment((struct sockaddr *)&src, (struct sockaddr *)&dst, - DF, ttl_orig, 0, 0, IP6_FLOW(dis->ip6), fooling_orig, NULL, 0, 0, - dis->data_payload, dis->len_payload, pkt1, &pkt1_len)) - { - goto send_orig; - } - DLOG("resending original packet with extension header\n"); - if (!rawsend((struct sockaddr *)&dst, desync_fwmark, ifout, pkt1, pkt1_len)) - goto send_orig; - // this mode is final, no other options available - return ct_new_postnat_fix(ctrack, dis->ip, dis->ip6, NULL); - } - break; - default: - pkt1_len = 0; - break; - } - - enum dpi_desync_mode desync_mode = dp->desync_mode2 == DESYNC_NONE ? dp->desync_mode : dp->desync_mode2; - switch (desync_mode) - { - case DESYNC_UDPLEN: - pkt1_len = sizeof(pkt1); - if (!prepare_udp_segment((struct sockaddr *)&src, (struct sockaddr *)&dst, DF, ttl_orig, IP4_TOS(dis->ip), ip_id, IP6_FLOW(dis->ip6), fooling_orig, dp->udplen_pattern, sizeof(dp->udplen_pattern), dp->udplen_increment, dis->data_payload, dis->len_payload, pkt1, &pkt1_len)) - { - DLOG("could not construct packet with modified length. too large ?\n"); - break; - } - DLOG("resending original packet with increased by %d length\n", dp->udplen_increment); - if (!rawsend((struct sockaddr *)&dst, desync_fwmark, ifout, pkt1, pkt1_len)) - goto send_orig; - return ct_new_postnat_fix(ctrack, dis->ip, dis->ip6, NULL); - case DESYNC_TAMPER: - if (IsDhtD1(dis->data_payload, dis->len_payload)) - { - size_t szbuf, szcopy; - memcpy(pkt2, "d2:001:x", 8); - pkt2_len = 8; - szbuf = sizeof(pkt2) - pkt2_len; - szcopy = dis->len_payload - 1; - if (szcopy > szbuf) - { - DLOG("packet is too long to tamper"); - break; - } - memcpy(pkt2 + pkt2_len, dis->data_payload + 1, szcopy); - pkt2_len += szcopy; - pkt1_len = sizeof(pkt1); - if (!prepare_udp_segment((struct sockaddr *)&src, (struct sockaddr *)&dst, DF, ttl_orig, IP4_TOS(dis->ip), ip_id, IP6_FLOW(dis->ip6), fooling_orig, NULL, 0, 0, pkt2, pkt2_len, pkt1, &pkt1_len)) - { - DLOG("could not construct packet with modified length. too large ?\n"); - break; - } - DLOG("resending tampered DHT\n"); - if (!rawsend((struct sockaddr *)&dst, desync_fwmark, ifout, pkt1, pkt1_len)) - goto send_orig; - return ct_new_postnat_fix(ctrack, dis->ip, dis->ip6, NULL); - } - else - { - DLOG("payload is not tamperable\n"); - break; - } - case DESYNC_IPFRAG2: - { - verdict_udp_csum_fix(verdict, dis->udp, dis->transport_len, dis->ip, dis->ip6); - - uint8_t *pkt_orig; - size_t pkt_orig_len; - - uint32_t ident = dis->ip ? ip_id ? ip_id : htons(1 + random() % 0xFFFF) : htonl(1 + random() % 0xFFFFFFFF); - size_t ipfrag_pos = (dp->desync_ipfrag_pos_udp && dp->desync_ipfrag_pos_udp < dis->transport_len) ? dp->desync_ipfrag_pos_udp : sizeof(struct udphdr); - - pkt1_len = sizeof(pkt1); - pkt2_len = sizeof(pkt2); - - if (dis->ip6 && (fooling_orig == FOOL_HOPBYHOP || fooling_orig == FOOL_DESTOPT)) - { - pkt_orig_len = sizeof(pkt3); - if (!ip6_insert_simple_hdr(fooling_orig == FOOL_HOPBYHOP ? IPPROTO_HOPOPTS : IPPROTO_DSTOPTS, dis->data_pkt, dis->len_pkt, pkt3, &pkt_orig_len)) - goto send_orig; - pkt_orig = pkt3; - } - else - { - pkt_orig = dis->data_pkt; - pkt_orig_len = dis->len_pkt; - } - - if (!ip_frag(pkt_orig, pkt_orig_len, ipfrag_pos, ident, pkt1, &pkt1_len, pkt2, &pkt2_len)) - goto send_orig; - - DLOG("sending 1st ip fragment 0-%zu ip_payload_len=%zu : ", ipfrag_pos - 1, ipfrag_pos); - hexdump_limited_dlog(pkt1, pkt1_len, IP_MAXDUMP); DLOG("\n"); - if (!rawsend((struct sockaddr *)&dst, desync_fwmark, ifout, pkt1, pkt1_len)) - goto send_orig; - - DLOG("sending 2nd ip fragment %zu-%zu ip_payload_len=%zu : ", ipfrag_pos, dis->transport_len - 1, dis->transport_len - ipfrag_pos); - hexdump_limited_dlog(pkt2, pkt2_len, IP_MAXDUMP); DLOG("\n"); - if (!rawsend((struct sockaddr *)&dst, desync_fwmark, ifout, pkt2, pkt2_len)) - goto send_orig; - - if (replay) ctrack_replay->ip_id = IP4_IP_ID_NEXT(ip_id,dp->ip_id_mode); - - return ct_new_postnat_fix(ctrack, dis->ip, dis->ip6, NULL); - } - default: - break; - } - } - -send_orig: - if ((verdict & VERDICT_MASK) == VERDICT_DROP) - verdict = ct_new_postnat_fix(ctrack, dis->ip, dis->ip6, NULL); - else - if (udp_orig_send(verdict, desync_fwmark, ifout, dp, ctrack_replay, dis, bFake)) - verdict = ct_new_postnat_fix(ctrack, dis->ip, dis->ip6, NULL); - return verdict; -} - - -static void packet_debug(bool replay, const struct dissect *dis) -{ - if (params.debug) - { - if (replay) DLOG("REPLAY "); - if (dis->ip) - { - char s[66]; - str_ip(s, sizeof(s), dis->ip); - DLOG("IP4: %s", s); - } - else if (dis->ip6) - { - char s[128]; - str_ip6hdr(s, sizeof(s), dis->ip6, dis->proto); - DLOG("IP6: %s", s); - } - if (dis->tcp) - { - char s[80]; - str_tcphdr(s, sizeof(s), dis->tcp); - DLOG(" %s\n", s); - if (dis->len_payload) { DLOG("TCP: len=%zu : ", dis->len_payload); hexdump_limited_dlog(dis->data_payload, dis->len_payload, PKTDATA_MAXDUMP); DLOG("\n"); } - - } - else if (dis->udp) - { - char s[30]; - str_udphdr(s, sizeof(s), dis->udp); - DLOG(" %s\n", s); - if (dis->len_payload) { DLOG("UDP: len=%zu : ", dis->len_payload); hexdump_limited_dlog(dis->data_payload, dis->len_payload, PKTDATA_MAXDUMP); DLOG("\n"); } - } - else - DLOG("\n"); - } -} - - -static uint8_t dpi_desync_packet_play(bool replay, size_t reasm_offset, uint32_t fwmark, const char *ifin, const char *ifout, uint8_t *data_pkt, size_t *len_pkt) -{ - struct dissect dis; - uint8_t verdict = VERDICT_PASS; - - proto_dissect_l3l4(data_pkt, *len_pkt, &dis); - if (!!dis.ip != !!dis.ip6) - { - packet_debug(replay, &dis); - switch (dis.proto) - { - case IPPROTO_TCP: - if (dis.tcp) - { - verdict = dpi_desync_tcp_packet_play(replay, reasm_offset, fwmark, ifin, ifout, &dis); - verdict_tcp_csum_fix(verdict, dis.tcp, dis.transport_len, dis.ip, dis.ip6); - } - break; - case IPPROTO_UDP: - if (dis.udp) - { - verdict = dpi_desync_udp_packet_play(replay, reasm_offset, fwmark, ifin, ifout, &dis); - verdict_udp_csum_fix(verdict, dis.udp, dis.transport_len, dis.ip, dis.ip6); - } - break; - } - *len_pkt = dis.len_pkt; - } - return verdict; -} -uint8_t dpi_desync_packet(uint32_t fwmark, const char *ifin, const char *ifout, uint8_t *data_pkt, size_t *len_pkt) -{ - ipcachePurgeRateLimited(¶ms.ipcache, params.ipcache_lifetime); - return dpi_desync_packet_play(false, 0, fwmark, ifin, ifout, data_pkt, len_pkt); -} - - - -static bool replay_queue(struct rawpacket_tailhead *q) -{ - struct rawpacket *rp; - size_t offset; - unsigned int i; - bool b = true; - for (i = 1, offset = 0; (rp = rawpacket_dequeue(q)); offset += rp->len_payload, rawpacket_free(rp), i++) - { - DLOG("REPLAYING delayed packet #%u offset %zu\n", i, offset); - uint8_t verdict = dpi_desync_packet_play(true, offset, rp->fwmark, rp->ifin, rp->ifout, rp->packet, &rp->len); - switch (verdict & VERDICT_MASK) - { - case VERDICT_MODIFY: - DLOG("SENDING delayed packet #%u modified\n", i); - b &= rawsend_rp(rp); - break; - case VERDICT_PASS: - DLOG("SENDING delayed packet #%u unmodified\n", i); - b &= rawsend_rp(rp); - break; - case VERDICT_DROP: - DLOG("DROPPING delayed packet #%u\n", i); - break; - } - } - return b; -} diff --git a/nfq/desync.h b/nfq/desync.h deleted file mode 100644 index 4eb73613..00000000 --- a/nfq/desync.h +++ /dev/null @@ -1,56 +0,0 @@ -#pragma once - -#include "darkmagic.h" - -#include -#include - -#define __FAVOR_BSD -#include -#include -#include -#include - -#ifdef __linux__ -#define DPI_DESYNC_FWMARK_DEFAULT 0x40000000 -#else -#define DPI_DESYNC_FWMARK_DEFAULT 512 -#endif - -#define DPI_DESYNC_MAX_FAKE_LEN 9216 - -enum dpi_desync_mode { - DESYNC_NONE=0, - DESYNC_INVALID, - DESYNC_FAKE, - DESYNC_FAKE_KNOWN, - DESYNC_RST, - DESYNC_RSTACK, - DESYNC_SYNACK, - DESYNC_SYNDATA, - DESYNC_FAKEDSPLIT, - DESYNC_FAKEDDISORDER, - DESYNC_MULTISPLIT, - DESYNC_MULTIDISORDER, - DESYNC_HOSTFAKESPLIT, - DESYNC_IPFRAG2, - DESYNC_HOPBYHOP, - DESYNC_DESTOPT, - DESYNC_IPFRAG1, - DESYNC_UDPLEN, - DESYNC_TAMPER -}; - -extern const char *fake_http_request_default; -extern const uint8_t fake_tls_clienthello_default[680]; -void randomize_default_tls_payload(uint8_t *p); - -enum dpi_desync_mode desync_mode_from_string(const char *s); -bool desync_valid_zero_stage(enum dpi_desync_mode mode); -bool desync_valid_first_stage(enum dpi_desync_mode mode); -bool desync_only_first_stage(enum dpi_desync_mode mode); -bool desync_valid_second_stage(enum dpi_desync_mode mode); -bool desync_valid_second_stage_tcp(enum dpi_desync_mode mode); -bool desync_valid_second_stage_udp(enum dpi_desync_mode mode); - -uint8_t dpi_desync_packet(uint32_t fwmark, const char *ifin, const char *ifout, uint8_t *data_pkt, size_t *len_pkt); diff --git a/nfq/gzip.c b/nfq/gzip.c deleted file mode 100644 index a3e4eb76..00000000 --- a/nfq/gzip.c +++ /dev/null @@ -1,79 +0,0 @@ -#include "gzip.h" -#include -#include -#include - -#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) < BUFMIN) - { - bufsize += BUFCHUNK; - newbuf = *buf ? realloc(*buf, bufsize) : malloc(bufsize); - if (!newbuf) - { - r = Z_MEM_ERROR; - goto zerr; - } - *buf = newbuf; - } - zs.avail_out = bufsize - *size; - zs.next_out = (unsigned char*)(*buf + *size); - r = inflate(&zs, Z_NO_FLUSH); - if (r != Z_OK && r != Z_STREAM_END) goto zerr; - *size = bufsize - zs.avail_out; - } while (r == Z_OK && zs.avail_in); - } while (r == Z_OK); - - if (*size < bufsize) - { - // free extra space - if ((newbuf = realloc(*buf, *size))) *buf = newbuf; - } - - inflateEnd(&zs); - return Z_OK; - -zerr: - inflateEnd(&zs); - free(*buf); - *buf = NULL; - return r; -} - -bool is_gzip(FILE* F) -{ - unsigned char magic[2]; - bool b = !fseek(F, 0, SEEK_SET) && fread(magic, 1, 2, F) == 2 && magic[0] == 0x1F && magic[1] == 0x8B; - fseek(F, 0, SEEK_SET); - return b; -} diff --git a/nfq/gzip.h b/nfq/gzip.h deleted file mode 100644 index 15e30d2e..00000000 --- a/nfq/gzip.h +++ /dev/null @@ -1,8 +0,0 @@ -#pragma once - -#include -#include -#include - -int z_readfile(FILE *F,char **buf,size_t *size); -bool is_gzip(FILE* F); diff --git a/nfq/helpers.c b/nfq/helpers.c deleted file mode 100644 index 13d74286..00000000 --- a/nfq/helpers.c +++ /dev/null @@ -1,550 +0,0 @@ -#define _GNU_SOURCE - -#include "helpers.h" - -#include -#include -#include -#include -#include -#include -#include -#include - -int unique_size_t(size_t *pu, int ct) -{ - size_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 cmp_size_t(const void * a, const void * b) -{ - return *(size_t*)a < *(size_t*)b ? -1 : *(size_t*)a > *(size_t*)b; -} -void qsort_size_t(size_t *array,size_t ct) -{ - qsort(array,ct,sizeof(*array),cmp_size_t); -} - - -void rtrim(char *s) -{ - if (s) - for (char *p = s + strlen(s) - 1; p >= s && (*p == '\n' || *p == '\r'); p--) *p = '\0'; -} - -void replace_char(char *s, char from, char to) -{ - for(;*s;s++) if (*s==from) *s=to; -} - -char *strncasestr(const char *s, const char *find, size_t slen) -{ - char c, sc; - size_t len; - - if ((c = *find++) != '\0') - { - len = strlen(find); - do - { - do - { - if (slen-- < 1 || (sc = *s++) == '\0') return NULL; - } while (toupper(c) != toupper(sc)); - if (len > slen) return NULL; - } while (strncasecmp(s, find, len) != 0); - s--; - } - return (char *)s; -} - - -bool load_file(const char *filename, void *buffer, size_t *buffer_size) -{ - FILE *F; - - F = fopen(filename, "rb"); - if (!F) return false; - - *buffer_size = fread(buffer, 1, *buffer_size, F); - if (ferror(F)) - { - fclose(F); - return false; - } - - fclose(F); - return true; -} -bool load_file_nonempty(const char *filename, void *buffer, size_t *buffer_size) -{ - bool b = load_file(filename, buffer, buffer_size); - return b && *buffer_size; -} -bool save_file(const char *filename, const void *buffer, size_t buffer_size) -{ - FILE *F; - - F = fopen(filename, "wb"); - if (!F) return false; - - fwrite(buffer, 1, buffer_size, F); - if (ferror(F)) - { - fclose(F); - return false; - } - - fclose(F); - return true; -} -bool append_to_list_file(const char *filename, const char *s) -{ - FILE *F = fopen(filename,"at"); - if (!F) return false; - bool bOK = fprintf(F,"%s\n",s)>0; - fclose(F); - return bOK; -} - -void expand_bits(void *target, const void *source, unsigned int source_bitlen, unsigned int target_bytelen) -{ - unsigned int target_bitlen = target_bytelen<<3; - unsigned int bitlen = target_bitlen>3; - - if ((target_bytelen-bytelen)>=1) memset(target+bytelen,0,target_bytelen-bytelen); - memcpy(target,source,bytelen); - if ((bitlen &= 7)) ((uint8_t*)target)[bytelen] = ((uint8_t*)source)[bytelen] & (~((1 << (8-bitlen)) - 1)); -} - -// " [fd00::1]" => "fd00::1" -// "[fd00::1]:8000" => "fd00::1" -// "127.0.0.1" => "127.0.0.1" -// " 127.0.0.1:8000" => "127.0.0.1" -// " vk.com:8000" => "vk.com" -// return value: true - host is ip addr -bool strip_host_to_ip(char *host) -{ - size_t l; - char *h,*p; - uint8_t addr[16]; - - for (h = host ; *h==' ' || *h=='\t' ; h++); - l = strlen(h); - if (l>=2) - { - if (*h=='[') - { - // ipv6 ? - for (p=++h ; *p && *p!=']' ; p++); - if (*p==']') - { - l = p-h; - memmove(host,h,l); - host[l]=0; - return inet_pton(AF_INET6, host, addr)>0; - } - } - else - { - if (inet_pton(AF_INET6, h, addr)>0) - { - // ipv6 ? - if (host!=h) - { - l = strlen(h); - memmove(host,h,l); - host[l]=0; - } - return true; - } - else - { - // ipv4 ? - for (p=h ; *p && *p!=':' ; p++); - l = p-h; - if (host!=h) memmove(host,h,l); - host[l]=0; - return inet_pton(AF_INET, host, addr)>0; - } - } - } - return false; -} - -void ntop46(const struct sockaddr *sa, char *str, size_t len) -{ - if (!len) return; - *str = 0; - switch (sa->sa_family) - { - case AF_INET: - inet_ntop(sa->sa_family, &((struct sockaddr_in*)sa)->sin_addr, str, len); - break; - case AF_INET6: - inet_ntop(sa->sa_family, &((struct sockaddr_in6*)sa)->sin6_addr, str, len); - break; - default: - snprintf(str, len, "UNKNOWN_FAMILY_%d", sa->sa_family); - } -} -void ntop46_port(const struct sockaddr *sa, char *str, size_t len) -{ - char ip[40]; - ntop46(sa, ip, sizeof(ip)); - switch (sa->sa_family) - { - case AF_INET: - snprintf(str, len, "%s:%u", ip, ntohs(((struct sockaddr_in*)sa)->sin_port)); - break; - case AF_INET6: - snprintf(str, len, "[%s]:%u", ip, ntohs(((struct sockaddr_in6*)sa)->sin6_port)); - break; - default: - snprintf(str, len, "%s", ip); - } -} -void print_sockaddr(const struct sockaddr *sa) -{ - char ip_port[48]; - - ntop46_port(sa, ip_port, sizeof(ip_port)); - printf("%s", ip_port); -} - -bool pton4_port(const char *s, struct sockaddr_in *sa) -{ - char ip[16],*p; - size_t l; - unsigned int u; - - p = strchr(s,':'); - if (!p) return false; - l = p-s; - if (l<7 || l>15) return false; - memcpy(ip,s,l); - ip[l]=0; - p++; - - sa->sin_family = AF_INET; - if (inet_pton(AF_INET,ip,&sa->sin_addr)!=1 || sscanf(p,"%u",&u)!=1 || !u || u>0xFFFF) return false; - sa->sin_port = htons((uint16_t)u); - - return true; -} -bool pton6_port(const char *s, struct sockaddr_in6 *sa) -{ - char ip[40],*p; - size_t l; - unsigned int u; - - if (*s++!='[') return false; - p = strchr(s,']'); - if (!p || p[1]!=':') return false; - l = p-s; - if (l<2 || l>39) return false; - p+=2; - memcpy(ip,s,l); - ip[l]=0; - - sa->sin6_family = AF_INET6; - if (inet_pton(AF_INET6,ip,&sa->sin6_addr)!=1 || sscanf(p,"%u",&u)!=1 || !u || u>0xFFFF) return false; - sa->sin6_port = htons((uint16_t)u); - sa->sin6_flowinfo = 0; - sa->sin6_scope_id = 0; - - return true; -} - -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); -} - - -uint64_t pntoh64(const void *p) -{ - return (uint64_t)*((const uint8_t *)(p)+0) << 56 | - (uint64_t)*((const uint8_t *)(p)+1) << 48 | - (uint64_t)*((const uint8_t *)(p)+2) << 40 | - (uint64_t)*((const uint8_t *)(p)+3) << 32 | - (uint64_t)*((const uint8_t *)(p)+4) << 24 | - (uint64_t)*((const uint8_t *)(p)+5) << 16 | - (uint64_t)*((const uint8_t *)(p)+6) << 8 | - (uint64_t)*((const uint8_t *)(p)+7) << 0; -} -void phton64(uint8_t *p, uint64_t v) -{ - p[0] = (uint8_t)(v >> 56); - p[1] = (uint8_t)(v >> 48); - p[2] = (uint8_t)(v >> 40); - p[3] = (uint8_t)(v >> 32); - p[4] = (uint8_t)(v >> 24); - p[5] = (uint8_t)(v >> 16); - p[6] = (uint8_t)(v >> 8); - p[7] = (uint8_t)(v >> 0); -} - -bool seq_within(uint32_t s, uint32_t s1, uint32_t s2) -{ - return (s2>=s1 && s>=s1 && s<=s2) || (s2=s1)); -} - -bool ipv6_addr_is_zero(const struct in6_addr *a) -{ - return !memcmp(a,"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",16); -} - - -#define INVALID_HEX_DIGIT ((uint8_t)-1) -static inline uint8_t parse_hex_digit(char c) -{ - return (c>='0' && c<='9') ? c-'0' : (c>='a' && c<='f') ? c-'a'+0xA : (c>='A' && c<='F') ? c-'A'+0xA : INVALID_HEX_DIGIT; -} -static inline bool parse_hex_byte(const char *s, uint8_t *pbyte) -{ - uint8_t u,l; - u = parse_hex_digit(s[0]); - l = parse_hex_digit(s[1]); - if (u==INVALID_HEX_DIGIT || l==INVALID_HEX_DIGIT) - { - *pbyte=0; - return false; - } - else - { - *pbyte=(u<<4) | l; - return true; - } -} -bool parse_hex_str(const char *s, uint8_t *pbuf, size_t *size) -{ - uint8_t *pe = pbuf+*size; - *size=0; - while(pbufsize ? size : bufsize; - memcpy(buf,pattern+offset,size); - buf += size; - bufsize -= size; - } - while (bufsize) - { - size = bufsize>patsize ? patsize : bufsize; - memcpy(buf,pattern,size); - buf += size; - bufsize -= size; - } -} - -int fprint_localtime(FILE *F) -{ - struct tm t; - time_t now; - - time(&now); - localtime_r(&now,&t); - return fprintf(F, "%02d.%02d.%04d %02d:%02d:%02d", t.tm_mday, t.tm_mon + 1, t.tm_year + 1900, t.tm_hour, t.tm_min, t.tm_sec); -} - -time_t file_mod_time(const char *filename) -{ - struct stat st; - return stat(filename,&st)==-1 ? 0 : st.st_mtime; -} -bool file_mod_signature(const char *filename, file_mod_sig *ms) -{ - struct stat st; - if (stat(filename,&st)==-1) - { - FILE_MOD_RESET(ms); - return false; - } - ms->mod_time=st.st_mtime; - ms->size=st.st_size; - return true; -} - -bool file_open_test(const char *filename, int flags) -{ - int fd = open(filename,flags); - if (fd>=0) - { - close(fd); - return true; - } - return false; -} - -bool pf_in_range(uint16_t port, const port_filter *pf) -{ - return port && (((!pf->from && !pf->to) || (port>=pf->from && port<=pf->to)) ^ pf->neg); -} -bool pf_parse(const char *s, port_filter *pf) -{ - unsigned int v1,v2; - char c; - - if (!s) return false; - if (*s=='*' && s[1]==0) - { - pf->from=1; pf->to=0xFFFF; - return true; - } - if (*s=='~') - { - pf->neg=true; - s++; - } - else - pf->neg=false; - if (sscanf(s,"%u-%u%c",&v1,&v2,&c)==2) - { - if (v1>65535 || v2>65535 || v1>v2) return false; - pf->from=(uint16_t)v1; - pf->to=(uint16_t)v2; - } - else if (sscanf(s,"%u%c",&v1,&c)==1) - { - if (v1>65535) return false; - pf->to=pf->from=(uint16_t)v1; - } - else - return false; - // deny all case - if (!pf->from && !pf->to) pf->neg=true; - return true; -} -bool pf_is_empty(const port_filter *pf) -{ - return !pf->neg && !pf->from && !pf->to; -} - -void fill_random_bytes(uint8_t *p,size_t sz) -{ - size_t k,sz16 = sz>>1; - for(k=0;kaddr, s_ip, sizeof(s_ip)); - snprintf(s,s_len,cidr->preflen<32 ? "%s/%u" : "%s", s_ip, cidr->preflen); -} -void print_cidr4(const struct cidr4 *cidr) -{ - char s[19]; - str_cidr4(s,sizeof(s),cidr); - printf("%s",s); -} -void str_cidr6(char *s, size_t s_len, const struct cidr6 *cidr) -{ - char s_ip[40]; - *s_ip=0; - inet_ntop(AF_INET6, &cidr->addr, s_ip, sizeof(s_ip)); - snprintf(s,s_len,cidr->preflen<128 ? "%s/%u" : "%s", s_ip, cidr->preflen); -} -void print_cidr6(const struct cidr6 *cidr) -{ - char s[44]; - str_cidr6(s,sizeof(s),cidr); - printf("%s",s); -} -bool parse_cidr4(char *s, struct cidr4 *cidr) -{ - char *p,d; - bool b; - unsigned int plen; - - if ((p = strchr(s, '/'))) - { - if (sscanf(p + 1, "%u", &plen)!=1 || plen>32) - return false; - cidr->preflen = (uint8_t)plen; - d=*p; *p=0; // backup char - } - else - cidr->preflen = 32; - b = (inet_pton(AF_INET, s, &cidr->addr)==1); - if (p) *p=d; // restore char - return b; -} -bool parse_cidr6(char *s, struct cidr6 *cidr) -{ - char *p,d; - bool b; - unsigned int plen; - - if ((p = strchr(s, '/'))) - { - if (sscanf(p + 1, "%u", &plen)!=1 || plen>128) - return false; - cidr->preflen = (uint8_t)plen; - d=*p; *p=0; // backup char - } - else - cidr->preflen = 128; - b = (inet_pton(AF_INET6, s, &cidr->addr)==1); - if (p) *p=d; // restore char - return b; -} diff --git a/nfq/helpers.h b/nfq/helpers.h deleted file mode 100644 index 6e7a8329..00000000 --- a/nfq/helpers.h +++ /dev/null @@ -1,120 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include - -#define UNARY_PLUS(v) (v>0 ? "+" : "") - -// this saves memory. sockaddr_storage is larger than required. it can be 128 bytes. sockaddr_in6 is 28 bytes. -typedef union -{ - struct sockaddr_in sa4; // size 16 - struct sockaddr_in6 sa6; // size 28 - char _align[32]; // force 16-byte alignment for ip6_and int128 ops -} sockaddr_in46; - -int unique_size_t(size_t *pu, int ct); -void qsort_size_t(size_t *array,size_t ct); - -void rtrim(char *s); -void replace_char(char *s, char from, char to); -char *strncasestr(const char *s,const char *find, size_t slen); - -bool load_file(const char *filename,void *buffer,size_t *buffer_size); -bool load_file_nonempty(const char *filename,void *buffer,size_t *buffer_size); -bool save_file(const char *filename, const void *buffer, size_t buffer_size); -bool append_to_list_file(const char *filename, const char *s); - -void expand_bits(void *target, const void *source, unsigned int source_bitlen, unsigned int target_bytelen); - -bool strip_host_to_ip(char *host); - -void print_sockaddr(const struct sockaddr *sa); -void ntop46(const struct sockaddr *sa, char *str, size_t len); -void ntop46_port(const struct sockaddr *sa, char *str, size_t len); -bool pton4_port(const char *s, struct sockaddr_in *sa); -bool pton6_port(const char *s, struct sockaddr_in6 *sa); - -uint16_t saport(const struct sockaddr *sa); - -bool seq_within(uint32_t s, uint32_t s1, uint32_t s2); - -uint64_t pntoh64(const void *p); -void phton64(uint8_t *p, uint64_t v); - -bool ipv6_addr_is_zero(const struct in6_addr *a); - -static inline uint16_t pntoh16(const uint8_t *p) { - return ((uint16_t)p[0] << 8) | (uint16_t)p[1]; -} -static inline void phton16(uint8_t *p, uint16_t v) { - p[0] = (uint8_t)(v >> 8); - p[1] = v & 0xFF; -} -static inline uint32_t pntoh24(const uint8_t *p) { - return ((uint32_t)p[0] << 16) | ((uint32_t)p[1] << 8) | (uint32_t)p[2]; -} -static inline void phton24(uint8_t *p, uint32_t v) { - p[0] = (uint8_t)(v>>16); - p[1] = (uint8_t)(v>>8); - p[2] = (uint8_t)v; -} -static inline uint32_t pntoh32(const uint8_t *p) { - return ((uint32_t)p[0] << 24) | ((uint32_t)p[1] << 16) | ((uint32_t)p[2] << 8) | (uint32_t)p[3]; -} - -bool parse_hex_str(const char *s, uint8_t *pbuf, size_t *size); -void fill_pattern(uint8_t *buf,size_t bufsize,const void *pattern,size_t patsize,size_t offset); - -int fprint_localtime(FILE *F); - -typedef struct -{ - time_t mod_time; - off_t size; -} file_mod_sig; -#define FILE_MOD_COMPARE(ms1,ms2) (((ms1)->mod_time==(ms2)->mod_time) && ((ms1)->size==(ms2)->size)) -#define FILE_MOD_RESET(ms) memset(ms,0,sizeof(file_mod_sig)) -bool file_mod_signature(const char *filename, file_mod_sig *ms); -time_t file_mod_time(const char *filename); -bool file_open_test(const char *filename, int flags); - -typedef struct -{ - uint16_t from,to; - bool neg; -} port_filter; -bool pf_in_range(uint16_t port, const port_filter *pf); -bool pf_parse(const char *s, port_filter *pf); -bool pf_is_empty(const port_filter *pf); - -void fill_random_bytes(uint8_t *p,size_t sz); -void fill_random_az(uint8_t *p,size_t sz); -void fill_random_az09(uint8_t *p,size_t sz); - -void set_console_io_buffering(void); -bool set_env_exedir(const char *argv0); - - -struct cidr4 -{ - struct in_addr addr; - uint8_t preflen; -}; -struct cidr6 -{ - struct in6_addr addr; - uint8_t preflen; -}; -void str_cidr4(char *s, size_t s_len, const struct cidr4 *cidr); -void print_cidr4(const struct cidr4 *cidr); -void str_cidr6(char *s, size_t s_len, const struct cidr6 *cidr); -void print_cidr6(const struct cidr6 *cidr); -bool parse_cidr4(char *s, struct cidr4 *cidr); -bool parse_cidr6(char *s, struct cidr6 *cidr); diff --git a/nfq/hostlist.c b/nfq/hostlist.c deleted file mode 100644 index 2ad649c1..00000000 --- a/nfq/hostlist.c +++ /dev/null @@ -1,340 +0,0 @@ -#include -#include "hostlist.h" -#include "gzip.h" -#include "helpers.h" - -// inplace tolower() and add to pool -static bool addpool(hostlist_pool **hostlist, char **s, const char *end, int *ct) -{ - char *p=*s; - - // comment line - if ( *p == '#' || *p == ';' || *p == '/' || *p == '\r' || *p == '\n') - { - // advance until eol - for (; pfilename) - { - file_mod_sig fsig; - if (!file_mod_signature(hfile->filename, &fsig)) - { - // stat() error - DLOG_PERROR("file_mod_signature"); - DLOG_ERR("cannot access hostlist file '%s'. in-memory content remains unchanged.\n",hfile->filename); - return true; - } - if (FILE_MOD_COMPARE(&hfile->mod_sig,&fsig)) return true; // up to date - HostlistPoolDestroy(&hfile->hostlist); - if (!AppendHostList(&hfile->hostlist, hfile->filename)) - { - HostlistPoolDestroy(&hfile->hostlist); - return false; - } - hfile->mod_sig=fsig; - } - return true; -} -static bool LoadHostLists(struct hostlist_files_head *list) -{ - bool bres=true; - struct hostlist_file *hfile; - - LIST_FOREACH(hfile, list, next) - { - if (!LoadHostList(hfile)) - // at least one failed - bres=false; - } - return bres; -} - -bool NonEmptyHostlist(hostlist_pool **hostlist) -{ - // add impossible hostname if the list is empty - return *hostlist ? true : HostlistPoolAddStrLen(hostlist, "@&()", 4, 0); -} - -static void MakeAutolistsNonEmpty() -{ - struct desync_profile_list *dpl; - LIST_FOREACH(dpl, ¶ms.desync_profiles, next) - { - if (dpl->dp.hostlist_auto) - NonEmptyHostlist(&dpl->dp.hostlist_auto->hostlist); - } -} - -bool LoadAllHostLists() -{ - if (!LoadHostLists(¶ms.hostlists)) - return false; - MakeAutolistsNonEmpty(); - return true; -} - - - -static bool SearchHostList(hostlist_pool *hostlist, const char *host, bool no_match_subdomains) -{ - if (hostlist) - { - const char *p = host; - const struct hostlist_pool *hp; - bool bHostFull=true; - while (p) - { - DLOG("hostlist check for %s : ", p); - hp = HostlistPoolGetStr(hostlist, p); - if (hp) - { - if ((hp->flags & HOSTLIST_POOL_FLAG_STRICT_MATCH) && !bHostFull) - { - DLOG("negative : strict_mismatch : %s != %s\n", p, host); - } - else - { - DLOG("positive\n"); - return true; - } - } - else - DLOG("negative\n"); - if (no_match_subdomains) break; - p = strchr(p, '.'); - if (p) p++; - bHostFull = false; - } - } - return false; -} - - -static bool HostlistsReloadCheck(const struct hostlist_collection_head *hostlists) -{ - struct hostlist_item *item; - LIST_FOREACH(item, hostlists, next) - { - if (!LoadHostList(item->hfile)) - return false; - } - MakeAutolistsNonEmpty(); - return true; -} -bool HostlistsReloadCheckForProfile(const struct desync_profile *dp) -{ - return HostlistsReloadCheck(&dp->hl_collection) && HostlistsReloadCheck(&dp->hl_collection_exclude); -} -// return : true = apply fooling, false = do not apply -static bool HostlistCheck_(const struct hostlist_collection_head *hostlists, const struct hostlist_collection_head *hostlists_exclude, const char *host, bool no_match_subdomains, bool *excluded, bool bSkipReloadCheck) -{ - struct hostlist_item *item; - - if (excluded) *excluded = false; - - if (!bSkipReloadCheck) - if (!HostlistsReloadCheck(hostlists) || !HostlistsReloadCheck(hostlists_exclude)) - return false; - - LIST_FOREACH(item, hostlists_exclude, next) - { - DLOG("[%s] exclude ", item->hfile->filename ? item->hfile->filename : "fixed"); - if (SearchHostList(item->hfile->hostlist, host, no_match_subdomains)) - { - if (excluded) *excluded = true; - return false; - } - } - // old behavior compat: all include lists are empty means check passes - if (!hostlist_collection_is_empty(hostlists)) - { - LIST_FOREACH(item, hostlists, next) - { - DLOG("[%s] include ", item->hfile->filename ? item->hfile->filename : "fixed"); - if (SearchHostList(item->hfile->hostlist, host, no_match_subdomains)) - return true; - } - return false; - } - return true; -} - - -// return : true = apply fooling, false = do not apply -bool HostlistCheck(const struct desync_profile *dp, const char *host, bool no_match_subdomains, bool *excluded, bool bSkipReloadCheck) -{ - DLOG("* hostlist check for profile %d\n",dp->n); - return HostlistCheck_(&dp->hl_collection, &dp->hl_collection_exclude, host, no_match_subdomains, excluded, bSkipReloadCheck); -} - - -static struct hostlist_file *RegisterHostlist_(struct hostlist_files_head *hostlists, struct hostlist_collection_head *hl_collection, const char *filename) -{ - struct hostlist_file *hfile; - - if (filename) - { - if (!(hfile=hostlist_files_search(hostlists, filename))) - if (!(hfile=hostlist_files_add(hostlists, filename))) - return NULL; - if (!hostlist_collection_search(hl_collection, filename)) - if (!hostlist_collection_add(hl_collection, hfile)) - return NULL; - } - else - { - if (!(hfile=hostlist_files_add(hostlists, NULL))) - return NULL; - if (!hostlist_collection_add(hl_collection, hfile)) - return NULL; - } - - return hfile; -} -struct hostlist_file *RegisterHostlist(struct desync_profile *dp, bool bExclude, const char *filename) -{ -/* - if (filename && !file_mod_time(filename)) - { - DLOG_ERR("cannot access hostlist file '%s'\n",filename); - return NULL; - } -*/ - return RegisterHostlist_( - ¶ms.hostlists, - bExclude ? &dp->hl_collection_exclude : &dp->hl_collection, - filename); -} - -void HostlistsDebug() -{ - if (!params.debug) return; - - struct hostlist_file *hfile; - struct desync_profile_list *dpl; - struct hostlist_item *hl_item; - - LIST_FOREACH(hfile, ¶ms.hostlists, next) - { - if (hfile->filename) - DLOG("hostlist file %s%s\n",hfile->filename,hfile->hostlist ? "" : " (empty)"); - else - DLOG("hostlist fixed%s\n",hfile->hostlist ? "" : " (empty)"); - } - - LIST_FOREACH(dpl, ¶ms.desync_profiles, next) - { - LIST_FOREACH(hl_item, &dpl->dp.hl_collection, next) - if (hl_item->hfile!=dpl->dp.hostlist_auto) - { - if (hl_item->hfile->filename) - DLOG("profile %d include hostlist %s%s\n",dpl->dp.n, hl_item->hfile->filename,hl_item->hfile->hostlist ? "" : " (empty)"); - else - DLOG("profile %d include fixed hostlist%s\n",dpl->dp.n, hl_item->hfile->hostlist ? "" : " (empty)"); - } - LIST_FOREACH(hl_item, &dpl->dp.hl_collection_exclude, next) - { - if (hl_item->hfile->filename) - DLOG("profile %d exclude hostlist %s%s\n",dpl->dp.n,hl_item->hfile->filename,hl_item->hfile->hostlist ? "" : " (empty)"); - else - DLOG("profile %d exclude fixed hostlist%s\n",dpl->dp.n,hl_item->hfile->hostlist ? "" : " (empty)"); - } - if (dpl->dp.hostlist_auto) - DLOG("profile %d auto hostlist %s%s\n",dpl->dp.n,dpl->dp.hostlist_auto->filename,dpl->dp.hostlist_auto->hostlist ? "" : " (empty)"); - } -} diff --git a/nfq/hostlist.h b/nfq/hostlist.h deleted file mode 100644 index 954d0cb9..00000000 --- a/nfq/hostlist.h +++ /dev/null @@ -1,17 +0,0 @@ -#pragma once - -#include -#include "pools.h" -#include "params.h" - -bool AppendHostlistItem(hostlist_pool **hostlist, char *s); -bool AppendHostList(hostlist_pool **hostlist, const char *filename); -bool LoadAllHostLists(); -bool NonEmptyHostlist(hostlist_pool **hostlist); -// return : true = apply fooling, false = do not apply -bool HostlistCheck(const struct desync_profile *dp,const char *host, bool no_match_subdomains, bool *excluded, bool bSkipReloadCheck); -struct hostlist_file *RegisterHostlist(struct desync_profile *dp, bool bExclude, const char *filename); -bool HostlistsReloadCheckForProfile(const struct desync_profile *dp); -void HostlistsDebug(); - -#define ResetAllHostlistsModTime() hostlist_files_reset_modtime(¶ms.hostlists) diff --git a/nfq/ipset.c b/nfq/ipset.c deleted file mode 100644 index a853582e..00000000 --- a/nfq/ipset.c +++ /dev/null @@ -1,319 +0,0 @@ -#include -#include "ipset.h" -#include "gzip.h" -#include "helpers.h" - - -// inplace tolower() and add to pool -static bool addpool(ipset *ips, char **s, const char *end, int *ct) -{ - char *p, cidr[128]; - size_t l; - struct cidr4 c4; - struct cidr6 c6; - - // advance until eol - for (p=*s; p=sizeof(cidr)) l=sizeof(cidr)-1; - memcpy(cidr,*s,l); - cidr[l]=0; - rtrim(cidr); - - if (parse_cidr4(cidr,&c4)) - { - if (!ipset4AddCidr(&ips->ips4, &c4)) - { - ipsetDestroy(ips); - return false; - } - if (ct) (*ct)++; - } - else if (parse_cidr6(cidr,&c6)) - { - if (!ipset6AddCidr(&ips->ips6, &c6)) - { - ipsetDestroy(ips); - return false; - } - if (ct) (*ct)++; - } - else - DLOG_ERR("bad ip or subnet : %s\n",cidr); - } - - // advance to the next line - for (; pfilename) - { - file_mod_sig fsig; - if (!file_mod_signature(hfile->filename, &fsig)) - { - // stat() error - DLOG_PERROR("file_mod_signature"); - DLOG_ERR("cannot access ipset file '%s'. in-memory content remains unchanged.\n",hfile->filename); - return true; - } - if (FILE_MOD_COMPARE(&hfile->mod_sig,&fsig)) return true; // up to date - ipsetDestroy(&hfile->ipset); - if (!AppendIpset(&hfile->ipset, hfile->filename)) - { - ipsetDestroy(&hfile->ipset); - return false; - } - hfile->mod_sig=fsig; - } - return true; -} -static bool LoadIpsets(struct ipset_files_head *list) -{ - bool bres=true; - struct ipset_file *hfile; - - LIST_FOREACH(hfile, list, next) - { - if (!LoadIpset(hfile)) - // at least one failed - bres=false; - } - return bres; -} - -bool LoadAllIpsets() -{ - return LoadIpsets(¶ms.ipsets); -} - -static bool SearchIpset(const ipset *ips, const struct in_addr *ipv4, const struct in6_addr *ipv6) -{ - char s_ip[40]; - bool bInSet=false; - - if (!!ipv4 != !!ipv6) - { - *s_ip=0; - if (ipv4) - { - if (params.debug) inet_ntop(AF_INET, ipv4, s_ip, sizeof(s_ip)); - if (ips->ips4) bInSet = ipset4Check(ips->ips4, ipv4, 32); - } - if (ipv6) - { - if (params.debug) inet_ntop(AF_INET6, ipv6, s_ip, sizeof(s_ip)); - if (ips->ips6) bInSet = ipset6Check(ips->ips6, ipv6, 128); - } - DLOG("ipset check for %s : %s\n", s_ip, bInSet ? "positive" : "negative"); - } - else - // ipv4 and ipv6 are both empty or non-empty - DLOG("ipset check error !!!!!!!! ipv4=%p ipv6=%p\n",ipv4,ipv6); - return bInSet; -} - -static bool IpsetsReloadCheck(const struct ipset_collection_head *ipsets) -{ - struct ipset_item *item; - LIST_FOREACH(item, ipsets, next) - { - if (!LoadIpset(item->hfile)) - return false; - } - return true; -} -bool IpsetsReloadCheckForProfile(const struct desync_profile *dp) -{ - return IpsetsReloadCheck(&dp->ips_collection) && IpsetsReloadCheck(&dp->ips_collection_exclude); -} - -static bool IpsetCheck_(const struct ipset_collection_head *ips, const struct ipset_collection_head *ips_exclude, const struct in_addr *ipv4, const struct in6_addr *ipv6) -{ - struct ipset_item *item; - - if (!IpsetsReloadCheck(ips) || !IpsetsReloadCheck(ips_exclude)) - return false; - - LIST_FOREACH(item, ips_exclude, next) - { - DLOG("[%s] exclude ",item->hfile->filename ? item->hfile->filename : "fixed"); - if (SearchIpset(&item->hfile->ipset, ipv4, ipv6)) - return false; - } - // old behavior compat: all include lists are empty means check passes - if (!ipset_collection_is_empty(ips)) - { - LIST_FOREACH(item, ips, next) - { - DLOG("[%s] include ",item->hfile->filename ? item->hfile->filename : "fixed"); - if (SearchIpset(&item->hfile->ipset, ipv4, ipv6)) - return true; - } - return false; - } - return true; -} - -bool IpsetCheck(const struct desync_profile *dp, const struct in_addr *ipv4, const struct in6_addr *ipv6) -{ - if (PROFILE_IPSETS_ABSENT(dp)) return true; - DLOG("* ipset check for profile %d\n",dp->n); - return IpsetCheck_(&dp->ips_collection,&dp->ips_collection_exclude,ipv4,ipv6); -} - - -static struct ipset_file *RegisterIpset_(struct ipset_files_head *ipsets, struct ipset_collection_head *ips_collection, const char *filename) -{ - struct ipset_file *hfile; - if (filename) - { - if (!(hfile=ipset_files_search(ipsets, filename))) - if (!(hfile=ipset_files_add(ipsets, filename))) - return NULL; - if (!ipset_collection_search(ips_collection, filename)) - if (!ipset_collection_add(ips_collection, hfile)) - return NULL; - } - else - { - if (!(hfile=ipset_files_add(ipsets, NULL))) - return NULL; - if (!ipset_collection_add(ips_collection, hfile)) - return NULL; - } - return hfile; -} -struct ipset_file *RegisterIpset(struct desync_profile *dp, bool bExclude, const char *filename) -{ - if (filename && !file_mod_time(filename)) - { - DLOG_ERR("cannot access ipset file '%s'\n",filename); - return NULL; - } - return RegisterIpset_( - ¶ms.ipsets, - bExclude ? &dp->ips_collection_exclude : &dp->ips_collection, - filename); -} - -static const char *dbg_ipset_fill(const ipset *ips) -{ - if (ips->ips4) - if (ips->ips6) - return "ipv4+ipv6"; - else - return "ipv4"; - else - if (ips->ips6) - return "ipv6"; - else - return "empty"; -} -void IpsetsDebug() -{ - if (!params.debug) return; - - struct ipset_file *hfile; - struct desync_profile_list *dpl; - struct ipset_item *ips_item; - - LIST_FOREACH(hfile, ¶ms.ipsets, next) - { - if (hfile->filename) - DLOG("ipset file %s (%s)\n",hfile->filename,dbg_ipset_fill(&hfile->ipset)); - else - DLOG("ipset fixed (%s)\n",dbg_ipset_fill(&hfile->ipset)); - } - - LIST_FOREACH(dpl, ¶ms.desync_profiles, next) - { - LIST_FOREACH(ips_item, &dpl->dp.ips_collection, next) - if (ips_item->hfile->filename) - DLOG("profile %d include ipset %s (%s)\n",dpl->dp.n,ips_item->hfile->filename,dbg_ipset_fill(&ips_item->hfile->ipset)); - else - DLOG("profile %d include fixed ipset (%s)\n",dpl->dp.n,dbg_ipset_fill(&ips_item->hfile->ipset)); - LIST_FOREACH(ips_item, &dpl->dp.ips_collection_exclude, next) - if (ips_item->hfile->filename) - DLOG("profile %d exclude ipset %s (%s)\n",dpl->dp.n,ips_item->hfile->filename,dbg_ipset_fill(&ips_item->hfile->ipset)); - else - DLOG("profile %d exclude fixed ipset (%s)\n",dpl->dp.n,dbg_ipset_fill(&ips_item->hfile->ipset)); - } -} diff --git a/nfq/ipset.h b/nfq/ipset.h deleted file mode 100644 index b711ac50..00000000 --- a/nfq/ipset.h +++ /dev/null @@ -1,14 +0,0 @@ -#pragma once - -#include -#include -#include "params.h" -#include "pools.h" - -bool LoadAllIpsets(); -bool IpsetCheck(const struct desync_profile *dp, const struct in_addr *ipv4, const struct in6_addr *ipv6); -struct ipset_file *RegisterIpset(struct desync_profile *dp, bool bExclude, const char *filename); -void IpsetsDebug(); -bool AppendIpsetItem(ipset *ips, char *ip); - -#define ResetAllIpsetModTime() ipset_files_reset_modtime(¶ms.ipsets) diff --git a/nfq/kavl.h b/nfq/kavl.h deleted file mode 100644 index f3520fd7..00000000 --- a/nfq/kavl.h +++ /dev/null @@ -1,400 +0,0 @@ -/* The MIT License - - Copyright (c) 2018 by Attractive Chaos - - Permission is hereby granted, free of charge, to any person obtaining - a copy of this software and associated documentation files (the - "Software"), to deal in the Software without restriction, including - without limitation the rights to use, copy, modify, merge, publish, - distribute, sublicense, and/or sell copies of the Software, and to - permit persons to whom the Software is furnished to do so, subject to - the following conditions: - - The above copyright notice and this permission notice shall be - included in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - SOFTWARE. -*/ - -/* An example: - -#include -#include -#include -#include "kavl.h" - -struct my_node { - char key; - KAVL_HEAD(struct my_node) head; -}; -#define my_cmp(p, q) (((q)->key < (p)->key) - ((p)->key < (q)->key)) -KAVL_INIT(my, struct my_node, head, my_cmp) - -int main(void) { - const char *str = "MNOLKQOPHIA"; // from wiki, except a duplicate - struct my_node *root = 0; - int i, l = strlen(str); - for (i = 0; i < l; ++i) { // insert in the input order - struct my_node *q, *p = malloc(sizeof(*p)); - p->key = str[i]; - q = kavl_insert(my, &root, p, 0); - if (p != q) free(p); // if already present, free - } - kavl_itr_t(my) itr; - kavl_itr_first(my, root, &itr); // place at first - do { // traverse - const struct my_node *p = kavl_at(&itr); - putchar(p->key); - free((void*)p); // free node - } while (kavl_itr_next(my, &itr)); - putchar('\n'); - return 0; -} -*/ - -#ifndef KAVL_H -#define KAVL_H - -#ifdef __STRICT_ANSI__ -#define inline __inline__ -#endif - -#define KAVL_MAX_DEPTH 64 - -#define kavl_size(head, p) ((p)? (p)->head.size : 0) -#define kavl_size_child(head, q, i) ((q)->head.p[(i)]? (q)->head.p[(i)]->head.size : 0) - -#define KAVL_HEAD(__type) \ - struct { \ - __type *p[2]; \ - signed char balance; /* balance factor */ \ - unsigned size; /* #elements in subtree */ \ - } - -#define __KAVL_FIND(suf, __scope, __type, __head, __cmp) \ - __scope __type *kavl_find_##suf(const __type *root, const __type *x, unsigned *cnt_) { \ - const __type *p = root; \ - unsigned cnt = 0; \ - while (p != 0) { \ - int cmp; \ - cmp = __cmp(x, p); \ - if (cmp >= 0) cnt += kavl_size_child(__head, p, 0) + 1; \ - if (cmp < 0) p = p->__head.p[0]; \ - else if (cmp > 0) p = p->__head.p[1]; \ - else break; \ - } \ - if (cnt_) *cnt_ = cnt; \ - return (__type*)p; \ - } - -#define __KAVL_ROTATE(suf, __type, __head) \ - /* one rotation: (a,(b,c)q)p => ((a,b)p,c)q */ \ - static inline __type *kavl_rotate1_##suf(__type *p, int dir) { /* dir=0 to left; dir=1 to right */ \ - int opp = 1 - dir; /* opposite direction */ \ - __type *q = p->__head.p[opp]; \ - unsigned size_p = p->__head.size; \ - p->__head.size -= q->__head.size - kavl_size_child(__head, q, dir); \ - q->__head.size = size_p; \ - p->__head.p[opp] = q->__head.p[dir]; \ - q->__head.p[dir] = p; \ - return q; \ - } \ - /* two consecutive rotations: (a,((b,c)r,d)q)p => ((a,b)p,(c,d)q)r */ \ - static inline __type *kavl_rotate2_##suf(__type *p, int dir) { \ - int b1, opp = 1 - dir; \ - __type *q = p->__head.p[opp], *r = q->__head.p[dir]; \ - unsigned size_x_dir = kavl_size_child(__head, r, dir); \ - r->__head.size = p->__head.size; \ - p->__head.size -= q->__head.size - size_x_dir; \ - q->__head.size -= size_x_dir + 1; \ - p->__head.p[opp] = r->__head.p[dir]; \ - r->__head.p[dir] = p; \ - q->__head.p[dir] = r->__head.p[opp]; \ - r->__head.p[opp] = q; \ - b1 = dir == 0? +1 : -1; \ - if (r->__head.balance == b1) q->__head.balance = 0, p->__head.balance = -b1; \ - else if (r->__head.balance == 0) q->__head.balance = p->__head.balance = 0; \ - else q->__head.balance = b1, p->__head.balance = 0; \ - r->__head.balance = 0; \ - return r; \ - } - -#define __KAVL_INSERT(suf, __scope, __type, __head, __cmp) \ - __scope __type *kavl_insert_##suf(__type **root_, __type *x, unsigned *cnt_) { \ - unsigned char stack[KAVL_MAX_DEPTH]; \ - __type *path[KAVL_MAX_DEPTH]; \ - __type *bp, *bq; \ - __type *p, *q, *r = 0; /* _r_ is potentially the new root */ \ - int i, which = 0, top, b1, path_len; \ - unsigned cnt = 0; \ - bp = *root_, bq = 0; \ - /* find the insertion location */ \ - for (p = bp, q = bq, top = path_len = 0; p; q = p, p = p->__head.p[which]) { \ - int cmp; \ - cmp = __cmp(x, p); \ - if (cmp >= 0) cnt += kavl_size_child(__head, p, 0) + 1; \ - if (cmp == 0) { \ - if (cnt_) *cnt_ = cnt; \ - return p; \ - } \ - if (p->__head.balance != 0) \ - bq = q, bp = p, top = 0; \ - stack[top++] = which = (cmp > 0); \ - path[path_len++] = p; \ - } \ - if (cnt_) *cnt_ = cnt; \ - x->__head.balance = 0, x->__head.size = 1, x->__head.p[0] = x->__head.p[1] = 0; \ - if (q == 0) *root_ = x; \ - else q->__head.p[which] = x; \ - if (bp == 0) return x; \ - for (i = 0; i < path_len; ++i) ++path[i]->__head.size; \ - for (p = bp, top = 0; p != x; p = p->__head.p[stack[top]], ++top) /* update balance factors */ \ - if (stack[top] == 0) --p->__head.balance; \ - else ++p->__head.balance; \ - if (bp->__head.balance > -2 && bp->__head.balance < 2) return x; /* no re-balance needed */ \ - /* re-balance */ \ - which = (bp->__head.balance < 0); \ - b1 = which == 0? +1 : -1; \ - q = bp->__head.p[1 - which]; \ - if (q->__head.balance == b1) { \ - r = kavl_rotate1_##suf(bp, which); \ - q->__head.balance = bp->__head.balance = 0; \ - } else r = kavl_rotate2_##suf(bp, which); \ - if (bq == 0) *root_ = r; \ - else bq->__head.p[bp != bq->__head.p[0]] = r; \ - return x; \ - } - -#define __KAVL_ERASE(suf, __scope, __type, __head, __cmp) \ - __scope __type *kavl_erase_##suf(__type **root_, const __type *x, unsigned *cnt_) { \ - __type *p, *path[KAVL_MAX_DEPTH], fake; \ - unsigned char dir[KAVL_MAX_DEPTH]; \ - int i, d = 0, cmp; \ - unsigned cnt = 0; \ - fake.__head.p[0] = *root_, fake.__head.p[1] = 0; \ - if (cnt_) *cnt_ = 0; \ - if (x) { \ - for (cmp = -1, p = &fake; cmp; cmp = __cmp(x, p)) { \ - int which = (cmp > 0); \ - if (cmp > 0) cnt += kavl_size_child(__head, p, 0) + 1; \ - dir[d] = which; \ - path[d++] = p; \ - p = p->__head.p[which]; \ - if (p == 0) { \ - if (cnt_) *cnt_ = 0; \ - return 0; \ - } \ - } \ - cnt += kavl_size_child(__head, p, 0) + 1; /* because p==x is not counted */ \ - } else { \ - for (p = &fake, cnt = 1; p; p = p->__head.p[0]) \ - dir[d] = 0, path[d++] = p; \ - p = path[--d]; \ - } \ - if (cnt_) *cnt_ = cnt; \ - for (i = 1; i < d; ++i) --path[i]->__head.size; \ - if (p->__head.p[1] == 0) { /* ((1,.)2,3)4 => (1,3)4; p=2 */ \ - path[d-1]->__head.p[dir[d-1]] = p->__head.p[0]; \ - } else { \ - __type *q = p->__head.p[1]; \ - if (q->__head.p[0] == 0) { /* ((1,2)3,4)5 => ((1)2,4)5; p=3 */ \ - q->__head.p[0] = p->__head.p[0]; \ - q->__head.balance = p->__head.balance; \ - path[d-1]->__head.p[dir[d-1]] = q; \ - path[d] = q, dir[d++] = 1; \ - q->__head.size = p->__head.size - 1; \ - } else { /* ((1,((.,2)3,4)5)6,7)8 => ((1,(2,4)5)3,7)8; p=6 */ \ - __type *r; \ - int e = d++; /* backup _d_ */\ - for (;;) { \ - dir[d] = 0; \ - path[d++] = q; \ - r = q->__head.p[0]; \ - if (r->__head.p[0] == 0) break; \ - q = r; \ - } \ - r->__head.p[0] = p->__head.p[0]; \ - q->__head.p[0] = r->__head.p[1]; \ - r->__head.p[1] = p->__head.p[1]; \ - r->__head.balance = p->__head.balance; \ - path[e-1]->__head.p[dir[e-1]] = r; \ - path[e] = r, dir[e] = 1; \ - for (i = e + 1; i < d; ++i) --path[i]->__head.size; \ - r->__head.size = p->__head.size - 1; \ - } \ - } \ - while (--d > 0) { \ - __type *q = path[d]; \ - int which, other, b1 = 1, b2 = 2; \ - which = dir[d], other = 1 - which; \ - if (which) b1 = -b1, b2 = -b2; \ - q->__head.balance += b1; \ - if (q->__head.balance == b1) break; \ - else if (q->__head.balance == b2) { \ - __type *r = q->__head.p[other]; \ - if (r->__head.balance == -b1) { \ - path[d-1]->__head.p[dir[d-1]] = kavl_rotate2_##suf(q, which); \ - } else { \ - path[d-1]->__head.p[dir[d-1]] = kavl_rotate1_##suf(q, which); \ - if (r->__head.balance == 0) { \ - r->__head.balance = -b1; \ - q->__head.balance = b1; \ - break; \ - } else r->__head.balance = q->__head.balance = 0; \ - } \ - } \ - } \ - *root_ = fake.__head.p[0]; \ - return p; \ - } - -#define kavl_free(__type, __head, __root, __free) do { \ - __type *_p, *_q; \ - for (_p = __root; _p; _p = _q) { \ - if (_p->__head.p[0] == 0) { \ - _q = _p->__head.p[1]; \ - __free(_p); \ - } else { \ - _q = _p->__head.p[0]; \ - _p->__head.p[0] = _q->__head.p[1]; \ - _q->__head.p[1] = _p; \ - } \ - } \ - } while (0) - -#define __KAVL_ITR(suf, __scope, __type, __head, __cmp) \ - struct kavl_itr_##suf { \ - const __type *stack[KAVL_MAX_DEPTH], **top, *right; /* _right_ points to the right child of *top */ \ - }; \ - __scope void kavl_itr_first_##suf(const __type *root, struct kavl_itr_##suf *itr) { \ - const __type *p; \ - for (itr->top = itr->stack - 1, p = root; p; p = p->__head.p[0]) \ - *++itr->top = p; \ - itr->right = (*itr->top)->__head.p[1]; \ - } \ - __scope int kavl_itr_find_##suf(const __type *root, const __type *x, struct kavl_itr_##suf *itr) { \ - const __type *p = root; \ - itr->top = itr->stack - 1; \ - while (p != 0) { \ - int cmp; \ - cmp = __cmp(x, p); \ - if (cmp < 0) *++itr->top = p, p = p->__head.p[0]; \ - else if (cmp > 0) p = p->__head.p[1]; \ - else break; \ - } \ - if (p) { \ - *++itr->top = p; \ - itr->right = p->__head.p[1]; \ - return 1; \ - } else if (itr->top >= itr->stack) { \ - itr->right = (*itr->top)->__head.p[1]; \ - return 0; \ - } else return 0; \ - } \ - __scope int kavl_itr_next_##suf(struct kavl_itr_##suf *itr) { \ - for (;;) { \ - const __type *p; \ - for (p = itr->right, --itr->top; p; p = p->__head.p[0]) \ - *++itr->top = p; \ - if (itr->top < itr->stack) return 0; \ - itr->right = (*itr->top)->__head.p[1]; \ - return 1; \ - } \ - } - -/** - * Insert a node to the tree - * - * @param suf name suffix used in KAVL_INIT() - * @param proot pointer to the root of the tree (in/out: root may change) - * @param x node to insert (in) - * @param cnt number of nodes smaller than or equal to _x_; can be NULL (out) - * - * @return _x_ if not present in the tree, or the node equal to x. - */ -#define kavl_insert(suf, proot, x, cnt) kavl_insert_##suf(proot, x, cnt) - -/** - * Find a node in the tree - * - * @param suf name suffix used in KAVL_INIT() - * @param root root of the tree - * @param x node value to find (in) - * @param cnt number of nodes smaller than or equal to _x_; can be NULL (out) - * - * @return node equal to _x_ if present, or NULL if absent - */ -#define kavl_find(suf, root, x, cnt) kavl_find_##suf(root, x, cnt) - -/** - * Delete a node from the tree - * - * @param suf name suffix used in KAVL_INIT() - * @param proot pointer to the root of the tree (in/out: root may change) - * @param x node value to delete; if NULL, delete the first node (in) - * - * @return node removed from the tree if present, or NULL if absent - */ -#define kavl_erase(suf, proot, x, cnt) kavl_erase_##suf(proot, x, cnt) -#define kavl_erase_first(suf, proot) kavl_erase_##suf(proot, 0, 0) - -#define kavl_itr_t(suf) struct kavl_itr_##suf - -/** - * Place the iterator at the smallest object - * - * @param suf name suffix used in KAVL_INIT() - * @param root root of the tree - * @param itr iterator - */ -#define kavl_itr_first(suf, root, itr) kavl_itr_first_##suf(root, itr) - -/** - * Place the iterator at the object equal to or greater than the query - * - * @param suf name suffix used in KAVL_INIT() - * @param root root of the tree - * @param x query (in) - * @param itr iterator (out) - * - * @return 1 if find; 0 otherwise. kavl_at(itr) is NULL if and only if query is - * larger than all objects in the tree - */ -#define kavl_itr_find(suf, root, x, itr) kavl_itr_find_##suf(root, x, itr) - -/** - * Move to the next object in order - * - * @param itr iterator (modified) - * - * @return 1 if there is a next object; 0 otherwise - */ -#define kavl_itr_next(suf, itr) kavl_itr_next_##suf(itr) - -/** - * Return the pointer at the iterator - * - * @param itr iterator - * - * @return pointer if present; NULL otherwise - */ -#define kavl_at(itr) ((itr)->top < (itr)->stack? 0 : *(itr)->top) - -#define KAVL_INIT2(suf, __scope, __type, __head, __cmp) \ - __KAVL_FIND(suf, __scope, __type, __head, __cmp) \ - __KAVL_ROTATE(suf, __type, __head) \ - __KAVL_INSERT(suf, __scope, __type, __head, __cmp) \ - __KAVL_ERASE(suf, __scope, __type, __head, __cmp) \ - __KAVL_ITR(suf, __scope, __type, __head, __cmp) - -#define KAVL_INIT(suf, __type, __head, __cmp) \ - KAVL_INIT2(suf,, __type, __head, __cmp) - -#endif diff --git a/nfq/nfqws.c b/nfq/nfqws.c deleted file mode 100644 index a24c1489..00000000 --- a/nfq/nfqws.c +++ /dev/null @@ -1,3642 +0,0 @@ -#define _GNU_SOURCE - -#include "nfqws.h" -#include "sec.h" -#include "desync.h" -#include "helpers.h" -#include "checksum.h" -#include "params.h" -#include "protocol.h" -#include "hostlist.h" -#include "ipset.h" -#include "gzip.h" -#include "pools.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#ifdef __CYGWIN__ -#include "win.h" -#endif - -#ifdef USE_SYSTEMD -#include -#endif - -#ifdef __linux__ -#include -#define NF_DROP 0 -#define NF_ACCEPT 1 -#endif - -#define CTRACK_T_SYN 60 -#define CTRACK_T_FIN 60 -#define CTRACK_T_EST 300 -#define CTRACK_T_UDP 60 - -#define MAX_CONFIG_FILE_SIZE 16384 - -struct params_s params; -static bool bReload = false; -#ifdef __CYGWIN__ -bool bQuit = false; -#endif - -static void onhup(int sig) -{ - printf("HUP received ! Lists will be reloaded.\n"); - bReload = true; -} -static void ReloadCheck() -{ - if (bReload) - { - ResetAllHostlistsModTime(); - if (!LoadAllHostLists()) - { - DLOG_ERR("hostlists load failed. this is fatal.\n"); - exit(1); - } - ResetAllIpsetModTime(); - if (!LoadAllIpsets()) - { - DLOG_ERR("ipset load failed. this is fatal.\n"); - exit(1); - } - bReload = false; - } -} - -static void onusr1(int sig) -{ - printf("\nCONNTRACK DUMP\n"); - ConntrackPoolDump(¶ms.conntrack); - printf("\n"); -} -static void onusr2(int sig) -{ - printf("\nHOSTFAIL POOL DUMP\n"); - - struct desync_profile_list *dpl; - LIST_FOREACH(dpl, ¶ms.desync_profiles, next) - { - printf("\nDESYNC PROFILE %d\n", dpl->dp.n); - HostFailPoolDump(dpl->dp.hostlist_auto_fail_counters); - } - if (params.autottl_present || params.cache_hostname) - { - printf("\nIPCACHE\n"); - ipcachePrint(¶ms.ipcache); - } - printf("\n"); -} - -static void pre_desync(void) -{ - signal(SIGHUP, onhup); - signal(SIGUSR1, onusr1); - signal(SIGUSR2, onusr2); -} - - -static uint8_t processPacketData(uint32_t *mark, const char *ifin, const char *ifout, uint8_t *data_pkt, size_t *len_pkt) -{ -#ifdef __linux__ - if (*mark & params.desync_fwmark) - { - DLOG("ignoring generated packet\n"); - return VERDICT_PASS; - } -#endif - return dpi_desync_packet(*mark, ifin, ifout, data_pkt, len_pkt); -} - - -static bool test_list_files() -{ - struct hostlist_file *hfile; - struct ipset_file *ifile; - - LIST_FOREACH(hfile, ¶ms.hostlists, next) - if (hfile->filename && !file_open_test(hfile->filename, O_RDONLY)) - { - DLOG_PERROR("file_open_test"); - DLOG_ERR("cannot access hostlist file '%s'\n", hfile->filename); - return false; - } - LIST_FOREACH(ifile, ¶ms.ipsets, next) - if (ifile->filename && !file_open_test(ifile->filename, O_RDONLY)) - { - DLOG_PERROR("file_open_test"); - DLOG_ERR("cannot access ipset file '%s'\n", ifile->filename); - return false; - } - return true; -} - - -#ifdef __linux__ -static int nfq_cb(struct nfq_q_handle *qh, struct nfgenmsg *nfmsg, struct nfq_data *nfa, void *cookie) -{ - int id, ilen; - size_t len; - struct nfqnl_msg_packet_hdr *ph; - uint8_t *data; - uint32_t ifidx_out, ifidx_in; - char ifout[IFNAMSIZ], ifin[IFNAMSIZ]; - - ph = nfq_get_msg_packet_hdr(nfa); - id = ph ? ntohl(ph->packet_id) : 0; - - uint32_t mark = nfq_get_nfmark(nfa); - ilen = nfq_get_payload(nfa, &data); - - ifidx_out = nfq_get_outdev(nfa); - *ifout = 0; - if (ifidx_out) if_indextoname(ifidx_out, ifout); - - ifidx_in = nfq_get_indev(nfa); - *ifin = 0; - if (ifidx_in) if_indextoname(ifidx_in, ifin); - - DLOG("\npacket: id=%d len=%d mark=%08X ifin=%s(%u) ifout=%s(%u)\n", id, ilen, mark, ifin, ifidx_in, ifout, ifidx_out); - - if (ilen >= 0) - { - len = ilen; - uint8_t verdict = processPacketData(&mark, ifin, ifout, data, &len); - switch (verdict & VERDICT_MASK) - { - case VERDICT_MODIFY: - DLOG("packet: id=%d pass modified. len=%zu\n", id, len); - return nfq_set_verdict2(qh, id, NF_ACCEPT, mark, (uint32_t)len, data); - case VERDICT_DROP: - DLOG("packet: id=%d drop\n", id); - return nfq_set_verdict2(qh, id, NF_DROP, mark, 0, NULL); - } - } - DLOG("packet: id=%d pass unmodified\n", id); - return nfq_set_verdict2(qh, id, NF_ACCEPT, mark, 0, NULL); -} -static void nfq_deinit(struct nfq_handle **h, struct nfq_q_handle **qh) -{ - if (*qh) - { - DLOG_CONDUP("unbinding from queue %u\n", params.qnum); - nfq_destroy_queue(*qh); - *qh = NULL; - } - if (*h) - { - DLOG_CONDUP("closing library handle\n"); - nfq_close(*h); - *h = NULL; - } -} -static bool nfq_init(struct nfq_handle **h, struct nfq_q_handle **qh) -{ - nfq_deinit(h, qh); - - DLOG_CONDUP("opening library handle\n"); - *h = nfq_open(); - if (!*h) { - DLOG_PERROR("nfq_open()"); - goto exiterr; - } - - DLOG_CONDUP("unbinding existing nf_queue handler for AF_INET (if any)\n"); - if (nfq_unbind_pf(*h, AF_INET) < 0) { - DLOG_PERROR("nfq_unbind_pf()"); - goto exiterr; - } - - DLOG_CONDUP("binding nfnetlink_queue as nf_queue handler for AF_INET\n"); - if (nfq_bind_pf(*h, AF_INET) < 0) { - DLOG_PERROR("nfq_bind_pf()"); - goto exiterr; - } - - DLOG_CONDUP("binding this socket to queue '%u'\n", params.qnum); - *qh = nfq_create_queue(*h, params.qnum, &nfq_cb, ¶ms); - if (!*qh) { - DLOG_PERROR("nfq_create_queue()"); - goto exiterr; - } - - DLOG_CONDUP("setting copy_packet mode\n"); - if (nfq_set_mode(*qh, NFQNL_COPY_PACKET, 0xffff) < 0) { - DLOG_PERROR("can't set packet_copy mode"); - goto exiterr; - } - if (nfq_set_queue_maxlen(*qh, Q_MAXLEN) < 0) { - DLOG_PERROR("can't set queue maxlen"); - 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)) - { - DLOG_ERR("can't set queue flags. its OK on linux <3.6\n"); - // dot not fail. not supported on old linuxes <3.6 - } - - DLOG_CONDUP("initializing raw sockets bind-fix4=%u bind-fix6=%u\n", params.bind_fix4, params.bind_fix6); - if (!rawsend_preinit(params.bind_fix4, params.bind_fix6)) - goto exiterr; - - int yes = 1, fd = nfq_fd(*h); - -#if defined SOL_NETLINK && defined NETLINK_NO_ENOBUFS - if (setsockopt(fd, SOL_NETLINK, NETLINK_NO_ENOBUFS, &yes, sizeof(yes)) == -1) - DLOG_PERROR("setsockopt(NETLINK_NO_ENOBUFS)"); -#endif - - return true; -exiterr: - nfq_deinit(h, qh); - return false; -} - -static void notify_ready(void) -{ -#ifdef USE_SYSTEMD - int r = sd_notify(0, "READY=1"); - if (r < 0) - DLOG_ERR("sd_notify: %s\n", strerror(-r)); -#endif -} - -static int nfq_main(void) -{ - uint8_t buf[16384] __attribute__((aligned)); - struct nfq_handle *h = NULL; - struct nfq_q_handle *qh = NULL; - int fd, e; - ssize_t rd; - FILE *Fpid = NULL; - - if (*params.pidfile && !(Fpid = fopen(params.pidfile, "w"))) - { - DLOG_PERROR("create pidfile"); - return 1; - } - - if (params.droproot && !droproot(params.uid, params.user, params.gid, params.gid_count) || !dropcaps()) - goto err; - print_id(); - if (params.droproot && !test_list_files()) - goto err; - - if (!nfq_init(&h, &qh)) - goto err; - -#ifdef HAS_FILTER_SSID - if (params.filter_ssid_present) - { - if (!wlan_info_init()) - { - DLOG_ERR("cannot initialize wlan info capture\n"); - goto err; - } - DLOG("wlan info capture initialized\n"); - } -#endif - - if (params.daemon) daemonize(); - - sec_harden(); - - if (Fpid) - { - if (fprintf(Fpid, "%d", getpid()) <= 0) - { - DLOG_PERROR("write pidfile"); - goto err; - } - fclose(Fpid); - Fpid = NULL; - } - - pre_desync(); - notify_ready(); - - fd = nfq_fd(h); - do - { - while ((rd = recv(fd, buf, sizeof(buf), 0)) >= 0) - { - ReloadCheck(); -#ifdef HAS_FILTER_SSID - if (params.filter_ssid_present) - if (!wlan_info_get_rate_limited()) - DLOG_ERR("cannot get wlan info\n"); -#endif - if (rd) - { - int r = nfq_handle_packet(h, (char *)buf, (int)rd); - if (r) DLOG_ERR("nfq_handle_packet error %d\n", r); - } - else - DLOG("recv from nfq returned 0 !\n"); - } - e = errno; - DLOG_ERR("recv: recv=%zd errno %d\n", rd, e); - errno = e; - DLOG_PERROR("recv"); - // do not fail on ENOBUFS - } while (e == ENOBUFS); - - nfq_deinit(&h, &qh); -#ifdef HAS_FILTER_SSID - wlan_info_deinit(); -#endif - return 0; -err: - if (Fpid) fclose(Fpid); - nfq_deinit(&h, &qh); -#ifdef HAS_FILTER_SSID - wlan_info_deinit(); -#endif - return 1; -} - -#elif defined(BSD) - -static int dvt_main(void) -{ - uint8_t buf[16384] __attribute__((aligned)); - struct sockaddr_storage sa_from; - int fd[2] = { -1,-1 }; // 4,6 - int i, r, res = 1, fdct = 1, fdmax; - unsigned int id = 0; - socklen_t socklen; - ssize_t rd, wr; - fd_set fdset; - FILE *Fpid = NULL; - - if (*params.pidfile && !(Fpid = fopen(params.pidfile, "w"))) - { - DLOG_PERROR("create pidfile"); - return 1; - } - - { - struct sockaddr_in bp4; - bp4.sin_family = AF_INET; - bp4.sin_port = htons(params.port); - bp4.sin_addr.s_addr = INADDR_ANY; - - DLOG_CONDUP("creating divert4 socket\n"); - fd[0] = socket_divert(AF_INET); - if (fd[0] == -1) { - DLOG_PERROR("socket (DIVERT4)"); - goto exiterr; - } - DLOG_CONDUP("binding divert4 socket\n"); - if (bind(fd[0], (struct sockaddr*)&bp4, sizeof(bp4)) < 0) - { - DLOG_PERROR("bind (DIVERT4)"); - goto exiterr; - } - } - - -#ifdef __OpenBSD__ - { - // in OpenBSD must use separate divert sockets for ipv4 and ipv6 - struct sockaddr_in6 bp6; - memset(&bp6, 0, sizeof(bp6)); - bp6.sin6_family = AF_INET6; - bp6.sin6_port = htons(params.port); - - DLOG_CONDUP("creating divert6 socket\n"); - fd[1] = socket_divert(AF_INET6); - if (fd[1] == -1) { - DLOG_PERROR("socket (DIVERT6)"); - goto exiterr; - } - DLOG_CONDUP("binding divert6 socket\n"); - if (bind(fd[1], (struct sockaddr*)&bp6, sizeof(bp6)) < 0) - { - DLOG_PERROR("bind (DIVERT6)"); - goto exiterr; - } - fdct++; - } -#endif - fdmax = (fd[0] > fd[1] ? fd[0] : fd[1]) + 1; - - DLOG_CONDUP("initializing raw sockets\n"); - if (!rawsend_preinit(false, false)) - goto exiterr; - - - if (params.droproot && !droproot(params.uid, params.user, params.gid, params.gid_count)) - goto exiterr; - print_id(); - if (params.droproot && !test_list_files()) - goto exiterr; - - if (params.daemon) daemonize(); - - if (Fpid) - { - if (fprintf(Fpid, "%d", getpid()) <= 0) - { - DLOG_PERROR("write pidfile"); - goto exiterr; - } - fclose(Fpid); - Fpid = NULL; - } - - pre_desync(); - - for (;;) - { - FD_ZERO(&fdset); - for (i = 0; i < fdct; i++) FD_SET(fd[i], &fdset); - r = select(fdmax, &fdset, NULL, NULL, NULL); - if (r == -1) - { - if (errno == EINTR) - { - // a signal received - continue; - } - DLOG_PERROR("select"); - goto exiterr; - } - for (i = 0; i < fdct; i++) - { - if (FD_ISSET(fd[i], &fdset)) - { - socklen = sizeof(sa_from); - rd = recvfrom(fd[i], buf, sizeof(buf), 0, (struct sockaddr*)&sa_from, &socklen); - if (rd < 0) - { - DLOG_PERROR("recvfrom"); - goto exiterr; - } - else if (rd > 0) - { - uint32_t mark = 0; - uint8_t verdict; - size_t len = rd; - - ReloadCheck(); - - DLOG("\npacket: id=%u len=%zu\n", id, len); - verdict = processPacketData(&mark, NULL, NULL, buf, &len); - switch (verdict & VERDICT_MASK) - { - case VERDICT_PASS: - case VERDICT_MODIFY: - if ((verdict & VERDICT_MASK) == VERDICT_PASS) - DLOG("packet: id=%u reinject unmodified\n", id); - else - DLOG("packet: id=%u reinject modified len=%zu\n", id, len); - wr = sendto(fd[i], buf, len, 0, (struct sockaddr*)&sa_from, socklen); - if (wr < 0) - DLOG_PERROR("reinject sendto"); - else if (wr != len) - DLOG_ERR("reinject sendto: not all data was reinjected. received %zu, sent %zd\n", len, wr); - break; - default: - DLOG("packet: id=%u drop\n", id); - } - id++; - } - else - { - DLOG("unexpected zero size recvfrom\n"); - } - } - } - } - - res = 0; -exiterr: - if (Fpid) fclose(Fpid); - if (fd[0] != -1) close(fd[0]); - if (fd[1] != -1) close(fd[1]); - return res; -} - - -#elif defined (__CYGWIN__) - -static int win_main(const char *windivert_filter) -{ - size_t len; - unsigned int id; - uint8_t verdict; - bool bOutbound; - uint8_t packet[16384]; - uint32_t mark; - WINDIVERT_ADDRESS wa; - char ifname[IFNAMSIZ]; - - if (params.daemon) daemonize(); - - if (*params.pidfile && !writepid(params.pidfile)) - { - DLOG_ERR("could not write pidfile"); - return ERROR_TOO_MANY_OPEN_FILES; // code 4 = The system cannot open the file - } - - if (!win_dark_init(¶ms.ssid_filter, ¶ms.nlm_filter)) - { - DLOG_ERR("win_dark_init failed. win32 error %u (0x%08X)\n", w_win32_error, w_win32_error); - return w_win32_error; - } - - pre_desync(); - - for (;;) - { - if (!logical_net_filter_match()) - { - DLOG_CONDUP("logical network is not present. waiting it to appear.\n"); - do - { - if (bQuit) - { - DLOG("QUIT requested\n"); - win_dark_deinit(); - return 0; - } - usleep(500000); - } while (!logical_net_filter_match()); - DLOG_CONDUP("logical network now present\n"); - } - - if (!windivert_init(windivert_filter)) - { - win_dark_deinit(); - return w_win32_error; - } - - DLOG_CONDUP("windivert initialized. capture is started.\n"); - - for (id = 0;; id++) - { - len = sizeof(packet); - if (!windivert_recv(packet, &len, &wa)) - { - if (errno == ENOBUFS) - { - DLOG("windivert: ignoring too large packet\n"); - continue; // too large packet - } - else if (errno == ENODEV) - { - DLOG_CONDUP("logical network disappeared. deinitializing windivert.\n"); - rawsend_cleanup(); - break; - } - else if (errno == EINTR) - { - DLOG("QUIT requested\n"); - win_dark_deinit(); - return 0; - } - DLOG_ERR("windivert: recv failed. errno %d\n", errno); - win_dark_deinit(); - return w_win32_error; - } - - ReloadCheck(); - - *ifname = 0; - snprintf(ifname, sizeof(ifname), "%u.%u", wa.Network.IfIdx, wa.Network.SubIfIdx); - DLOG("\npacket: id=%u len=%zu %s IPv6=%u IPChecksum=%u TCPChecksum=%u UDPChecksum=%u IfIdx=%u.%u\n", id, len, wa.Outbound ? "outbound" : "inbound", wa.IPv6, wa.IPChecksum, wa.TCPChecksum, wa.UDPChecksum, wa.Network.IfIdx, wa.Network.SubIfIdx); - if (wa.Impostor) - { - DLOG("windivert: passing impostor packet\n"); - verdict = VERDICT_PASS; - } - else if (wa.Loopback) - { - DLOG("windivert: passing loopback packet\n"); - verdict = VERDICT_PASS; - } - else - { - mark = 0; - // pseudo interface id IfIdx.SubIfIdx - verdict = processPacketData(&mark, ifname, ifname, packet, &len); - } - switch (verdict & VERDICT_MASK) - { - case VERDICT_PASS: - case VERDICT_MODIFY: - if ((verdict & VERDICT_MASK) == VERDICT_PASS) - DLOG("packet: id=%u reinject unmodified\n", id); - else - DLOG("packet: id=%u reinject modified len=%zu\n", id, len); - if (!windivert_send(packet, len, &wa)) - DLOG_ERR("windivert: reinject of packet id=%u failed\n", id); - break; - default: - DLOG("packet: id=%u drop\n", id); - } - } - } - win_dark_deinit(); - return 0; -} - -#endif // multiple OS divert handlers - - - - -static void exit_clean(int code) -{ - cleanup_params(¶ms); - exit(code); -} - - -static bool parse_uid(const char *opt, uid_t *uid, gid_t *gid, int *gid_count, int max_gids) -{ - unsigned int u; - char c, *p, *e; - - *gid_count = 0; - if ((e = strchr(optarg, ':'))) *e++ = 0; - if (sscanf(opt, "%u", &u) != 1) return false; - *uid = (uid_t)u; - for (p = e; p; ) - { - if ((e = strchr(p, ','))) - { - c = *e; - *e = 0; - } - if (p) - { - if (sscanf(p, "%u", &u) != 1) return false; - if (*gid_count >= max_gids) return false; - gid[(*gid_count)++] = (gid_t)u; - } - if (e) *e++ = c; - p = e; - } - return true; -} - -static bool parse_ws_scale_factor(char *s, uint16_t *wsize, uint8_t *wscale) -{ - int v; - char *p; - - if ((p = strchr(s, ':'))) *p++ = 0; - v = atoi(s); - if (v < 0 || v>65535) - { - DLOG_ERR("bad wsize\n"); - return false; - } - *wsize = (uint16_t)v; - if (p && *p) - { - v = atoi(p); - if (v < 0 || v>255) - { - DLOG_ERR("bad wscale\n"); - return false; - } - *wscale = (uint8_t)v; - } - return true; -} - -static bool parse_cutoff(const char *opt, unsigned int *value, char *mode) -{ - *mode = (*opt == 'n' || *opt == 'd' || *opt == 's') ? *opt++ : 'n'; - return sscanf(opt, "%u", value) > 0; -} -static bool parse_net32_signed(const char *opt, uint32_t *value) -{ - if (((opt[0] == '0' && opt[1] == 'x') || (opt[0] == '-' && opt[1] == '0' && opt[2] == 'x')) && sscanf(opt + 2 + (opt[0] == '-'), "%X", (int32_t*)value) > 0) - { - if (opt[0] == '-') *value = -*value; - return true; - } - else - { - return sscanf(opt, "%d", (int32_t*)value) > 0; - } -} -static void load_file_or_exit(const char *filename, void *buf, size_t *size, size_t *offset) -{ - size_t ofs; - - // 0xaabbcc - // filename - // @filename - // +123@filename - - if (offset) *offset = 0; - if (filename[0] == '0' && filename[1] == 'x') - { - if (!parse_hex_str(filename + 2, buf, size) || !*size) - { - DLOG_ERR("invalid hex string: %s\n", filename + 2); - exit_clean(1); - } - DLOG("read %zu bytes from hex string\n", *size); - } - else - { - ofs = 0; - if (filename[0] == '+') - { - filename++; - if (sscanf(filename, "%zu", &ofs) != 1) - { - DLOG("offset read error: %s\n", filename); - exit_clean(1); - } - while (*filename && *filename != '@') filename++; - if (*filename == '@') filename++; - } - else if (filename[0] == '@') - filename++; - if (!load_file_nonempty(filename, buf, size)) - { - DLOG_ERR("could not read %s\n", filename); - exit_clean(1); - } - DLOG("read %zu bytes from '%s'. offset=%zu\n", *size, filename, ofs); - if (ofs >= *size) - { - DLOG("'%s' : offset %zu is out of data range %zu\n", filename, ofs, *size); - exit_clean(1); - } - if (offset) - *offset = ofs; - else - memmove(buf, (uint8_t*)buf + ofs, *size -= ofs); - } -} - -static bool parse_autottl(const char *s, autottl *t, int8_t def_delta, uint8_t def_min, uint8_t def_max) -{ - bool neg = true; - unsigned int delta, min, max; - - t->delta = def_delta; - t->min = def_min; - t->max = def_max; - if (s) - { - // "-" means disable - if (s[0] == '-' && s[1] == 0) - memset(t, 0, sizeof(*t)); - else - { - max = t->max; - if (*s == '+') - { - neg = false; - s++; - } - else if (*s == '-') - s++; - switch (sscanf(s, "%u:%u-%u", &delta, &min, &max)) - { - case 3: - if ((delta && !max) || max > 255) return false; - t->max = (uint8_t)max; - case 2: - if ((delta && !min) || min > 255 || min > max) return false; - t->min = (uint8_t)min; - case 1: - if (delta > 127) return false; - t->delta = (int8_t)(neg ? -delta : delta); - break; - default: - return false; - } - } - } - return true; -} - -static bool parse_l7_list(char *opt, uint32_t *l7) -{ - char *e, *p, c; - - for (p = opt, *l7 = 0; p; ) - { - if ((e = strchr(p, ','))) - { - c = *e; - *e = 0; - } - - if (!strcmp(p, "http")) - *l7 |= L7_PROTO_HTTP; - else if (!strcmp(p, "tls")) - *l7 |= L7_PROTO_TLS; - else if (!strcmp(p, "quic")) - *l7 |= L7_PROTO_QUIC; - else if (!strcmp(p, "wireguard")) - *l7 |= L7_PROTO_WIREGUARD; - else if (!strcmp(p, "dht")) - *l7 |= L7_PROTO_DHT; - else if (!strcmp(p, "discord")) - *l7 |= L7_PROTO_DISCORD; - else if (!strcmp(p, "stun")) - *l7 |= L7_PROTO_STUN; - else if (!strcmp(p, "unknown")) - *l7 |= L7_PROTO_UNKNOWN; - else return false; - - if (e) *e++ = c; - p = e; - } - return true; -} - -static bool parse_pf_list(char *opt, struct port_filters_head *pfl) -{ - char *e, *p, c; - port_filter pf; - bool b; - - for (p = opt; p; ) - { - if ((e = strchr(p, ','))) - { - c = *e; - *e = 0; - } - - b = pf_parse(p, &pf) && port_filter_add(pfl, &pf); - if (e) *e++ = c; - if (!b) return false; - - p = e; - } - return true; -} - -static bool wf_make_l3(char *opt, bool *ipv4, bool *ipv6) -{ - char *e, *p, c; - - for (p = opt, *ipv4 = *ipv6 = false; p; ) - { - if ((e = strchr(p, ','))) - { - c = *e; - *e = 0; - } - - if (!strcmp(p, "ipv4")) - *ipv4 = true; - else if (!strcmp(p, "ipv6")) - *ipv6 = true; - else return false; - - if (e) *e++ = c; - p = e; - } - return true; -} - -static bool parse_httpreqpos(const char *s, struct proto_pos *sp) -{ - if (!strcmp(s, "method")) - { - sp->marker = PM_HTTP_METHOD; - sp->pos = 2; - } - else if (!strcmp(s, "host")) - { - sp->marker = PM_HOST; - sp->pos = 1; - } - else - return false; - return true; -} -static bool parse_tlspos(const char *s, struct proto_pos *sp) -{ - if (!strcmp(s, "sni")) - { - sp->marker = PM_HOST; - sp->pos = 1; - } - else if (!strcmp(s, "sniext")) - { - sp->marker = PM_SNI_EXT; - sp->pos = 1; - } - else if (!strcmp(s, "snisld")) - { - sp->marker = PM_HOST_MIDSLD; - sp->pos = 0; - } - else - return false; - return true; -} - -static bool parse_int16(const char *p, int16_t *v) -{ - if (*p == '+' || *p == '-' || *p >= '0' && *p <= '9') - { - int i = atoi(p); - *v = (int16_t)i; - return *v == i; // check overflow - } - return false; -} -static bool parse_posmarker(const char *opt, uint8_t *posmarker) -{ - if (!strcmp(opt, "host")) - *posmarker = PM_HOST; - else if (!strcmp(opt, "endhost")) - *posmarker = PM_HOST_END; - else if (!strcmp(opt, "sld")) - *posmarker = PM_HOST_SLD; - else if (!strcmp(opt, "midsld")) - *posmarker = PM_HOST_MIDSLD; - else if (!strcmp(opt, "endsld")) - *posmarker = PM_HOST_ENDSLD; - else if (!strcmp(opt, "method")) - *posmarker = PM_HTTP_METHOD; - else if (!strcmp(opt, "sniext")) - *posmarker = PM_SNI_EXT; - else - return false; - return true; -} -static bool parse_split_pos(char *opt, struct proto_pos *split) -{ - if (parse_int16(opt, &split->pos)) - { - split->marker = PM_ABS; - return !!split->pos; - } - else - { - char c, *p = opt; - bool b; - - for (; *opt && *opt != '+' && *opt != '-'; opt++); - c = *opt; *opt = 0; - b = parse_posmarker(p, &split->marker); - *opt = c; - if (!b) return false; - if (*opt) - return parse_int16(opt, &split->pos); - else - split->pos = 0; - } - return true; -} -static bool parse_split_pos_list(char *opt, struct proto_pos *splits, int splits_size, int *split_count) -{ - char c, *e, *p; - - for (p = opt, *split_count = 0; p && *split_count < splits_size; (*split_count)++) - { - if ((e = strchr(p, ','))) - { - c = *e; - *e = 0; - } - if (!parse_split_pos(p, splits + *split_count)) return false; - if (e) *e++ = c; - p = e; - } - if (p) return false; // too much splits - return true; -} - -static bool parse_domain_list(char *opt, hostlist_pool **pp) -{ - char *e, *p, c; - - for (p = opt; p; ) - { - if ((e = strchr(p, ','))) - { - c = *e; - *e = 0; - } - - if (*p && !AppendHostlistItem(pp, p)) return false; - - if (e) *e++ = c; - p = e; - } - return true; -} - -static bool parse_ip_list(char *opt, ipset *pp) -{ - char *e, *p, c; - - for (p = opt; p; ) - { - if ((e = strchr(p, ','))) - { - c = *e; - *e = 0; - } - - if (*p && !AppendIpsetItem(pp, p)) return false; - - if (e) *e++ = c; - p = e; - } - return true; -} - -static bool parse_tlsmod_list(char *opt, struct fake_tls_mod *tls_mod) -{ - char *e, *e2, *p, c, c2; - - tls_mod->mod &= FAKE_TLS_MOD_SAVE_MASK; - tls_mod->mod |= FAKE_TLS_MOD_SET; - for (p = opt; p; ) - { - for (e2 = p; *e2 && *e2 != ',' && *e2 != '='; e2++); - - if ((e = strchr(e2, ','))) - { - c = *e; - *e = 0; - } - - if (*e2 == '=') - { - c2 = *e2; - *e2 = 0; - } - else - e2 = NULL; - - if (!strcmp(p, "rnd")) - tls_mod->mod |= FAKE_TLS_MOD_RND; - else if (!strcmp(p, "rndsni")) - tls_mod->mod |= FAKE_TLS_MOD_RND_SNI; - else if (!strcmp(p, "sni")) - { - tls_mod->mod |= FAKE_TLS_MOD_SNI; - if (!e2 || !e2[1] || e2[1] == ',') goto err; - strncpy(tls_mod->sni, e2 + 1, sizeof(tls_mod->sni) - 1); - tls_mod->sni[sizeof(tls_mod->sni) - 1 - 1] = 0; - } - else if (!strcmp(p, "padencap")) - tls_mod->mod |= FAKE_TLS_MOD_PADENCAP; - else if (!strcmp(p, "dupsid")) - tls_mod->mod |= FAKE_TLS_MOD_DUP_SID; - else if (strcmp(p, "none")) - goto err; - - if (e2) *e2 = c2; - if (e) *e++ = c; - p = e; - } - return true; -err: - if (e2) *e2 = c2; - if (e) *e++ = c; - return false; -} - -static bool parse_hostfakesplit_mod(char *opt, struct hostfakesplit_mod *hfs_mod) -{ - char *e, *e2, *p, c, c2; - - for (p = opt; p; ) - { - for (e2 = p; *e2 && *e2 != ',' && *e2 != '='; e2++); - - if ((e = strchr(e2, ','))) - { - c = *e; - *e = 0; - } - - if (*e2 == '=') - { - c2 = *e2; - *e2 = 0; - } - else - e2 = NULL; - - if (!strcmp(p, "host")) - { - if (!e2 || !e2[1] || e2[1] == ',') goto err; - strncpy(hfs_mod->host, e2 + 1, sizeof(hfs_mod->host) - 1); - hfs_mod->host[sizeof(hfs_mod->host) - 1 - 1] = 0; - hfs_mod->host_size = strlen(hfs_mod->host); // cache value - } - else if (!strcmp(p, "altorder")) - { - if (!e2 || !e2[1] || e2[1] == ',') goto err; - hfs_mod->ordering = atoi(e2 + 1); - if (hfs_mod->ordering < 0 || hfs_mod->ordering>1) goto err; - } - else if (strcmp(p, "none")) - goto err; - - if (e2) *e2 = c2; - if (e) *e++ = c; - p = e; - } - return true; -err: - if (e2) *e2 = c2; - if (e) *e++ = c; - return false; -} - -static bool parse_fakedsplit_mod(char *opt, struct fakedsplit_mod *fs_mod) -{ - char *e, *e2, *p, c, c2; - - for (p = opt; p; ) - { - for (e2 = p; *e2 && *e2 != ',' && *e2 != '='; e2++); - - if ((e = strchr(e2, ','))) - { - c = *e; - *e = 0; - } - - if (*e2 == '=') - { - c2 = *e2; - *e2 = 0; - } - else - e2 = NULL; - - if (!strcmp(p, "altorder")) - { - if (!e2 || !e2[1] || e2[1] == ',') goto err; - fs_mod->ordering = atoi(e2 + 1); - // unsplitted altorder in 0x03 mask. 0x04 reserved. splitted altorder in 0x18 mask - if ((fs_mod->ordering & 0xFFFFFFE4) || (((fs_mod->ordering>>3) & 3)>2)) goto err; - } - else if (strcmp(p, "none")) - goto err; - - if (e2) *e2 = c2; - if (e) *e++ = c; - p = e; - } - return true; -err: - if (e2) *e2 = c2; - if (e) *e++ = c; - return false; -} - -static bool parse_tcpmod(char *opt, struct tcp_mod *tcp_mod) -{ - char *e, *e2, *p, c, c2; - - for (p = opt; p; ) - { - for (e2 = p; *e2 && *e2 != ',' && *e2 != '='; e2++); - - if ((e = strchr(e2, ','))) - { - c = *e; - *e = 0; - } - - if (*e2 == '=') - { - c2 = *e2; - *e2 = 0; - } - else - e2 = NULL; - - if (!strcmp(p, "seq")) - { - tcp_mod->seq = true; - } - else if (strcmp(p, "none")) - goto err; - - if (e2) *e2 = c2; - if (e) *e++ = c; - p = e; - } - return true; -err: - if (e2) *e2 = c2; - if (e) *e++ = c; - return false; -} - -static bool parse_fooling(char *opt, unsigned int *fooling_mode) -{ - char *e, *p = opt; - while (p) - { - e = strchr(p, ','); - if (e) *e++ = 0; - if (!strcmp(p, "md5sig")) - *fooling_mode |= FOOL_MD5SIG; - else if (!strcmp(p, "ts")) - *fooling_mode |= FOOL_TS; - else if (!strcmp(p, "badsum")) - *fooling_mode |= FOOL_BADSUM; - else if (!strcmp(p, "badseq")) - *fooling_mode |= FOOL_BADSEQ; - else if (!strcmp(p, "datanoack")) - *fooling_mode |= FOOL_DATANOACK; - else if (!strcmp(p, "hopbyhop")) - *fooling_mode |= FOOL_HOPBYHOP; - else if (!strcmp(p, "hopbyhop2")) - *fooling_mode |= FOOL_HOPBYHOP2; - else if (strcmp(p, "none")) - return false; - p = e; - } - return true; -} - -static bool parse_strlist(char *opt, struct str_list_head *list) -{ - char *e, *p = optarg; - while (p) - { - e = strchr(p, ','); - if (e) *e++ = 0; - if (*p && !strlist_add(list, p)) - return false; - p = e; - } - return true; -} - -static bool parse_tcpflags(char *opt, uint16_t *fl) -{ - unsigned int u; - char *e, *p, c; - - if (sscanf(optarg, "0x%X", &u)<=0 && sscanf(optarg, "%u", &u)<=0) - { - *fl=0; - for (p = opt; p; ) - { - if ((e = strchr(p, ','))) - { - c = *e; - *e = 0; - } - - if (!strcasecmp(p, "FIN")) - *fl |= TH_FIN; - else if (!strcasecmp(p, "SYN")) - *fl |= TH_SYN; - else if (!strcasecmp(p, "RST")) - *fl |= TH_RST; - else if (!strcasecmp(p, "PSH") || !strcasecmp(p, "PUSH")) - *fl |= TH_PUSH; - else if (!strcasecmp(p, "ACK")) - *fl |= TH_ACK; - else if (!strcasecmp(p, "URG")) - *fl |= TH_URG; - else if (!strcasecmp(p, "ECE")) - *fl |= 0x40; - else if (!strcasecmp(p, "CWR")) - *fl |= 0x80; - else if (!strcasecmp(p, "AE") || !strcasecmp(p, "AECN") || !strcasecmp(p, "ACCECN")) - *fl |= 0x100; - else if (!strcasecmp(p, "R1")) - *fl |= 0x200; - else if (!strcasecmp(p, "R2")) - *fl |= 0x400; - else if (!strcasecmp(p, "R3")) - *fl |= 0x800; - else - return false; - if (e) *e++ = c; - p = e; - } - - return true; - } - else - { - *fl = u & 0xFFF; - return *fl==u; - } -} - - - -static void split_compat(struct desync_profile *dp) -{ - if (!dp->split_count) - { - dp->splits[dp->split_count].marker = PM_ABS; - dp->splits[dp->split_count].pos = 2; - dp->split_count++; - } - if ((dp->seqovl.marker != PM_ABS || dp->seqovl.pos < 0) && (dp->desync_mode == DESYNC_FAKEDSPLIT || dp->desync_mode == DESYNC_MULTISPLIT || dp->desync_mode2 == DESYNC_FAKEDSPLIT || dp->desync_mode2 == DESYNC_MULTISPLIT)) - { - DLOG_ERR("split seqovl supports only absolute positive positions\n"); - exit_clean(1); - } -} - -static void SplitDebug(void) -{ - struct desync_profile_list *dpl; - const struct desync_profile *dp; - LIST_FOREACH(dpl, ¶ms.desync_profiles, next) - { - dp = &dpl->dp; - for (int x = 0; x < dp->split_count; x++) - DLOG("profile %d multisplit %s %d\n", dp->n, posmarker_name(dp->splits[x].marker), dp->splits[x].pos); - if (!PROTO_POS_EMPTY(&dp->seqovl)) DLOG("profile %d seqovl %s %d\n", dp->n, posmarker_name(dp->seqovl.marker), dp->seqovl.pos); - if (!PROTO_POS_EMPTY(&dp->hostfakesplit_midhost)) DLOG("profile %d hostfakesplit midhost %s %d\n", dp->n, posmarker_name(dp->hostfakesplit_midhost.marker), dp->hostfakesplit_midhost.pos); - } -} - -static bool onetime_tls_mod_blob(int profile_n, int fake_n, const struct fake_tls_mod *tls_mod, uint8_t *fake_tls, size_t *fake_tls_size, size_t fake_tls_buf_size, struct fake_tls_mod_cache *modcache) -{ - const uint8_t *ext; - size_t extlen; - - modcache->extlen_offset = modcache->padlen_offset = 0; - if (tls_mod->mod & (FAKE_TLS_MOD_RND_SNI | FAKE_TLS_MOD_SNI | FAKE_TLS_MOD_PADENCAP)) - { - if (!TLSFindExtLen(fake_tls, *fake_tls_size, &modcache->extlen_offset)) - { - DLOG_ERR("profile %d fake[%d] padencap set but tls fake structure invalid\n", profile_n, fake_n); - return false; - } - DLOG("profile %d fake[%d] tls extensions length offset : %zu\n", profile_n, fake_n, modcache->extlen_offset); - if (tls_mod->mod & (FAKE_TLS_MOD_RND_SNI | FAKE_TLS_MOD_SNI)) - { - size_t slen; - if (!TLSFindExt(fake_tls, *fake_tls_size, 0, &ext, &extlen, false)) - { - DLOG_ERR("profile %d fake[%d] sni mod is set but tls fake does not have SNI\n", profile_n, fake_n); - return false; - } - uint8_t *sniext = fake_tls + (ext - fake_tls); - if (!TLSAdvanceToHostInSNI(&ext, &extlen, &slen)) - { - DLOG_ERR("profile %d fake[%d] sni set but tls fake has invalid SNI structure\n", profile_n, fake_n); - return false; - } - uint8_t *sni = fake_tls + (ext - fake_tls); - if (tls_mod->mod & FAKE_TLS_MOD_SNI) - { - size_t slen_new = strlen(tls_mod->sni); - ssize_t slen_delta = slen_new - slen; - char *s1 = NULL; - if (params.debug) - { - if ((s1 = malloc(slen + 1))) - { - memcpy(s1, sni, slen); s1[slen] = 0; - } - } - if (slen_delta) - { - if ((*fake_tls_size + slen_delta) > fake_tls_buf_size) - { - DLOG_ERR("profile %d fake[%d] not enough space for new SNI\n", profile_n, fake_n); - free(s1); - return false; - } - memmove(sni + slen_new, sni + slen, fake_tls + *fake_tls_size - (sni + slen)); - phton16(fake_tls + 3, (uint16_t)(pntoh16(fake_tls + 3) + slen_delta)); - phton24(fake_tls + 6, (uint32_t)(pntoh24(fake_tls + 6) + slen_delta)); - phton16(fake_tls + modcache->extlen_offset, (uint16_t)(pntoh16(fake_tls + modcache->extlen_offset) + slen_delta)); - phton16(sniext - 2, (uint16_t)(pntoh16(sniext - 2) + slen_delta)); - phton16(sniext, (uint16_t)(pntoh16(sniext) + slen_delta)); - phton16(sni - 2, (uint16_t)(pntoh16(sni - 2) + slen_delta)); - *fake_tls_size += slen_delta; - slen = slen_new; - } - DLOG("profile %d fake[%d] change SNI : %s => %s size_delta=%zd\n", profile_n, fake_n, s1, tls_mod->sni, slen_delta); - free(s1); - - memcpy(sni, tls_mod->sni, slen_new); - } - if (tls_mod->mod & FAKE_TLS_MOD_RND_SNI) - { - if (!slen) - { - DLOG_ERR("profile %d fake[%d] rndsni set but tls fake has zero sized SNI\n", profile_n, fake_n); - return false; - } - - char *s1 = NULL, *s2 = NULL; - if (params.debug) - { - if ((s1 = malloc(slen + 1))) - { - memcpy(s1, sni, slen); s1[slen] = 0; - } - } - - fill_random_az(sni, 1); - if (slen >= 7) // domain name in SNI must be at least 3 chars long to enable xxx.tls randomization - { - fill_random_az09(sni + 1, slen - 5); - sni[slen - 4] = '.'; - memcpy(sni + slen - 3, tld[random() % (sizeof(tld) / sizeof(*tld))], 3); - } - else - fill_random_az09(sni + 1, slen - 1); - - if (params.debug) - { - if (s1 && (s2 = malloc(slen + 1))) - { - memcpy(s2, sni, slen); s2[slen] = 0; - DLOG("profile %d fake[%d] generated random SNI : %s -> %s\n", profile_n, fake_n, s1, s2); - } - free(s1); free(s2); - } - } - } - if (tls_mod->mod & FAKE_TLS_MOD_PADENCAP) - { - if (TLSFindExt(fake_tls, *fake_tls_size, 21, &ext, &extlen, false)) - { - if ((ext - fake_tls + extlen) != *fake_tls_size) - { - DLOG_ERR("profile %d fake[%d] tls padding ext is present but it's not at the end. padding ext offset %zu, padding ext size %zu, fake size %zu\n", profile_n, fake_n, ext - fake_tls, extlen, *fake_tls_size); - return false; - } - modcache->padlen_offset = ext - fake_tls - 2; - DLOG("profile %d fake[%d] tls padding ext is present, padding length offset %zu\n", profile_n, fake_n, modcache->padlen_offset); - } - else - { - if ((*fake_tls_size + 4) > fake_tls_buf_size) - { - DLOG_ERR("profile %d fake[%d] tls padding is absent and there's no space to add it\n", profile_n, fake_n); - return false; - } - phton16(fake_tls + *fake_tls_size, 21); - *fake_tls_size += 2; - modcache->padlen_offset = *fake_tls_size; - phton16(fake_tls + *fake_tls_size, 0); - *fake_tls_size += 2; - phton16(fake_tls + modcache->extlen_offset, pntoh16(fake_tls + modcache->extlen_offset) + 4); - phton16(fake_tls + 3, pntoh16(fake_tls + 3) + 4); // increase tls record len - phton24(fake_tls + 6, pntoh24(fake_tls + 6) + 4); // increase tls handshake len - DLOG("profile %d fake[%d] tls padding is absent. added. padding length offset %zu\n", profile_n, fake_n, modcache->padlen_offset); - } - } - } - return true; -} -static bool onetime_tls_mod(struct desync_profile *dp) -{ - struct blob_item *fake_tls; - struct fake_tls_mod *tls_mod; - int n = 0; - - LIST_FOREACH(fake_tls, &dp->fake_tls, next) - { - ++n; - tls_mod = (struct fake_tls_mod *)fake_tls->extra2; - if (!tls_mod) continue; - if (dp->n && !(tls_mod->mod & (FAKE_TLS_MOD_SET | FAKE_TLS_MOD_CUSTOM_FAKE))) - tls_mod->mod |= FAKE_TLS_MOD_RND | FAKE_TLS_MOD_RND_SNI | FAKE_TLS_MOD_DUP_SID; // old behavior compat + dup_sid - if (!(tls_mod->mod & ~FAKE_TLS_MOD_SAVE_MASK)) - continue; - - if (!IsTLSClientHello(fake_tls->data, fake_tls->size, false) || (fake_tls->size < (44 + fake_tls->data[43]))) // has session id ? - { - DLOG("profile %d fake[%d] tls mod set but tls fake structure invalid.\n", dp->n, n); - return false; - } - if (!fake_tls->extra) - { - fake_tls->extra = malloc(sizeof(struct fake_tls_mod_cache)); - if (!fake_tls->extra) return false; - } - if (!onetime_tls_mod_blob(dp->n, n, tls_mod, fake_tls->data, &fake_tls->size, fake_tls->size_buf, (struct fake_tls_mod_cache*)fake_tls->extra)) - return false; - if (fake_tls->offset >= fake_tls->size) - { - DLOG("profile %d fake[%d] tls mod shrinked data to %zu and offset %zu is now out of data range\n", dp->n, n, fake_tls->size, fake_tls->offset); - return false; - } - } - return true; -} - -static struct blob_item *load_blob_to_collection(const char *filename, struct blob_collection_head *blobs, size_t max_size, size_t size_reserve) -{ - struct blob_item *blob = blob_collection_add(blobs); - uint8_t *p; - - if (!blob || (!(blob->data = malloc(max_size + size_reserve)))) - { - DLOG_ERR("out of memory\n"); - exit_clean(1); - } - blob->size = max_size; - load_file_or_exit(filename, blob->data, &blob->size, &blob->offset); - p = realloc(blob->data, blob->size + size_reserve); - if (!p) - { - DLOG_ERR("out of memory\n"); - exit_clean(1); - } - blob->data = p; - blob->size_buf = blob->size + size_reserve; - return blob; -} -static struct blob_item *load_const_blob_to_collection(const void *data, size_t sz, struct blob_collection_head *blobs, size_t size_reserve, size_t offset) -{ - if (offset >= sz) - { - DLOG_ERR("offset %zu is out of data range %zu\n", offset, sz); - exit_clean(1); - } - struct blob_item *blob = blob_collection_add(blobs); - if (!blob || (!(blob->data = malloc(sz + size_reserve)))) - { - DLOG_ERR("out of memory\n"); - exit_clean(1); - } - blob->size = sz; - blob->size_buf = sz + size_reserve; - blob->offset = offset; - memcpy(blob->data, data, sz); - return blob; -} - - -#ifdef __CYGWIN__ -static bool wf_make_pf(char *opt, const char *l4, const char *portname, char *buf, size_t len) -{ - char *e, *p, c, s1[64]; - port_filter pf; - int n; - - if (len < 3) return false; - - for (n = 0, p = opt, *buf = '(', buf[1] = 0; p; n++) - { - if ((e = strchr(p, ','))) - { - c = *e; - *e = 0; - } - if (!pf_parse(p, &pf)) return false; - - if (pf.from == pf.to) - snprintf(s1, sizeof(s1), "(%s.%s %s %u)", l4, portname, pf.neg ? "!=" : "==", pf.from); - else - snprintf(s1, sizeof(s1), "(%s.%s %s %u %s %s.%s %s %u)", l4, portname, pf.neg ? "<" : ">=", pf.from, pf.neg ? "or" : "and", l4, portname, pf.neg ? ">" : "<=", pf.to); - if (n) strncat(buf, " or ", len - strlen(buf) - 1); - strncat(buf, s1, len - strlen(buf) - 1); - - if (e) *e++ = c; - p = e; - } - strncat(buf, ")", len - strlen(buf) - 1); - return true; -} - -#define DIVERT_NO_LOCALNETSv4_DST "(" \ - "(ip.DstAddr < 127.0.0.1 or ip.DstAddr > 127.255.255.255) and " \ - "(ip.DstAddr < 10.0.0.0 or ip.DstAddr > 10.255.255.255) and " \ - "(ip.DstAddr < 192.168.0.0 or ip.DstAddr > 192.168.255.255) and " \ - "(ip.DstAddr < 172.16.0.0 or ip.DstAddr > 172.31.255.255) and " \ - "(ip.DstAddr < 169.254.0.0 or ip.DstAddr > 169.254.255.255))" -#define DIVERT_NO_LOCALNETSv4_SRC "(" \ - "(ip.SrcAddr < 127.0.0.1 or ip.SrcAddr > 127.255.255.255) and " \ - "(ip.SrcAddr < 10.0.0.0 or ip.SrcAddr > 10.255.255.255) and " \ - "(ip.SrcAddr < 192.168.0.0 or ip.SrcAddr > 192.168.255.255) and " \ - "(ip.SrcAddr < 172.16.0.0 or ip.SrcAddr > 172.31.255.255) and " \ - "(ip.SrcAddr < 169.254.0.0 or ip.SrcAddr > 169.254.255.255))" - -#define DIVERT_NO_LOCALNETSv6_DST "(" \ - "(ipv6.DstAddr > ::1) and " \ - "(ipv6.DstAddr < 2001::0 or ipv6.DstAddr >= 2001:1::0) and " \ - "(ipv6.DstAddr < fc00::0 or ipv6.DstAddr >= fe00::0) and " \ - "(ipv6.DstAddr < fe80::0 or ipv6.DstAddr >= fec0::0) and " \ - "(ipv6.DstAddr < ff00::0 or ipv6.DstAddr >= ffff::0))" -#define DIVERT_NO_LOCALNETSv6_SRC "(" \ - "(ipv6.SrcAddr > ::1) and " \ - "(ipv6.SrcAddr < 2001::0 or ipv6.SrcAddr >= 2001:1::0) and " \ - "(ipv6.SrcAddr < fc00::0 or ipv6.SrcAddr >= fe00::0) and " \ - "(ipv6.SrcAddr < fe80::0 or ipv6.SrcAddr >= fec0::0) and " \ - "(ipv6.SrcAddr < ff00::0 or ipv6.SrcAddr >= ffff::0))" - -#define DIVERT_NO_LOCALNETS_SRC "(" DIVERT_NO_LOCALNETSv4_SRC " or " DIVERT_NO_LOCALNETSv6_SRC ")" -#define DIVERT_NO_LOCALNETS_DST "(" DIVERT_NO_LOCALNETSv4_DST " or " DIVERT_NO_LOCALNETSv6_DST ")" - -#define DIVERT_TCP_NOT_EMPTY "(!tcp or tcp.Syn or tcp.Rst or tcp.Fin or tcp.PayloadLength>0)" -#define DIVERT_TCP_INBOUNDS "(tcp.Ack and tcp.Syn or tcp.Rst or tcp.Fin)" - -// HTTP/1.? 30(2|7) -#define DIVERT_HTTP_REDIRECT "tcp.PayloadLength>=12 and tcp.Payload32[0]==0x48545450 and tcp.Payload16[2]==0x2F31 and tcp.Payload[6]==0x2E and tcp.Payload16[4]==0x2033 and tcp.Payload[10]==0x30 and (tcp.Payload[11]==0x32 or tcp.Payload[11]==0x37)" - -#define DIVERT_PROLOG "!impostor and !loopback" - -static bool wf_make_filter( - char *wf, size_t len, - unsigned int IfIdx, unsigned int SubIfIdx, - bool ipv4, bool ipv6, - const char *pf_tcp_src, const char *pf_tcp_dst, - const char *pf_udp_src, const char *pf_udp_dst, - const struct str_list_head *wf_raw_part, - bool bFilterOutLAN) -{ - char pf_dst_buf[8192], iface[64]; - struct str_list *wfpart; - int n; - const char *pf_dst; - const char *f_tcpin = *pf_tcp_src ? dp_list_have_autohostlist(¶ms.desync_profiles) ? "(" DIVERT_TCP_INBOUNDS " or (" DIVERT_HTTP_REDIRECT "))" : DIVERT_TCP_INBOUNDS : ""; - const char *f_tcp_not_empty = (*pf_tcp_src && !dp_list_need_all_out(¶ms.desync_profiles)) ? DIVERT_TCP_NOT_EMPTY " and " : ""; - snprintf(iface, sizeof(iface), " ifIdx=%u and subIfIdx=%u and", IfIdx, SubIfIdx); - - snprintf(wf, len, "%s and%s%s\n(", - DIVERT_PROLOG, - IfIdx ? iface : "", - ipv4 ? ipv6 ? "" : " ip and" : " ipv6 and" - ); - - n = 0; - if (!LIST_EMPTY(wf_raw_part)) - { - LIST_FOREACH(wfpart, wf_raw_part, next) - { - snprintf(wf + strlen(wf), len - strlen(wf), "%s(\n%s\n )", n ? "\n or\n " : "\n ", wfpart->str); - n++; - } - } - - if (*pf_tcp_src || *pf_udp_src) - { - if (*pf_tcp_src && *pf_udp_src) - { - snprintf(pf_dst_buf, sizeof(pf_dst_buf), "(%s or %s)", pf_tcp_dst, pf_udp_dst); - pf_dst = pf_dst_buf; - } - else - pf_dst = *pf_tcp_dst ? pf_tcp_dst : pf_udp_dst; - - snprintf(wf + strlen(wf), len - strlen(wf), n++ ? "\n or\n " : "\n "); - - snprintf(wf + strlen(wf), len - strlen(wf), - "(\n (outbound and %s%s)\n or\n (inbound and tcp%s%s%s%s%s)\n )", - f_tcp_not_empty, - pf_dst, - *pf_tcp_src ? "" : " and false", - *f_tcpin ? " and " : "", - *f_tcpin ? f_tcpin : "", - *pf_tcp_src ? " and " : "", - *pf_tcp_src ? pf_tcp_src : ""); - - } - strncat(wf, "\n)", len - strlen(wf) - 1); - - if (bFilterOutLAN) - snprintf(wf + strlen(wf), len - strlen(wf), "\nand\n(\n outbound and %s\n or\n inbound and %s\n)\n", - ipv4 ? ipv6 ? DIVERT_NO_LOCALNETS_DST : DIVERT_NO_LOCALNETSv4_DST : DIVERT_NO_LOCALNETSv6_DST, - ipv4 ? ipv6 ? DIVERT_NO_LOCALNETS_SRC : DIVERT_NO_LOCALNETSv4_SRC : DIVERT_NO_LOCALNETSv6_SRC); - - return true; -} - -static unsigned int hash_jen(const void *data, unsigned int len) -{ - unsigned int hash; - HASH_JEN(data, len, hash); - return hash; -} - -#endif - - -static void exithelp(void) -{ - printf( -#if !defined( __OpenBSD__) && !defined(__ANDROID__) - " @|$\t\t\t\t; read file for options. must be the only argument. other options are ignored.\n\n" -#endif -#ifdef __ANDROID__ - " --debug=0|1|syslog|android|@\n" -#else - " --debug=0|1|syslog|@\n" -#endif - " --version\t\t\t\t\t\t; print version and exit\n" - " --dry-run\t\t\t\t\t\t; verify parameters and exit with code 0 if successful\n" - " --comment=any_text\n" -#ifdef __linux__ - " --qnum=\n" -#elif defined(BSD) - " --port=\t\t\t\t\t\t; divert port\n" -#endif - " --daemon\t\t\t\t\t\t; daemonize\n" - " --pidfile=\t\t\t\t\t; write pid to file\n" -#ifndef __CYGWIN__ - " --user=\t\t\t\t\t; drop root privs\n" - " --uid=uid[:gid1,gid2,...]\t\t\t\t; drop root privs\n" -#endif -#ifdef __linux__ - " --bind-fix4\t\t\t\t\t\t; apply outgoing interface selection fix for generated ipv4 packets\n" - " --bind-fix6\t\t\t\t\t\t; apply outgoing interface selection fix for generated ipv6 packets\n" -#endif - " --ctrack-timeouts=S:E:F[:U]\t\t\t\t; internal conntrack timeouts for TCP SYN, ESTABLISHED, FIN stages, UDP timeout. default %u:%u:%u:%u\n" - " --ctrack-disable=[0|1]\t\t\t\t\t; 1 or no argument disables conntrack\n" - " --ipcache-lifetime=\t\t\t\t; time in seconds to keep cached hop count and domain name (default %u). 0 = no expiration\n" - " --ipcache-hostname=[0|1]\t\t\t\t; 1 or no argument enables ip->hostname caching\n" -#ifdef __CYGWIN__ - "\nWINDIVERT FILTER:\n" - " --wf-iface=[.]\t\t\t\t; numeric network interface and subinterface indexes\n" - " --wf-l3=ipv4|ipv6\t\t\t\t\t; L3 protocol filter. multiple comma separated values allowed.\n" - " --wf-tcp=[~]port1[-port2]\t\t\t\t; TCP port filter. ~ means negation. multiple comma separated values allowed.\n" - " --wf-udp=[~]port1[-port2]\t\t\t\t; UDP port filter. ~ means negation. multiple comma separated values allowed.\n" - " --wf-raw-part=|@\t\t\t; partial raw windivert filter string or filename\n" - " --wf-filter-lan=0|1\t\t\t\t\t; add excluding filter for non-global IP (default : 1)\n" - " --wf-raw=|@\t\t\t\t; full raw windivert filter string or filename. replaces --wf-tcp,--wf-udp,--wf-raw-part\n" - " --wf-save=\t\t\t\t\t; save windivert filter string to a file and exit\n" - "\nLOGICAL NETWORK FILTER:\n" - " --ssid-filter=ssid1[,ssid2,ssid3,...]\t\t\t; enable winws only if any of specified wifi SSIDs connected\n" - " --nlm-filter=net1[,net2,net3,...]\t\t\t; enable winws only if any of specified NLM network is connected. names and GUIDs are accepted.\n" - " --nlm-list[=all]\t\t\t\t\t; list Network List Manager (NLM) networks. connected only or all.\n" -#endif - "\nMULTI-STRATEGY:\n" - " --new\t\t\t\t\t\t\t; begin new strategy\n" - " --skip\t\t\t\t\t\t\t; do not use this strategy\n" - " --filter-l3=ipv4|ipv6\t\t\t\t\t; L3 protocol filter. multiple comma separated values allowed.\n" - " --filter-tcp=[~]port1[-port2]|*\t\t\t; TCP port filter. ~ means negation. setting tcp and not setting udp filter denies udp. comma separated list allowed.\n" - " --filter-udp=[~]port1[-port2]|*\t\t\t; UDP port filter. ~ means negation. setting udp and not setting tcp filter denies tcp. comma separated list allowed.\n" - " --filter-l7=[http|tls|quic|wireguard|dht|discord|stun|unknown] ; L6-L7 protocol filter. multiple comma separated values allowed.\n" -#ifdef HAS_FILTER_SSID - " --filter-ssid=ssid1[,ssid2,ssid3,...]\t\t\t; per profile wifi SSID filter\n" -#endif - " --ipset=\t\t\t\t\t; ipset include filter (one ip/CIDR per line, ipv4 and ipv6 accepted, gzip supported, multiple ipsets allowed)\n" - " --ipset-ip=\t\t\t\t\t; comma separated fixed subnet list\n" - " --ipset-exclude=\t\t\t\t; ipset exclude filter (one ip/CIDR per line, ipv4 and ipv6 accepted, gzip supported, multiple ipsets allowed)\n" - " --ipset-exclude-ip=\t\t\t\t; comma separated fixed subnet list\n" - "\nHOSTLIST FILTER:\n" - " --hostlist=\t\t\t\t\t; apply dpi desync only to the listed hosts (one host per line, subdomains auto apply, gzip supported, multiple hostlists allowed)\n" - " --hostlist-domains=\t\t\t; comma separated fixed domain list\n" - " --hostlist-exclude=\t\t\t\t; do not apply dpi desync to the listed hosts (one host per line, subdomains auto apply, gzip supported, multiple hostlists allowed)\n" - " --hostlist-exclude-domains=\t\t; comma separated fixed domain list\n" - " --hostlist-auto=\t\t\t\t; detect DPI blocks and build hostlist automatically\n" - " --hostlist-auto-fail-threshold=\t\t\t; how many failed attempts cause hostname to be added to auto hostlist (default : %d)\n" - " --hostlist-auto-fail-time=\t\t\t; all failed attemps must be within these seconds (default : %d)\n" - " --hostlist-auto-retrans-threshold=\t\t; how many request retransmissions cause attempt to fail (default : %d)\n" - " --hostlist-auto-debug=\t\t\t; debug auto hostlist positives\n" - "\nTAMPER:\n" - " --wsize=[:]\t\t\t; set window size. 0 = do not modify. OBSOLETE !\n" - " --wssize=[:]\t\t; set window size for server. 0 = do not modify. default scale_factor = 0.\n" - " --wssize-cutoff=[n|d|s]N\t\t\t\t; apply server wsize only to packet numbers (n, default), data packet numbers (d), relative sequence (s) less than N\n" - " --wssize-forced-cutoff=0|1\t\t\t\t; 1(default)=auto cutoff wssize on known protocol\n" - " --synack-split=[syn|synack|acksyn]\t\t\t; perform TCP split handshake : send SYN only, SYN+ACK or ACK+SYN\n" - " --orig-ttl=\t\t\t\t\t; set TTL for original packets\n" - " --orig-ttl6=\t\t\t\t\t; set ipv6 hop limit for original packets. by default ttl value is used\n" - " --orig-autottl=[[:[-]]|-]\t\t; auto ttl mode for both ipv4 and ipv6. default: +%d:%u-%u\n" - " --orig-autottl6=[[:[-]]|-]\t\t; overrides --orig-autottl for ipv6 only\n" - " --orig-tcp-flags-set=\t\t; set these tcp flags (flags |= value). value can be int, hex or comma separated list : FIN,SYN,RST,PSH,ACK,URG,ECE,CWR,AE,R1,R2,R3\n" - " --orig-tcp-flags-unset=\t\t; unset these tcp flags (flags &= ~value)\n" - " --orig-mod-start=[n|d|s]N\t\t\t\t; apply orig TTL mod to packet numbers (n, default), data packet numbers (d), relative sequence (s) greater or equal than N\n" - " --orig-mod-cutoff=[n|d|s]N\t\t\t\t; apply orig TTL mod to packet numbers (n, default), data packet numbers (d), relative sequence (s) less than N\n" - " --dup=\t\t\t\t\t\t; duplicate original packets. send N dups before original.\n" - " --dup-replace=[0|1]\t\t\t\t\t; 1 or no argument means do not send original, only dups\n" - " --dup-ttl=\t\t\t\t\t; set TTL for dups\n" - " --dup-ttl6=\t\t\t\t\t; set ipv6 hop limit for dups. by default ttl value is used\n" - " --dup-autottl=[[:[-]]|-]\t\t; auto ttl mode for both ipv4 and ipv6. default: %d:%u-%u\n" - " --dup-autottl6=[[:[-]]|-]\t\t; overrides --dup-autottl for ipv6 only\n" - " --dup-tcp-flags-set=\t\t; set these tcp flags (flags |= value). value can be int, hex or comma separated list : FIN,SYN,RST,PSH,ACK,URG,ECE,CWR,AE,R1,R2,R3\n" - " --dup-tcp-flags-unset=\t\t; unset these tcp flags (flags &= ~value)\n" - " --dup-fooling=[,]\t\t\t\t; can use multiple comma separated values. modes : none md5sig badseq badsum datanoack ts hopbyhop hopbyhop2\n" - " --dup-ts-increment=\t\t\t\t; ts fooling TSval signed increment for dup. default %d\n" - " --dup-badseq-increment=\t\t\t; badseq fooling seq signed increment for dup. default %d\n" - " --dup-badack-increment=\t\t\t; badseq fooling ackseq signed increment for dup. default %d\n" - " --dup-ip-id=same|zero|seq|rnd\t\t\t\t; ipv4 ip_id mode for dupped packets\n" - " --dup-start=[n|d|s]N\t\t\t\t\t; apply dup to packet numbers (n, default), data packet numbers (d), relative sequence (s) greater or equal than N\n" - " --dup-cutoff=[n|d|s]N\t\t\t\t\t; apply dup to packet numbers (n, default), data packet numbers (d), relative sequence (s) less than N\n" - " --hostcase\t\t\t\t\t\t; change Host: => host:\n" - " --hostspell\t\t\t\t\t\t; exact spelling of \"Host\" header. must be 4 chars. default is \"host\"\n" - " --hostnospace\t\t\t\t\t\t; remove space after Host: and add it to User-Agent: to preserve packet size\n" - " --domcase\t\t\t\t\t\t; mix domain case : Host: TeSt.cOm\n" - " --methodeol\t\t\t\t\t\t; add '\\n' before method and remove space from Host:\n" - " --ip-id=zero|seq|seqgroup|rnd\t\t\t\t; ipv4 ip_id assignment scheme\n" - " --dpi-desync=[,][,]\t\t; try to desync dpi state. modes :\n" - "\t\t\t\t\t\t\t; synack syndata fake fakeknown rst rstack hopbyhop destopt ipfrag1\n" - "\t\t\t\t\t\t\t; multisplit multidisorder fakedsplit fakeddisorder hostfakesplit ipfrag2 udplen tamper\n" -#ifdef __linux__ - " --dpi-desync-fwmark=\t\t\t; override fwmark for desync packet. default = 0x%08X (%u)\n" -#elif defined(SO_USER_COOKIE) - " --dpi-desync-sockarg=\t\t\t; override sockarg (SO_USER_COOKIE) for desync packet. default = 0x%08X (%u)\n" -#endif - " --dpi-desync-ttl=\t\t\t\t\t; set ttl for fakes packets\n" - " --dpi-desync-ttl6=\t\t\t\t; set ipv6 hop limit for fake packet. by default --dpi-desync-ttl value is used.\n" - " --dpi-desync-autottl=[[:[-]]|-]\t; auto ttl mode for both ipv4 and ipv6. default: %d:%u-%u\n" - " --dpi-desync-autottl6=[[:[-]]|-]\t; overrides --dpi-desync-autottl for ipv6 only\n" - " --dpi-desync-tcp-flags-set=\t; set these tcp flags (flags |= value). value can be int, hex or comma separated list : FIN,SYN,RST,PSH,ACK,URG,ECE,CWR,AE,R1,R2,R3\n" - " --dpi-desync-tcp-flags-unset=\t; unset these tcp flags (flags &= ~value)\n" - " --dpi-desync-fooling=[,]\t\t\t; can use multiple comma separated values. modes : none md5sig badseq badsum datanoack ts hopbyhop hopbyhop2\n" - " --dpi-desync-repeats=\t\t\t\t; send every desync packet N times\n" - " --dpi-desync-skip-nosni=0|1\t\t\t\t; 1(default)=do not act on ClientHello without SNI\n" - " --dpi-desync-split-pos=N|-N|marker+N|marker-N\t\t; comma separated list of split positions\n" - "\t\t\t\t\t\t\t; markers: method,host,endhost,sld,endsld,midsld,sniext\n" - "\t\t\t\t\t\t\t; full list is only used by multisplit and multidisorder\n" - "\t\t\t\t\t\t\t; fakedsplit/fakeddisorder use first l7-protocol-compatible parameter if present, first abs value otherwise\n" - " --dpi-desync-split-seqovl=N|-N|marker+N|marker-N\t; use sequence overlap before first sent original split segment\n" - " --dpi-desync-split-seqovl-pattern=[+ofs]@|0xHEX ; pattern for the fake part of overlap\n" - " --dpi-desync-fakedsplit-pattern=[+ofs]@|0xHEX ; fake pattern for fakedsplit/fakeddisorder\n" - " --dpi-desync-fakedsplit-mod=mod[,mod]\t\t\t; mods can be none,altorder=0|1|2|3 + 0|8|16\n" - " --dpi-desync-hostfakesplit-midhost=marker+N|marker-N\t; additionally split real hostname at specified marker. must be within host..endhost or won't be splitted.\n" - " --dpi-desync-hostfakesplit-mod=mod[,mod]\t\t; mods can be none,host=,altorder=0|1\n" - " --dpi-desync-ipfrag-pos-tcp=<8..%u>\t\t\t; ip frag position starting from the transport header. multiple of 8, default %u.\n" - " --dpi-desync-ipfrag-pos-udp=<8..%u>\t\t\t; ip frag position starting from the transport header. multiple of 8, default %u.\n" - " --dpi-desync-ts-increment=\t\t\t; ts fooling TSval signed increment. default %d\n" - " --dpi-desync-badseq-increment=\t\t; badseq fooling seq signed increment. default %d\n" - " --dpi-desync-badack-increment=\t\t; badseq fooling ackseq signed increment. default %d\n" - " --dpi-desync-any-protocol=0|1\t\t\t\t; 0(default)=desync only http and tls 1=desync any nonempty data packet\n" - " --dpi-desync-fake-tcp-mod=mod[,mod]\t\t\t; comma separated list of tcp fake mods. available mods : none,seq\n" - " --dpi-desync-fake-http=[+ofs]@|0xHEX\t\t; fake http request\n" - " --dpi-desync-fake-tls=[+ofs]@|0xHEX|![+offset] ; fake TLS ClientHello (for https)\n" - " --dpi-desync-fake-tls-mod=mod[,mod]\t\t\t; comma separated list of TLS fake mods. available mods : none,rnd,rndsni,sni=,dupsid,padencap\n" - " --dpi-desync-fake-unknown=[+ofs]@|0xHEX\t; unknown protocol fake payload\n" - " --dpi-desync-fake-syndata=[+ofs]@|0xHEX\t; SYN data payload\n" - " --dpi-desync-fake-quic=[+ofs]@|0xHEX\t\t; fake QUIC Initial\n" - " --dpi-desync-fake-wireguard=[+ofs]@|0xHEX\t; fake wireguard handshake initiation\n" - " --dpi-desync-fake-dht=[+ofs]@|0xHEX\t\t; DHT protocol fake payload (d1...e)\n" - " --dpi-desync-fake-discord=[+ofs]@|0xHEX\t; discord protocol fake payload (Voice IP Discovery)\n" - " --dpi-desync-fake-stun=[+ofs]@|0xHEX\t\t; STUN protocol fake payload\n" - " --dpi-desync-fake-unknown-udp=[+ofs]@|0xHEX\t; unknown udp protocol fake payload\n" - " --dpi-desync-udplen-increment=\t\t\t; increase or decrease udp packet length by N bytes (default %u). negative values decrease length.\n" - " --dpi-desync-udplen-pattern=[+ofs]@|0xHEX\t; udp tail fill pattern\n" - " --dpi-desync-start=[n|d|s]N\t\t\t\t; apply dpi desync only to packet numbers (n, default), data packet numbers (d), relative sequence (s) greater or equal than N\n" - " --dpi-desync-cutoff=[n|d|s]N\t\t\t\t; apply dpi desync only to packet numbers (n, default), data packet numbers (d), relative sequence (s) less than N\n", - CTRACK_T_SYN, CTRACK_T_EST, CTRACK_T_FIN, CTRACK_T_UDP, - IPCACHE_LIFETIME, - HOSTLIST_AUTO_FAIL_THRESHOLD_DEFAULT, HOSTLIST_AUTO_FAIL_TIME_DEFAULT, HOSTLIST_AUTO_RETRANS_THRESHOLD_DEFAULT, - AUTOTTL_DEFAULT_ORIG_DELTA, AUTOTTL_DEFAULT_ORIG_MIN, AUTOTTL_DEFAULT_ORIG_MAX, - AUTOTTL_DEFAULT_DUP_DELTA, AUTOTTL_DEFAULT_DUP_MIN, AUTOTTL_DEFAULT_DUP_MAX, - TS_INCREMENT_DEFAULT, BADSEQ_INCREMENT_DEFAULT, BADSEQ_ACK_INCREMENT_DEFAULT, -#if defined(__linux__) || defined(SO_USER_COOKIE) - DPI_DESYNC_FWMARK_DEFAULT, DPI_DESYNC_FWMARK_DEFAULT, -#endif - AUTOTTL_DEFAULT_DESYNC_DELTA, AUTOTTL_DEFAULT_DESYNC_MIN, AUTOTTL_DEFAULT_DESYNC_MAX, - DPI_DESYNC_MAX_FAKE_LEN, IPFRAG_UDP_DEFAULT, - DPI_DESYNC_MAX_FAKE_LEN, IPFRAG_TCP_DEFAULT, - TS_INCREMENT_DEFAULT, BADSEQ_INCREMENT_DEFAULT, BADSEQ_ACK_INCREMENT_DEFAULT, - UDPLEN_INCREMENT_DEFAULT - ); - exit(1); -} -static void exithelp_clean(void) -{ - cleanup_params(¶ms); - exithelp(); -} - -#if !defined( __OpenBSD__) && !defined(__ANDROID__) -// no static to not allow optimizer to inline this func (save stack) -void config_from_file(const char *filename) -{ - // config from a file - char buf[MAX_CONFIG_FILE_SIZE]; - buf[0] = 'x'; // fake argv[0] - buf[1] = ' '; - size_t bufsize = sizeof(buf) - 3; - if (!load_file(filename, buf + 2, &bufsize)) - { - DLOG_ERR("could not load config file '%s'\n", filename); - exit_clean(1); - } - buf[bufsize + 2] = 0; - // wordexp fails if it sees \t \n \r between args - replace_char(buf, '\n', ' '); - replace_char(buf, '\r', ' '); - replace_char(buf, '\t', ' '); - if (wordexp(buf, ¶ms.wexp, WRDE_NOCMD)) - { - DLOG_ERR("failed to split command line options from file '%s'\n", filename); - exit_clean(1); - } -} -#endif - -void check_dp(const struct desync_profile *dp) -{ - // only linux has connbytes limiter - if ((dp->desync_any_proto && !dp->desync_cutoff && - (dp->desync_mode == DESYNC_FAKE || dp->desync_mode == DESYNC_RST || dp->desync_mode == DESYNC_RSTACK || - dp->desync_mode == DESYNC_FAKEDSPLIT || dp->desync_mode == DESYNC_FAKEDDISORDER || dp->desync_mode == DESYNC_HOSTFAKESPLIT || - dp->desync_mode2 == DESYNC_FAKEDSPLIT || dp->desync_mode2 == DESYNC_FAKEDDISORDER || dp->desync_mode2 == DESYNC_HOSTFAKESPLIT)) - || - dp->dup_repeats && !dp->dup_cutoff) - { -#ifdef __linux__ - DLOG_CONDUP("WARNING !!! in profile %d you are using --dpi-desync-any-protocol without --dpi-desync-cutoff or --dup without --dup-cutoff\n", dp->n); - DLOG_CONDUP("WARNING !!! it's completely ok if connbytes or payload based ip/nf tables limiter is applied. Make sure it exists.\n"); -#else - DLOG_CONDUP("WARNING !!! possible TRASH FLOOD configuration detected in profile %d\n", dp->n); - DLOG_CONDUP("WARNING !!! in profile %d you are using --dpi-desync-any-protocol without --dpi-desync-cutoff or --dup without --dup-cutoff\n", dp->n); - DLOG_CONDUP("WARNING !!! fakes or dups will be sent on every processed packet\n"); - DLOG_CONDUP("WARNING !!! make sure it's really what you want\n"); -#ifdef __CYGWIN__ - DLOG_CONDUP("WARNING !!! in most cases this is acceptable only with custom payload based windivert filter (--wf-raw, --wf-raw-part)\n"); -#endif -#endif - } -} - -#define STRINGIFY(x) #x -#define TOSTRING(x) STRINGIFY(x) -#if defined(ZAPRET_GH_VER) || defined (ZAPRET_GH_HASH) -#ifdef __ANDROID__ -#define PRINT_VER printf("github android version %s (%s)\n\n", TOSTRING(ZAPRET_GH_VER), TOSTRING(ZAPRET_GH_HASH)) -#else -#define PRINT_VER printf("github version %s (%s)\n\n", TOSTRING(ZAPRET_GH_VER), TOSTRING(ZAPRET_GH_HASH)) -#endif -#else -#ifdef __ANDROID__ -#define PRINT_VER printf("self-built android version %s %s\n\n", __DATE__, __TIME__) -#else -#define PRINT_VER printf("self-built version %s %s\n\n", __DATE__, __TIME__) -#endif -#endif - -enum opt_indices { - IDX_DEBUG, - IDX_DRY_RUN, - IDX_VERSION, - IDX_COMMENT, -#ifdef __linux__ - IDX_QNUM, -#elif defined(BSD) - IDX_PORT, -#endif - IDX_DAEMON, - IDX_PIDFILE, -#ifndef __CYGWIN__ - IDX_USER, - IDX_UID, -#endif - IDX_WSIZE, - IDX_WSSIZE, - IDX_WSSIZE_CUTOFF, - IDX_WSSIZE_FORCED_CUTOFF, - IDX_SYNACK_SPLIT, - IDX_CTRACK_TIMEOUTS, - IDX_CTRACK_DISABLE, - IDX_IPCACHE_LIFETIME, - IDX_IPCACHE_HOSTNAME, - IDX_HOSTCASE, - IDX_HOSTSPELL, - IDX_HOSTNOSPACE, - IDX_DOMCASE, - IDX_METHODEOL, - IDX_IP_ID, - IDX_DPI_DESYNC, -#ifdef __linux__ - IDX_DPI_DESYNC_FWMARK, -#elif defined(SO_USER_COOKIE) - IDX_DPI_DESYNC_SOCKARG, -#endif - IDX_DUP, - IDX_DUP_TTL, - IDX_DUP_TTL6, - IDX_DUP_AUTOTTL, - IDX_DUP_AUTOTTL6, - IDX_DUP_TCP_FLAGS_SET, - IDX_DUP_TCP_FLAGS_UNSET, - IDX_DUP_FOOLING, - IDX_DUP_TS_INCREMENT, - IDX_DUP_BADSEQ_INCREMENT, - IDX_DUP_BADACK_INCREMENT, - IDX_DUP_REPLACE, - IDX_DUP_IP_ID, - IDX_DUP_START, - IDX_DUP_CUTOFF, - IDX_ORIG_TTL, - IDX_ORIG_TTL6, - IDX_ORIG_AUTOTTL, - IDX_ORIG_AUTOTTL6, - IDX_ORIG_TCP_FLAGS_SET, - IDX_ORIG_TCP_FLAGS_UNSET, - IDX_ORIG_MOD_START, - IDX_ORIG_MOD_CUTOFF, - IDX_DPI_DESYNC_TTL, - IDX_DPI_DESYNC_TTL6, - IDX_DPI_DESYNC_AUTOTTL, - IDX_DPI_DESYNC_AUTOTTL6, - IDX_DPI_DESYNC_TCP_FLAGS_SET, - IDX_DPI_DESYNC_TCP_FLAGS_UNSET, - IDX_DPI_DESYNC_FOOLING, - IDX_DPI_DESYNC_REPEATS, - IDX_DPI_DESYNC_SKIP_NOSNI, - IDX_DPI_DESYNC_SPLIT_POS, - IDX_DPI_DESYNC_SPLIT_HTTP_REQ, - IDX_DPI_DESYNC_SPLIT_TLS, - IDX_DPI_DESYNC_SPLIT_SEQOVL, - IDX_DPI_DESYNC_SPLIT_SEQOVL_PATTERN, - IDX_DPI_DESYNC_FAKEDSPLIT_PATTERN, - IDX_DPI_DESYNC_FAKEDSPLIT_MOD, - IDX_DPI_DESYNC_HOSTFAKESPLIT_MIDHOST, - IDX_DPI_DESYNC_HOSTFAKESPLIT_MOD, - IDX_DPI_DESYNC_IPFRAG_POS_TCP, - IDX_DPI_DESYNC_IPFRAG_POS_UDP, - IDX_DPI_DESYNC_TS_INCREMENT, - IDX_DPI_DESYNC_BADSEQ_INCREMENT, - IDX_DPI_DESYNC_BADACK_INCREMENT, - IDX_DPI_DESYNC_ANY_PROTOCOL, - IDX_DPI_DESYNC_FAKE_TCP_MOD, - IDX_DPI_DESYNC_FAKE_HTTP, - IDX_DPI_DESYNC_FAKE_TLS, - IDX_DPI_DESYNC_FAKE_TLS_MOD, - IDX_DPI_DESYNC_FAKE_UNKNOWN, - IDX_DPI_DESYNC_FAKE_SYNDATA, - IDX_DPI_DESYNC_FAKE_QUIC, - IDX_DPI_DESYNC_FAKE_WIREGUARD, - IDX_DPI_DESYNC_FAKE_DHT, - IDX_DPI_DESYNC_FAKE_DISCORD, - IDX_DPI_DESYNC_FAKE_STUN, - IDX_DPI_DESYNC_FAKE_UNKNOWN_UDP, - IDX_DPI_DESYNC_UDPLEN_INCREMENT, - IDX_DPI_DESYNC_UDPLEN_PATTERN, - IDX_DPI_DESYNC_CUTOFF, - IDX_DPI_DESYNC_START, - IDX_HOSTLIST, - IDX_HOSTLIST_DOMAINS, - IDX_HOSTLIST_EXCLUDE, - IDX_HOSTLIST_EXCLUDE_DOMAINS, - IDX_HOSTLIST_AUTO, - IDX_HOSTLIST_AUTO_FAIL_THRESHOLD, - IDX_HOSTLIST_AUTO_FAIL_TIME, - IDX_HOSTLIST_AUTO_RETRANS_THRESHOLD, - IDX_HOSTLIST_AUTO_DEBUG, - IDX_NEW, - IDX_SKIP, - IDX_FILTER_L3, - IDX_FILTER_TCP, - IDX_FILTER_UDP, - IDX_FILTER_L7, -#ifdef HAS_FILTER_SSID - IDX_FILTER_SSID, -#endif - IDX_IPSET, - IDX_IPSET_IP, - IDX_IPSET_EXCLUDE, - IDX_IPSET_EXCLUDE_IP, -#ifdef __linux__ - IDX_BIND_FIX4, - IDX_BIND_FIX6, -#elif defined(__CYGWIN__) - IDX_WF_IFACE, - IDX_WF_L3, - IDX_WF_TCP, - IDX_WF_UDP, - IDX_WF_RAW, - IDX_WF_RAW_PART, - IDX_WF_FILTER_LAN, - IDX_WF_SAVE, - IDX_SSID_FILTER, - IDX_NLM_FILTER, - IDX_NLM_LIST, -#endif - IDX_LAST, -}; - -static const struct option long_options[] = { - [IDX_DEBUG] = {"debug", optional_argument, 0, 0}, - [IDX_DRY_RUN] = {"dry-run", no_argument, 0, 0}, - [IDX_VERSION] = {"version", no_argument, 0, 0}, - [IDX_COMMENT] = {"comment", optional_argument, 0, 0}, -#ifdef __linux__ - [IDX_QNUM] = {"qnum", required_argument, 0, 0}, -#elif defined(BSD) - [IDX_PORT] = {"port", required_argument, 0, 0}, -#endif - [IDX_DAEMON] = {"daemon", no_argument, 0, 0}, - [IDX_PIDFILE] = {"pidfile", required_argument, 0, 0}, -#ifndef __CYGWIN__ - [IDX_USER] = {"user", required_argument, 0, 0}, - [IDX_UID] = {"uid", required_argument, 0, 0}, -#endif - [IDX_WSIZE] = {"wsize", required_argument, 0, 0}, - [IDX_WSSIZE] = {"wssize", required_argument, 0, 0}, - [IDX_WSSIZE_CUTOFF] = {"wssize-cutoff", required_argument, 0, 0}, - [IDX_WSSIZE_FORCED_CUTOFF] = {"wssize-forced-cutoff", required_argument, 0, 0}, - [IDX_SYNACK_SPLIT] = {"synack-split", optional_argument, 0, 0}, - [IDX_CTRACK_TIMEOUTS] = {"ctrack-timeouts", required_argument, 0, 0}, - [IDX_CTRACK_DISABLE] = {"ctrack-disable", optional_argument, 0, 0}, - [IDX_IPCACHE_LIFETIME] = {"ipcache-lifetime", required_argument, 0, 0}, - [IDX_IPCACHE_HOSTNAME] = {"ipcache-hostname", optional_argument, 0, 0}, - [IDX_HOSTCASE] = {"hostcase", no_argument, 0, 0}, - [IDX_HOSTSPELL] = {"hostspell", required_argument, 0, 0}, - [IDX_HOSTNOSPACE] = {"hostnospace", no_argument, 0, 0}, - [IDX_DOMCASE] = {"domcase", no_argument, 0, 0}, - [IDX_METHODEOL] = {"methodeol", no_argument, 0, 0}, - [IDX_IP_ID] = {"ip-id", required_argument, 0, 0}, - [IDX_DPI_DESYNC] = {"dpi-desync", required_argument, 0, 0}, -#ifdef __linux__ - [IDX_DPI_DESYNC_FWMARK] = {"dpi-desync-fwmark", required_argument, 0, 0}, -#elif defined(SO_USER_COOKIE) - [IDX_DPI_DESYNC_SOCKARG] = {"dpi-desync-sockarg", required_argument, 0, 0}, -#endif - [IDX_DUP] = {"dup", required_argument, 0, 0}, - [IDX_DUP_TTL] = {"dup-ttl", required_argument, 0, 0}, - [IDX_DUP_TTL6] = {"dup-ttl6", required_argument, 0, 0}, - [IDX_DUP_AUTOTTL] = {"dup-autottl", optional_argument, 0, 0}, - [IDX_DUP_AUTOTTL6] = {"dup-autottl6", optional_argument, 0, 0}, - [IDX_DUP_TCP_FLAGS_SET] = {"dup-tcp-flags-set", optional_argument, 0, 0}, - [IDX_DUP_TCP_FLAGS_UNSET] = {"dup-tcp-flags-unset", optional_argument, 0, 0}, - [IDX_DUP_FOOLING] = {"dup-fooling", required_argument, 0, 0}, - [IDX_DUP_TS_INCREMENT] = {"dup-ts-increment", required_argument, 0, 0}, - [IDX_DUP_BADSEQ_INCREMENT] = {"dup-badseq-increment", required_argument, 0, 0}, - [IDX_DUP_BADACK_INCREMENT] = {"dup-badack-increment", required_argument, 0, 0}, - [IDX_DUP_REPLACE] = {"dup-replace", optional_argument, 0, 0}, - [IDX_DUP_IP_ID] = {"dup-ip-id", required_argument, 0, 0}, - [IDX_DUP_START] = {"dup-start", required_argument, 0, 0}, - [IDX_DUP_CUTOFF] = {"dup-cutoff", required_argument, 0, 0}, - [IDX_ORIG_TTL] = {"orig-ttl", required_argument, 0, 0}, - [IDX_ORIG_TTL6] = {"orig-ttl6", required_argument, 0, 0}, - [IDX_ORIG_AUTOTTL] = {"orig-autottl", optional_argument, 0, 0}, - [IDX_ORIG_AUTOTTL6] = {"orig-autottl6", optional_argument, 0, 0}, - [IDX_ORIG_TCP_FLAGS_SET] = {"orig-tcp-flags-set", optional_argument, 0, 0}, - [IDX_ORIG_TCP_FLAGS_UNSET] = {"orig-tcp-flags-unset", optional_argument, 0, 0}, - [IDX_ORIG_MOD_START] = {"orig-mod-start", required_argument, 0, 0}, - [IDX_ORIG_MOD_CUTOFF] = {"orig-mod-cutoff", required_argument, 0, 0}, - [IDX_DPI_DESYNC_TTL] = {"dpi-desync-ttl", required_argument, 0, 0}, - [IDX_DPI_DESYNC_TTL6] = {"dpi-desync-ttl6", required_argument, 0, 0}, - [IDX_DPI_DESYNC_AUTOTTL] = {"dpi-desync-autottl", optional_argument, 0, 0}, - [IDX_DPI_DESYNC_AUTOTTL6] = {"dpi-desync-autottl6", optional_argument, 0, 0}, - [IDX_DPI_DESYNC_TCP_FLAGS_SET] = {"dpi-desync-tcp-flags-set", optional_argument, 0, 0}, - [IDX_DPI_DESYNC_TCP_FLAGS_UNSET] = {"dpi-desync-tcp-flags-unset", optional_argument, 0, 0}, - [IDX_DPI_DESYNC_FOOLING] = {"dpi-desync-fooling", required_argument, 0, 0}, - [IDX_DPI_DESYNC_REPEATS] = {"dpi-desync-repeats", required_argument, 0, 0}, - [IDX_DPI_DESYNC_SKIP_NOSNI] = {"dpi-desync-skip-nosni", optional_argument, 0, 0}, - [IDX_DPI_DESYNC_SPLIT_POS] = {"dpi-desync-split-pos", required_argument, 0, 0}, - [IDX_DPI_DESYNC_SPLIT_HTTP_REQ] = {"dpi-desync-split-http-req", required_argument, 0, 0}, - [IDX_DPI_DESYNC_SPLIT_TLS] = {"dpi-desync-split-tls", required_argument, 0, 0}, - [IDX_DPI_DESYNC_SPLIT_SEQOVL] = {"dpi-desync-split-seqovl", required_argument, 0, 0}, - [IDX_DPI_DESYNC_SPLIT_SEQOVL_PATTERN] = {"dpi-desync-split-seqovl-pattern", required_argument, 0, 0}, - [IDX_DPI_DESYNC_FAKEDSPLIT_PATTERN] = {"dpi-desync-fakedsplit-pattern", required_argument, 0, 0}, - [IDX_DPI_DESYNC_FAKEDSPLIT_MOD] = {"dpi-desync-fakedsplit-mod", required_argument, 0, 0}, - [IDX_DPI_DESYNC_HOSTFAKESPLIT_MIDHOST] = {"dpi-desync-hostfakesplit-midhost", required_argument, 0, 0}, - [IDX_DPI_DESYNC_HOSTFAKESPLIT_MOD] = {"dpi-desync-hostfakesplit-mod", required_argument, 0, 0}, - [IDX_DPI_DESYNC_IPFRAG_POS_TCP] = {"dpi-desync-ipfrag-pos-tcp", required_argument, 0, 0}, - [IDX_DPI_DESYNC_IPFRAG_POS_UDP] = {"dpi-desync-ipfrag-pos-udp", required_argument, 0, 0}, - [IDX_DPI_DESYNC_TS_INCREMENT] = {"dpi-desync-ts-increment", required_argument, 0, 0}, - [IDX_DPI_DESYNC_BADSEQ_INCREMENT] = {"dpi-desync-badseq-increment", required_argument, 0, 0}, - [IDX_DPI_DESYNC_BADACK_INCREMENT] = {"dpi-desync-badack-increment", required_argument, 0, 0}, - [IDX_DPI_DESYNC_ANY_PROTOCOL] = {"dpi-desync-any-protocol", optional_argument, 0, 0}, - [IDX_DPI_DESYNC_FAKE_TCP_MOD] = {"dpi-desync-fake-tcp-mod", required_argument, 0, 0}, - [IDX_DPI_DESYNC_FAKE_HTTP] = {"dpi-desync-fake-http", required_argument, 0, 0}, - [IDX_DPI_DESYNC_FAKE_TLS] = {"dpi-desync-fake-tls", required_argument, 0, 0}, - [IDX_DPI_DESYNC_FAKE_TLS_MOD] = {"dpi-desync-fake-tls-mod", required_argument, 0, 0}, - [IDX_DPI_DESYNC_FAKE_UNKNOWN] = {"dpi-desync-fake-unknown", required_argument, 0, 0}, - [IDX_DPI_DESYNC_FAKE_SYNDATA] = {"dpi-desync-fake-syndata", required_argument, 0, 0}, - [IDX_DPI_DESYNC_FAKE_QUIC] = {"dpi-desync-fake-quic", required_argument, 0, 0}, - [IDX_DPI_DESYNC_FAKE_WIREGUARD] = {"dpi-desync-fake-wireguard", required_argument, 0, 0}, - [IDX_DPI_DESYNC_FAKE_DHT] = {"dpi-desync-fake-dht", required_argument, 0, 0}, - [IDX_DPI_DESYNC_FAKE_DISCORD] = {"dpi-desync-fake-discord", required_argument, 0, 0}, - [IDX_DPI_DESYNC_FAKE_STUN] = {"dpi-desync-fake-stun", required_argument, 0, 0}, - [IDX_DPI_DESYNC_FAKE_UNKNOWN_UDP] = {"dpi-desync-fake-unknown-udp", required_argument, 0, 0}, - [IDX_DPI_DESYNC_UDPLEN_INCREMENT] = {"dpi-desync-udplen-increment", required_argument, 0, 0}, - [IDX_DPI_DESYNC_UDPLEN_PATTERN] = {"dpi-desync-udplen-pattern", required_argument, 0, 0}, - [IDX_DPI_DESYNC_CUTOFF] = {"dpi-desync-cutoff", required_argument, 0, 0}, - [IDX_DPI_DESYNC_START] = {"dpi-desync-start", required_argument, 0, 0}, - [IDX_HOSTLIST] = {"hostlist", required_argument, 0, 0}, - [IDX_HOSTLIST_DOMAINS] = {"hostlist-domains", required_argument, 0, 0}, - [IDX_HOSTLIST_EXCLUDE] = {"hostlist-exclude", required_argument, 0, 0}, - [IDX_HOSTLIST_EXCLUDE_DOMAINS] = {"hostlist-exclude-domains", required_argument, 0, 0}, - [IDX_HOSTLIST_AUTO] = {"hostlist-auto", required_argument, 0, 0}, - [IDX_HOSTLIST_AUTO_FAIL_THRESHOLD] = {"hostlist-auto-fail-threshold", required_argument, 0, 0}, - [IDX_HOSTLIST_AUTO_FAIL_TIME] = {"hostlist-auto-fail-time", required_argument, 0, 0}, - [IDX_HOSTLIST_AUTO_RETRANS_THRESHOLD] = {"hostlist-auto-retrans-threshold", required_argument, 0, 0}, - [IDX_HOSTLIST_AUTO_DEBUG] = {"hostlist-auto-debug", required_argument, 0, 0}, - [IDX_NEW] = {"new", no_argument, 0, 0}, - [IDX_SKIP] = {"skip", no_argument, 0, 0}, - [IDX_FILTER_L3] = {"filter-l3", required_argument, 0, 0}, - [IDX_FILTER_TCP] = {"filter-tcp", required_argument, 0, 0}, - [IDX_FILTER_UDP] = {"filter-udp", required_argument, 0, 0}, - [IDX_FILTER_L7] = {"filter-l7", required_argument, 0, 0}, -#ifdef HAS_FILTER_SSID - [IDX_FILTER_SSID] = {"filter-ssid", required_argument, 0, 0}, -#endif - [IDX_IPSET] = {"ipset", required_argument, 0, 0}, - [IDX_IPSET_IP] = {"ipset-ip", required_argument, 0, 0}, - [IDX_IPSET_EXCLUDE] = {"ipset-exclude", required_argument, 0, 0}, - [IDX_IPSET_EXCLUDE_IP] = {"ipset-exclude-ip", required_argument, 0, 0}, -#ifdef __linux__ - [IDX_BIND_FIX4] = {"bind-fix4", no_argument, 0, 0}, - [IDX_BIND_FIX6] = {"bind-fix6", no_argument, 0, 0}, -#elif defined(__CYGWIN__) - [IDX_WF_IFACE] = {"wf-iface", required_argument, 0, 0}, - [IDX_WF_L3] = {"wf-l3", required_argument, 0, 0}, - [IDX_WF_TCP] = {"wf-tcp", required_argument, 0, 0}, - [IDX_WF_UDP] = {"wf-udp", required_argument, 0, 0}, - [IDX_WF_RAW] = {"wf-raw", required_argument, 0, 0}, - [IDX_WF_RAW_PART] = {"wf-raw-part", required_argument, 0, 0}, - [IDX_WF_FILTER_LAN] = {"wf-filter-lan", required_argument, 0, 0}, - [IDX_WF_SAVE] = {"wf-save", required_argument, 0, 0}, - [IDX_SSID_FILTER] = {"ssid-filter", required_argument, 0, 0}, - [IDX_NLM_FILTER] = {"nlm-filter", required_argument, 0, 0}, - [IDX_NLM_LIST] = {"nlm-list", optional_argument, 0, 0}, -#endif - [IDX_LAST] = {NULL, 0, NULL, 0}, -}; - -int main(int argc, char **argv) -{ - set_console_io_buffering(); - set_env_exedir(argv[0]); - -#ifdef __CYGWIN__ - if (service_run(argc, argv)) - { - // we were running as service. now exit. - return 0; - } -#endif - int result, v; - int option_index = 0; - bool bSkip = false, bDry = false; - struct hostlist_file *anon_hl = NULL, *anon_hl_exclude = NULL; - struct ipset_file *anon_ips = NULL, *anon_ips_exclude = NULL; -#ifdef __CYGWIN__ - char windivert_filter[16384], wf_pf_tcp_src[4096], wf_pf_tcp_dst[4096], wf_pf_udp_src[4096], wf_pf_udp_dst[4096], wf_save_file[256]; - bool wf_ipv4 = true, wf_ipv6 = true, wf_filter_lan = true; - unsigned int IfIdx = 0, SubIfIdx = 0; - unsigned int hash_wf_tcp = 0, hash_wf_udp = 0, hash_wf_raw = 0, hash_wf_raw_part = 0, hash_ssid_filter = 0, hash_nlm_filter = 0; - *windivert_filter = *wf_pf_tcp_src = *wf_pf_tcp_dst = *wf_pf_udp_src = *wf_pf_udp_dst = *wf_save_file = 0; -#endif - - srandom(time(NULL)); - - PRINT_VER; - - memset(¶ms, 0, sizeof(params)); - - struct desync_profile_list *dpl; - struct desync_profile *dp; - int desync_profile_count = 0; - - if (!(dpl = dp_list_add(¶ms.desync_profiles))) - { - DLOG_ERR("desync_profile_add: out of memory\n"); - exit_clean(1); - } - dp = &dpl->dp; - dp->n = ++desync_profile_count; - -#ifdef __linux__ - params.qnum = -1; -#elif defined(BSD) - params.port = 0; -#endif - params.desync_fwmark = DPI_DESYNC_FWMARK_DEFAULT; - params.ctrack_t_syn = CTRACK_T_SYN; - params.ctrack_t_est = CTRACK_T_EST; - params.ctrack_t_fin = CTRACK_T_FIN; - params.ctrack_t_udp = CTRACK_T_UDP; - params.ipcache_lifetime = IPCACHE_LIFETIME; - - LIST_INIT(¶ms.hostlists); - LIST_INIT(¶ms.ipsets); - -#ifdef __CYGWIN__ - LIST_INIT(¶ms.ssid_filter); - LIST_INIT(¶ms.nlm_filter); - LIST_INIT(¶ms.wf_raw_part); -#else - if (can_drop_root()) - { - params.uid = params.gid[0] = 0x7FFFFFFF; // default uid:gid - params.gid_count = 1; - params.droproot = true; - } -#endif - -#if !defined( __OpenBSD__) && !defined(__ANDROID__) - if (argc >= 2 && (argv[1][0] == '@' || argv[1][0] == '$')) - { - config_from_file(argv[1] + 1); - argv = params.wexp.we_wordv; - argc = params.wexp.we_wordc; - } -#endif - - if (argc < 2) exithelp_clean(); - while ((v = getopt_long_only(argc, argv, "", long_options, &option_index)) != -1) - { - if (v) - { - if (bDry) - exit_clean(1); - else - exithelp_clean(); - } - switch (option_index) - { - case IDX_DEBUG: - if (optarg) - { - if (*optarg == '@') - { - strncpy(params.debug_logfile, optarg + 1, sizeof(params.debug_logfile)); - params.debug_logfile[sizeof(params.debug_logfile) - 1] = 0; - FILE *F = fopen(params.debug_logfile, "wt"); - if (!F) - { - fprintf(stderr, "cannot create %s\n", params.debug_logfile); - exit_clean(1); - } - fclose(F); - params.debug = true; - params.debug_target = LOG_TARGET_FILE; - } - else if (!strcmp(optarg, "syslog")) - { - params.debug = true; - params.debug_target = LOG_TARGET_SYSLOG; - openlog(progname, LOG_PID, LOG_USER); - } -#ifdef __ANDROID__ - else if (!strcmp(optarg, "android")) - { - if (!params.debug) params.debug = 1; - params.debug_target = LOG_TARGET_ANDROID; - } -#endif - else if (optarg[0] >= '0' && optarg[0] <= '1') - { - params.debug = atoi(optarg); - params.debug_target = LOG_TARGET_CONSOLE; - } - else - { - fprintf(stderr, "invalid debug mode : %s\n", optarg); - exit_clean(1); - } - } - else - { - params.debug = true; - params.debug_target = LOG_TARGET_CONSOLE; - } - break; - case IDX_DRY_RUN: - bDry = true; - break; - case IDX_VERSION: - exit_clean(0); - break; - case IDX_COMMENT: - break; -#ifdef __linux__ - case IDX_QNUM: - params.qnum = atoi(optarg); - if (params.qnum < 0 || params.qnum>65535) - { - DLOG_ERR("bad qnum\n"); - exit_clean(1); - } - break; -#elif defined(BSD) - case IDX_PORT: - { - int i = atoi(optarg); - if (i <= 0 || i > 65535) - { - DLOG_ERR("bad port number\n"); - exit_clean(1); - } - params.port = (uint16_t)i; - } - break; -#endif - case IDX_DAEMON: - params.daemon = true; - break; - case IDX_PIDFILE: - snprintf(params.pidfile, sizeof(params.pidfile), "%s", optarg); - break; -#ifndef __CYGWIN__ - case IDX_USER: - { - free(params.user); params.user = NULL; - struct passwd *pwd = getpwnam(optarg); - if (!pwd) - { - DLOG_ERR("non-existent username supplied\n"); - exit_clean(1); - } - params.uid = pwd->pw_uid; - params.gid[0] = pwd->pw_gid; - params.gid_count = 1; - if (!(params.user = strdup(optarg))) - { - DLOG_ERR("strdup: out of memory\n"); - exit_clean(1); - } - params.droproot = true; - break; - } - case IDX_UID: - free(params.user); params.user = NULL; - if (!parse_uid(optarg, ¶ms.uid, params.gid, ¶ms.gid_count, MAX_GIDS)) - { - DLOG_ERR("--uid should be : uid[:gid,gid,...]\n"); - exit_clean(1); - } - if (!params.gid_count) - { - params.gid[0] = 0x7FFFFFFF; - params.gid_count = 1; - } - params.droproot = true; - break; -#endif - case IDX_WSIZE: - if (!parse_ws_scale_factor(optarg, &dp->wsize, &dp->wscale)) - exit_clean(1); - break; - case IDX_WSSIZE: - if (!parse_ws_scale_factor(optarg, &dp->wssize, &dp->wsscale)) - exit_clean(1); - break; - case IDX_WSSIZE_CUTOFF: - if (!parse_cutoff(optarg, &dp->wssize_cutoff, &dp->wssize_cutoff_mode)) - { - DLOG_ERR("invalid wssize-cutoff value\n"); - exit_clean(1); - } - break; - case IDX_WSSIZE_FORCED_CUTOFF: - dp->wssize_no_forced_cutoff = !atoi(optarg); - break; - case IDX_SYNACK_SPLIT: - dp->synack_split = SS_SYN; - if (optarg) - { - if (!strcmp(optarg, "synack")) - dp->synack_split = SS_SYNACK; - else if (!strcmp(optarg, "acksyn")) - dp->synack_split = SS_ACKSYN; - else if (strcmp(optarg, "syn")) - { - DLOG_ERR("invalid synack-split value\n"); - exit_clean(1); - } - } - break; - case IDX_CTRACK_TIMEOUTS: - if (sscanf(optarg, "%u:%u:%u:%u", ¶ms.ctrack_t_syn, ¶ms.ctrack_t_est, ¶ms.ctrack_t_fin, ¶ms.ctrack_t_udp) < 3) - { - DLOG_ERR("invalid ctrack-timeouts value\n"); - exit_clean(1); - } - break; - case IDX_CTRACK_DISABLE: - params.ctrack_disable = !optarg || atoi(optarg); - break; - case IDX_IPCACHE_LIFETIME: - if (sscanf(optarg, "%u", ¶ms.ipcache_lifetime) != 1) - { - DLOG_ERR("invalid ipcache-lifetime value\n"); - exit_clean(1); - } - break; - case IDX_IPCACHE_HOSTNAME: - params.cache_hostname = !optarg || atoi(optarg); - break; - case IDX_HOSTCASE: - dp->hostcase = true; - break; - case IDX_HOSTSPELL: - if (strlen(optarg) != 4) - { - DLOG_ERR("hostspell must be exactly 4 chars long\n"); - exit_clean(1); - } - dp->hostcase = true; - memcpy(dp->hostspell, optarg, 4); - break; - case IDX_HOSTNOSPACE: - if (dp->methodeol) - { - DLOG_ERR("--hostnospace and --methodeol are incompatible\n"); - exit_clean(1); - } - dp->hostnospace = true; - break; - case IDX_DOMCASE: - dp->domcase = true; - break; - case IDX_METHODEOL: - if (dp->hostnospace) - { - DLOG_ERR("--hostnospace and --methodeol are incompatible\n"); - exit_clean(1); - } - dp->methodeol = true; - break; - case IDX_IP_ID: - if (!strcmp(optarg,"zero")) - dp->ip_id_mode = IPID_ZERO; - else if (!strcmp(optarg,"seq")) - dp->ip_id_mode = IPID_SEQ; - else if (!strcmp(optarg,"seqgroup")) - dp->ip_id_mode = IPID_SEQ_GROUP; - else if (!strcmp(optarg,"rnd")) - dp->ip_id_mode = IPID_RND; - else - { - DLOG_ERR("invalid ip_id mode : %s\n",optarg); - exit_clean(1); - } - break; - case IDX_DPI_DESYNC: - { - char *mode = optarg, *mode2, *mode3; - mode2 = mode ? strchr(mode, ',') : NULL; - if (mode2) *mode2++ = 0; - mode3 = mode2 ? strchr(mode2, ',') : NULL; - if (mode3) *mode3++ = 0; - - dp->desync_mode0 = desync_mode_from_string(mode); - if (desync_valid_zero_stage(dp->desync_mode0)) - { - mode = mode2; - mode2 = mode3; - mode3 = NULL; - } - else - { - dp->desync_mode0 = DESYNC_NONE; - } - dp->desync_mode = desync_mode_from_string(mode); - dp->desync_mode2 = desync_mode_from_string(mode2); - if (dp->desync_mode0 == DESYNC_INVALID || dp->desync_mode == DESYNC_INVALID || dp->desync_mode2 == DESYNC_INVALID) - { - DLOG_ERR("invalid dpi-desync mode\n"); - exit_clean(1); - } - if (mode3) - { - DLOG_ERR("invalid desync combo : %s+%s+%s\n", mode, mode2, mode3); - exit_clean(1); - } - if (dp->desync_mode2 && (desync_only_first_stage(dp->desync_mode) || !(desync_valid_first_stage(dp->desync_mode) && desync_valid_second_stage(dp->desync_mode2)))) - { - DLOG_ERR("invalid desync combo : %s+%s\n", mode, mode2); - exit_clean(1); - } -#if defined(__OpenBSD__) - if (dp->desync_mode == DESYNC_IPFRAG2 || dp->desync_mode2 == DESYNC_IPFRAG2) - { - DLOG_ERR("OpenBSD has checksum issues with fragmented packets. ipfrag disabled.\n"); - exit_clean(1); - } -#endif - } - break; -#if defined(__linux__) - case IDX_DPI_DESYNC_FWMARK: -#elif defined(SO_USER_COOKIE) - case IDX_DPI_DESYNC_SOCKARG: -#endif -#if defined(__linux__) || defined(SO_USER_COOKIE) - params.desync_fwmark = 0; - if (sscanf(optarg, "0x%X", ¶ms.desync_fwmark) <= 0) sscanf(optarg, "%u", ¶ms.desync_fwmark); - if (!params.desync_fwmark) - { - DLOG_ERR("fwmark/sockarg should be decimal or 0xHEX and should not be zero\n"); - exit_clean(1); - } - break; -#endif - - case IDX_DUP: - if (sscanf(optarg, "%u", &dp->dup_repeats) < 1 || dp->dup_repeats > 1024) - { - DLOG_ERR("dup-repeats must be within 0..1024\n"); - exit_clean(1); - } - break; - case IDX_DUP_TTL: - dp->dup_ttl = (uint8_t)atoi(optarg); - break; - case IDX_DUP_TTL6: - dp->dup_ttl6 = (uint8_t)atoi(optarg); - break; - case IDX_DUP_AUTOTTL: - if (!parse_autottl(optarg, &dp->dup_autottl, AUTOTTL_DEFAULT_DUP_DELTA, AUTOTTL_DEFAULT_DUP_MIN, AUTOTTL_DEFAULT_DUP_MAX)) - { - DLOG_ERR("dup-autottl value error\n"); - exit_clean(1); - } - params.autottl_present = true; - break; - case IDX_DUP_AUTOTTL6: - if (!parse_autottl(optarg, &dp->dup_autottl6, AUTOTTL_DEFAULT_DUP_DELTA, AUTOTTL_DEFAULT_DUP_MIN, AUTOTTL_DEFAULT_DUP_MAX)) - { - DLOG_ERR("dup-autottl6 value error\n"); - exit_clean(1); - } - params.autottl_present = true; - break; - case IDX_DUP_TCP_FLAGS_SET: - if (!parse_tcpflags(optarg, &dp->dup_tcp_flags_set)) - { - DLOG_ERR("invalid tcp flags\n"); - exit_clean(1); - } - break; - case IDX_DUP_TCP_FLAGS_UNSET: - if (!parse_tcpflags(optarg, &dp->dup_tcp_flags_unset)) - { - DLOG_ERR("invalid tcp flags\n"); - exit_clean(1); - } - break; - case IDX_DUP_REPLACE: - dp->dup_replace = !optarg || atoi(optarg); - break; - case IDX_DUP_FOOLING: - if (!parse_fooling(optarg, &dp->dup_fooling_mode)) - { - DLOG_ERR("fooling allowed values : none,md5sig,ts,badseq,badsum,datanoack,hopbyhop,hopbyhop2\n"); - exit_clean(1); - } - break; - case IDX_DUP_TS_INCREMENT: - if (!parse_net32_signed(optarg, &dp->dup_ts_increment)) - { - DLOG_ERR("dup-ts-increment should be signed decimal or signed 0xHEX\n"); - exit_clean(1); - } - break; - case IDX_DUP_BADSEQ_INCREMENT: - if (!parse_net32_signed(optarg, &dp->dup_badseq_increment)) - { - DLOG_ERR("dup-badseq-increment should be signed decimal or signed 0xHEX\n"); - exit_clean(1); - } - break; - case IDX_DUP_BADACK_INCREMENT: - if (!parse_net32_signed(optarg, &dp->dup_badseq_ack_increment)) - { - DLOG_ERR("dup-badack-increment should be signed decimal or signed 0xHEX\n"); - exit_clean(1); - } - break; - case IDX_DUP_CUTOFF: - if (!parse_cutoff(optarg, &dp->dup_cutoff, &dp->dup_cutoff_mode)) - { - DLOG_ERR("invalid dup-cutoff value\n"); - exit_clean(1); - } - break; - case IDX_DUP_START: - if (!parse_cutoff(optarg, &dp->dup_start, &dp->dup_start_mode)) - { - DLOG_ERR("invalid dup-start value\n"); - exit_clean(1); - } - break; - case IDX_DUP_IP_ID: - if (!strcmp(optarg,"zero")) - dp->dup_ip_id_mode = IPID_ZERO; - else if (!strcmp(optarg,"same")) - dp->dup_ip_id_mode = IPID_SAME; - else if (!strcmp(optarg,"seq")) - dp->dup_ip_id_mode = IPID_SEQ; - else if (!strcmp(optarg,"rnd")) - dp->dup_ip_id_mode = IPID_RND; - else - { - DLOG_ERR("invalid dup ip_id mode : %s\n",optarg); - exit_clean(1); - } - break; - - case IDX_ORIG_TTL: - dp->orig_mod_ttl = (uint8_t)atoi(optarg); - break; - case IDX_ORIG_TTL6: - dp->orig_mod_ttl6 = (uint8_t)atoi(optarg); - break; - case IDX_ORIG_AUTOTTL: - if (!parse_autottl(optarg, &dp->orig_autottl, AUTOTTL_DEFAULT_ORIG_DELTA, AUTOTTL_DEFAULT_ORIG_MIN, AUTOTTL_DEFAULT_ORIG_MAX)) - { - DLOG_ERR("orig-autottl value error\n"); - exit_clean(1); - } - params.autottl_present = true; - break; - case IDX_ORIG_AUTOTTL6: - if (!parse_autottl(optarg, &dp->orig_autottl6, AUTOTTL_DEFAULT_ORIG_DELTA, AUTOTTL_DEFAULT_ORIG_MIN, AUTOTTL_DEFAULT_ORIG_MAX)) - { - DLOG_ERR("orig-autottl6 value error\n"); - exit_clean(1); - } - params.autottl_present = true; - break; - case IDX_ORIG_TCP_FLAGS_SET: - if (!parse_tcpflags(optarg, &dp->orig_tcp_flags_set)) - { - DLOG_ERR("invalid tcp flags\n"); - exit_clean(1); - } - break; - case IDX_ORIG_TCP_FLAGS_UNSET: - if (!parse_tcpflags(optarg, &dp->orig_tcp_flags_unset)) - { - DLOG_ERR("invalid tcp flags\n"); - exit_clean(1); - } - break; - case IDX_ORIG_MOD_CUTOFF: - if (!parse_cutoff(optarg, &dp->orig_mod_cutoff, &dp->orig_mod_cutoff_mode)) - { - DLOG_ERR("invalid orig-mod-cutoff value\n"); - exit_clean(1); - } - break; - case IDX_ORIG_MOD_START: - if (!parse_cutoff(optarg, &dp->orig_mod_start, &dp->orig_mod_start_mode)) - { - DLOG_ERR("invalid orig-mod-start value\n"); - exit_clean(1); - } - break; - - case IDX_DPI_DESYNC_TTL: - dp->desync_ttl = (uint8_t)atoi(optarg); - break; - case IDX_DPI_DESYNC_TTL6: - dp->desync_ttl6 = (uint8_t)atoi(optarg); - break; - case IDX_DPI_DESYNC_AUTOTTL: - if (!parse_autottl(optarg, &dp->desync_autottl, AUTOTTL_DEFAULT_DESYNC_DELTA, AUTOTTL_DEFAULT_DESYNC_MIN, AUTOTTL_DEFAULT_DESYNC_MAX)) - { - DLOG_ERR("dpi-desync-autottl value error\n"); - exit_clean(1); - } - params.autottl_present = true; - break; - case IDX_DPI_DESYNC_AUTOTTL6: - if (!parse_autottl(optarg, &dp->desync_autottl6, AUTOTTL_DEFAULT_DESYNC_DELTA, AUTOTTL_DEFAULT_DESYNC_MIN, AUTOTTL_DEFAULT_DESYNC_MAX)) - { - DLOG_ERR("dpi-desync-autottl6 value error\n"); - exit_clean(1); - } - params.autottl_present = true; - break; - case IDX_DPI_DESYNC_TCP_FLAGS_SET: - if (!parse_tcpflags(optarg, &dp->desync_tcp_flags_set)) - { - DLOG_ERR("invalid tcp flags\n"); - exit_clean(1); - } - break; - case IDX_DPI_DESYNC_TCP_FLAGS_UNSET: - if (!parse_tcpflags(optarg, &dp->desync_tcp_flags_unset)) - { - DLOG_ERR("invalid tcp flags\n"); - exit_clean(1); - } - break; - case IDX_DPI_DESYNC_FOOLING: - if (!parse_fooling(optarg, &dp->desync_fooling_mode)) - { - DLOG_ERR("fooling allowed values : none,md5sig,ts,badseq,badsum,datanoack,hopbyhop,hopbyhop2\n"); - exit_clean(1); - } - break; - case IDX_DPI_DESYNC_REPEATS: - if (sscanf(optarg, "%u", &dp->desync_repeats) < 1 || !dp->desync_repeats || dp->desync_repeats > 1024) - { - DLOG_ERR("dpi-desync-repeats must be within 1..1024\n"); - exit_clean(1); - } - break; - case IDX_DPI_DESYNC_SKIP_NOSNI: - dp->desync_skip_nosni = !optarg || atoi(optarg); - break; - case IDX_DPI_DESYNC_SPLIT_POS: - { - int ct; - if (!parse_split_pos_list(optarg, dp->splits + dp->split_count, MAX_SPLITS - dp->split_count, &ct)) - { - DLOG_ERR("could not parse split pos list or too much positions (before parsing - %u, max - %u) : %s\n", dp->split_count, MAX_SPLITS, optarg); - exit_clean(1); - } - dp->split_count += ct; - break; - } - case IDX_DPI_DESYNC_SPLIT_HTTP_REQ: - // obsolete arg - DLOG_CONDUP("WARNING ! --dpi-desync-split-http-req is deprecated. use --dpi-desync-split-pos with markers.\n", MAX_SPLITS); - if (dp->split_count >= MAX_SPLITS) - { - DLOG_ERR("Too much splits. max splits: %u\n", MAX_SPLITS); - exit_clean(1); - } - if (!parse_httpreqpos(optarg, dp->splits + dp->split_count)) - { - DLOG_ERR("Invalid argument for dpi-desync-split-http-req\n"); - exit_clean(1); - } - dp->split_count++; - break; - case IDX_DPI_DESYNC_SPLIT_TLS: - // obsolete arg - DLOG_CONDUP("WARNING ! --dpi-desync-split-tls is deprecated. use --dpi-desync-split-pos with markers.\n", MAX_SPLITS); - if (dp->split_count >= MAX_SPLITS) - { - DLOG_ERR("Too much splits. max splits: %u\n", MAX_SPLITS); - exit_clean(1); - } - if (!parse_tlspos(optarg, dp->splits + dp->split_count)) - { - DLOG_ERR("Invalid argument for dpi-desync-split-tls\n"); - exit_clean(1); - } - dp->split_count++; - break; - case IDX_DPI_DESYNC_SPLIT_SEQOVL: - if (!strcmp(optarg, "0")) - { - // allow zero = disable seqovl - dp->seqovl.marker = PM_ABS; - dp->seqovl.pos = 0; - } - else if (!parse_split_pos(optarg, &dp->seqovl)) - { - DLOG_ERR("Invalid argument for dpi-desync-split-seqovl\n"); - exit_clean(1); - } - break; - case IDX_DPI_DESYNC_SPLIT_SEQOVL_PATTERN: - { - char buf[sizeof(dp->seqovl_pattern)]; - size_t sz = sizeof(buf); - load_file_or_exit(optarg, buf, &sz, NULL); - fill_pattern(dp->seqovl_pattern, sizeof(dp->seqovl_pattern), buf, sz, 0); - break; - } - case IDX_DPI_DESYNC_FAKEDSPLIT_PATTERN: - { - free(dp->fsplit_pattern); - if (!(dp->fsplit_pattern = malloc(dp->fsplit_pattern_size=32768))) - { - DLOG_ERR("out of memory\n"); - exit_clean(1); - } - load_file_or_exit(optarg, dp->fsplit_pattern, &dp->fsplit_pattern_size, NULL); - dp->fsplit_pattern = realloc(dp->fsplit_pattern, dp->fsplit_pattern_size); - break; - } - case IDX_DPI_DESYNC_FAKEDSPLIT_MOD: - if (!parse_fakedsplit_mod(optarg, &dp->fs_mod)) - { - DLOG_ERR("Invalid fakedsplit mod : %s\n", optarg); - exit_clean(1); - } - break; - case IDX_DPI_DESYNC_HOSTFAKESPLIT_MIDHOST: - if (!strcmp(optarg, "0")) - { - // allow zero = disable midhost split - dp->hostfakesplit_midhost.marker = PM_ABS; - dp->hostfakesplit_midhost.pos = 0; - } - else if (!parse_split_pos(optarg, &dp->hostfakesplit_midhost)) - { - DLOG_ERR("Invalid argument for dpi-desync-hostfakesplit-midhost\n"); - exit_clean(1); - } - break; - case IDX_DPI_DESYNC_HOSTFAKESPLIT_MOD: - if (!parse_hostfakesplit_mod(optarg, &dp->hfs_mod)) - { - DLOG_ERR("Invalid hostfakesplit mod : %s\n", optarg); - exit_clean(1); - } - break; - case IDX_DPI_DESYNC_IPFRAG_POS_TCP: - if (sscanf(optarg, "%u", &dp->desync_ipfrag_pos_tcp) < 1 || dp->desync_ipfrag_pos_tcp<1 || dp->desync_ipfrag_pos_tcp>DPI_DESYNC_MAX_FAKE_LEN) - { - DLOG_ERR("dpi-desync-ipfrag-pos-tcp must be within 1..%u range\n", DPI_DESYNC_MAX_FAKE_LEN); - exit_clean(1); - } - if (dp->desync_ipfrag_pos_tcp & 7) - { - DLOG_ERR("dpi-desync-ipfrag-pos-tcp must be multiple of 8\n"); - exit_clean(1); - } - break; - case IDX_DPI_DESYNC_IPFRAG_POS_UDP: - if (sscanf(optarg, "%u", &dp->desync_ipfrag_pos_udp) < 1 || dp->desync_ipfrag_pos_udp<1 || dp->desync_ipfrag_pos_udp>DPI_DESYNC_MAX_FAKE_LEN) - { - DLOG_ERR("dpi-desync-ipfrag-pos-udp must be within 1..%u range\n", DPI_DESYNC_MAX_FAKE_LEN); - exit_clean(1); - } - if (dp->desync_ipfrag_pos_udp & 7) - { - DLOG_ERR("dpi-desync-ipfrag-pos-udp must be multiple of 8\n"); - exit_clean(1); - } - break; - case IDX_DPI_DESYNC_TS_INCREMENT: - if (!parse_net32_signed(optarg, &dp->desync_ts_increment)) - { - DLOG_ERR("dpi-desync-ts-increment should be signed decimal or signed 0xHEX\n"); - exit_clean(1); - } - break; - case IDX_DPI_DESYNC_BADSEQ_INCREMENT: - if (!parse_net32_signed(optarg, &dp->desync_badseq_increment)) - { - DLOG_ERR("dpi-desync-badseq-increment should be signed decimal or signed 0xHEX\n"); - exit_clean(1); - } - break; - case IDX_DPI_DESYNC_BADACK_INCREMENT: - if (!parse_net32_signed(optarg, &dp->desync_badseq_ack_increment)) - { - DLOG_ERR("dpi-desync-badack-increment should be signed decimal or signed 0xHEX\n"); - exit_clean(1); - } - break; - case IDX_DPI_DESYNC_ANY_PROTOCOL: - dp->desync_any_proto = !optarg || atoi(optarg); - break; - case IDX_DPI_DESYNC_FAKE_TCP_MOD: - if (!parse_tcpmod(optarg, &dp->tcp_mod)) - { - DLOG_ERR("Invalid tcp mod : %s\n", optarg); - exit_clean(1); - } - break; - case IDX_DPI_DESYNC_FAKE_HTTP: - load_blob_to_collection(optarg, &dp->fake_http, FAKE_MAX_TCP, 0); - break; - case IDX_DPI_DESYNC_FAKE_TLS: - { - if (optarg[0] == '!' && (optarg[1] == 0 || optarg[1] == '+')) - dp->tls_fake_last = load_const_blob_to_collection(fake_tls_clienthello_default, sizeof(fake_tls_clienthello_default), &dp->fake_tls, 4 + sizeof(dp->tls_mod_last.sni), optarg[1] == '+' ? (size_t)atoi(optarg + 1) : 0); - else - dp->tls_fake_last = load_blob_to_collection(optarg, &dp->fake_tls, FAKE_MAX_TCP, 4 + sizeof(dp->tls_mod_last.sni)); - if (!(dp->tls_fake_last->extra2 = malloc(sizeof(struct fake_tls_mod)))) - { - DLOG_ERR("out of memory\n"); - exit_clean(1); - } - struct fake_tls_mod *tls_mod = (struct fake_tls_mod*)dp->tls_fake_last->extra2; - *tls_mod = dp->tls_mod_last; - tls_mod->mod |= FAKE_TLS_MOD_CUSTOM_FAKE; - } - break; - case IDX_DPI_DESYNC_FAKE_TLS_MOD: - if (!parse_tlsmod_list(optarg, &dp->tls_mod_last)) - { - DLOG_ERR("Invalid tls mod : %s\n", optarg); - exit_clean(1); - } - if (dp->tls_fake_last) - *(struct fake_tls_mod*)dp->tls_fake_last->extra2 = dp->tls_mod_last; - break; - case IDX_DPI_DESYNC_FAKE_UNKNOWN: - load_blob_to_collection(optarg, &dp->fake_unknown, FAKE_MAX_TCP, 0); - break; - case IDX_DPI_DESYNC_FAKE_SYNDATA: - dp->fake_syndata_size = sizeof(dp->fake_syndata); - load_file_or_exit(optarg, dp->fake_syndata, &dp->fake_syndata_size, NULL); - break; - case IDX_DPI_DESYNC_FAKE_QUIC: - load_blob_to_collection(optarg, &dp->fake_quic, FAKE_MAX_UDP, 0); - break; - case IDX_DPI_DESYNC_FAKE_WIREGUARD: - load_blob_to_collection(optarg, &dp->fake_wg, FAKE_MAX_UDP, 0); - break; - case IDX_DPI_DESYNC_FAKE_DHT: - load_blob_to_collection(optarg, &dp->fake_dht, FAKE_MAX_UDP, 0); - break; - case IDX_DPI_DESYNC_FAKE_DISCORD: - load_blob_to_collection(optarg, &dp->fake_discord, FAKE_MAX_UDP, 0); - break; - case IDX_DPI_DESYNC_FAKE_STUN: - load_blob_to_collection(optarg, &dp->fake_stun, FAKE_MAX_UDP, 0); - break; - case IDX_DPI_DESYNC_FAKE_UNKNOWN_UDP: - load_blob_to_collection(optarg, &dp->fake_unknown_udp, FAKE_MAX_UDP, 0); - break; - case IDX_DPI_DESYNC_UDPLEN_INCREMENT: - if (sscanf(optarg, "%d", &dp->udplen_increment) < 1 || dp->udplen_increment > 0x7FFF || dp->udplen_increment < -0x8000) - { - DLOG_ERR("dpi-desync-udplen-increment must be integer within -32768..32767 range\n"); - exit_clean(1); - } - break; - case IDX_DPI_DESYNC_UDPLEN_PATTERN: - { - char buf[sizeof(dp->udplen_pattern)]; - size_t sz = sizeof(buf); - load_file_or_exit(optarg, buf, &sz, NULL); - fill_pattern(dp->udplen_pattern, sizeof(dp->udplen_pattern), buf, sz, 0); - } - break; - case IDX_DPI_DESYNC_CUTOFF: - if (!parse_cutoff(optarg, &dp->desync_cutoff, &dp->desync_cutoff_mode)) - { - DLOG_ERR("invalid desync-cutoff value\n"); - exit_clean(1); - } - break; - case IDX_DPI_DESYNC_START: - if (!parse_cutoff(optarg, &dp->desync_start, &dp->desync_start_mode)) - { - DLOG_ERR("invalid desync-start value\n"); - exit_clean(1); - } - break; - case IDX_HOSTLIST: - if (bSkip) break; - if (!RegisterHostlist(dp, false, optarg)) - { - DLOG_ERR("failed to register hostlist '%s'\n", optarg); - exit_clean(1); - } - break; - case IDX_HOSTLIST_DOMAINS: - if (bSkip) break; - if (!anon_hl && !(anon_hl = RegisterHostlist(dp, false, NULL))) - { - DLOG_ERR("failed to register anonymous hostlist\n"); - exit_clean(1); - } - if (!parse_domain_list(optarg, &anon_hl->hostlist)) - { - DLOG_ERR("failed to add domains to anonymous hostlist\n"); - exit_clean(1); - } - break; - case IDX_HOSTLIST_EXCLUDE: - if (bSkip) break; - if (!RegisterHostlist(dp, true, optarg)) - { - DLOG_ERR("failed to register hostlist '%s'\n", optarg); - exit_clean(1); - } - break; - case IDX_HOSTLIST_EXCLUDE_DOMAINS: - if (bSkip) break; - if (!anon_hl_exclude && !(anon_hl_exclude = RegisterHostlist(dp, true, NULL))) - { - DLOG_ERR("failed to register anonymous hostlist\n"); - exit_clean(1); - } - if (!parse_domain_list(optarg, &anon_hl_exclude->hostlist)) - { - DLOG_ERR("failed to add domains to anonymous hostlist\n"); - exit_clean(1); - } - break; - case IDX_HOSTLIST_AUTO: - if (bSkip) break; - if (dp->hostlist_auto) - { - DLOG_ERR("only one auto hostlist per profile is supported\n"); - exit_clean(1); - } - { - FILE *F = fopen(optarg, "a+b"); - if (!F) - { - DLOG_ERR("cannot create %s\n", optarg); - exit_clean(1); - } - bool bGzip = is_gzip(F); - fclose(F); - if (bGzip) - { - DLOG_ERR("gzipped auto hostlists are not supported\n"); - exit_clean(1); - } - } - if (!(dp->hostlist_auto = RegisterHostlist(dp, false, optarg))) - { - DLOG_ERR("failed to register hostlist '%s'\n", optarg); - exit_clean(1); - } - break; - case IDX_HOSTLIST_AUTO_FAIL_THRESHOLD: - dp->hostlist_auto_fail_threshold = (uint8_t)atoi(optarg); - if (dp->hostlist_auto_fail_threshold < 1 || dp->hostlist_auto_fail_threshold>20) - { - DLOG_ERR("auto hostlist fail threshold must be within 1..20\n"); - exit_clean(1); - } - break; - case IDX_HOSTLIST_AUTO_FAIL_TIME: - dp->hostlist_auto_fail_time = (uint8_t)atoi(optarg); - if (dp->hostlist_auto_fail_time < 1) - { - DLOG_ERR("auto hostlist fail time is not valid\n"); - exit_clean(1); - } - break; - case IDX_HOSTLIST_AUTO_RETRANS_THRESHOLD: - dp->hostlist_auto_retrans_threshold = (uint8_t)atoi(optarg); - if (dp->hostlist_auto_retrans_threshold < 2 || dp->hostlist_auto_retrans_threshold>10) - { - DLOG_ERR("auto hostlist fail threshold must be within 2..10\n"); - exit_clean(1); - } - break; - case IDX_HOSTLIST_AUTO_DEBUG: - { - FILE *F = fopen(optarg, "a+t"); - if (!F) - { - DLOG_ERR("cannot create %s\n", optarg); - exit_clean(1); - } - fclose(F); - strncpy(params.hostlist_auto_debuglog, optarg, sizeof(params.hostlist_auto_debuglog)); - params.hostlist_auto_debuglog[sizeof(params.hostlist_auto_debuglog) - 1] = '\0'; - } - break; - - case IDX_NEW: - if (bSkip) - { - dp_clear(dp); - dp_init(dp); - dp->n = desync_profile_count; - bSkip = false; - } - else - { - check_dp(dp); - if (!(dpl = dp_list_add(¶ms.desync_profiles))) - { - DLOG_ERR("desync_profile_add: out of memory\n"); - exit_clean(1); - } - dp = &dpl->dp; - dp->n = ++desync_profile_count; - } - anon_hl = anon_hl_exclude = NULL; - anon_ips = anon_ips_exclude = NULL; - break; - case IDX_SKIP: - bSkip = true; - break; - - case IDX_FILTER_L3: - if (!wf_make_l3(optarg, &dp->filter_ipv4, &dp->filter_ipv6)) - { - DLOG_ERR("bad value for --filter-l3\n"); - exit_clean(1); - } - break; - case IDX_FILTER_TCP: - if (!parse_pf_list(optarg, &dp->pf_tcp)) - { - DLOG_ERR("Invalid port filter : %s\n", optarg); - exit_clean(1); - } - // deny tcp if not set - if (!port_filters_deny_if_empty(&dp->pf_udp)) - exit_clean(1); - break; - case IDX_FILTER_UDP: - if (!parse_pf_list(optarg, &dp->pf_udp)) - { - DLOG_ERR("Invalid port filter : %s\n", optarg); - exit_clean(1); - } - // deny tcp if not set - if (!port_filters_deny_if_empty(&dp->pf_tcp)) - exit_clean(1); - break; - case IDX_FILTER_L7: - if (!parse_l7_list(optarg, &dp->filter_l7)) - { - DLOG_ERR("Invalid l7 filter : %s\n", optarg); - exit_clean(1); - } - break; -#ifdef HAS_FILTER_SSID - case IDX_FILTER_SSID: - if (!parse_strlist(optarg, &dp->filter_ssid)) - { - DLOG_ERR("strlist_add failed\n"); - exit_clean(1); - } - params.filter_ssid_present = true; - break; -#endif - case IDX_IPSET: - if (bSkip) break; - if (!RegisterIpset(dp, false, optarg)) - { - DLOG_ERR("failed to register ipset '%s'\n", optarg); - exit_clean(1); - } - break; - case IDX_IPSET_IP: - if (bSkip) break; - if (!anon_ips && !(anon_ips = RegisterIpset(dp, false, NULL))) - { - DLOG_ERR("failed to register anonymous ipset\n"); - exit_clean(1); - } - if (!parse_ip_list(optarg, &anon_ips->ipset)) - { - DLOG_ERR("failed to add subnets to anonymous ipset\n"); - exit_clean(1); - } - break; - case IDX_IPSET_EXCLUDE: - if (bSkip) break; - if (!RegisterIpset(dp, true, optarg)) - { - DLOG_ERR("failed to register ipset '%s'\n", optarg); - exit_clean(1); - } - break; - case IDX_IPSET_EXCLUDE_IP: - if (bSkip) break; - if (!anon_ips_exclude && !(anon_ips_exclude = RegisterIpset(dp, true, NULL))) - { - DLOG_ERR("failed to register anonymous ipset\n"); - exit_clean(1); - } - if (!parse_ip_list(optarg, &anon_ips_exclude->ipset)) - { - DLOG_ERR("failed to add subnets to anonymous ipset\n"); - exit_clean(1); - } - break; - - -#ifdef __linux__ - case IDX_BIND_FIX4: - params.bind_fix4 = true; - break; - case IDX_BIND_FIX6: - params.bind_fix6 = true; - break; -#elif defined(__CYGWIN__) - case IDX_WF_IFACE: - if (!sscanf(optarg, "%u.%u", &IfIdx, &SubIfIdx)) - { - DLOG_ERR("bad value for --wf-iface\n"); - exit_clean(1); - } - break; - case IDX_WF_L3: - if (!wf_make_l3(optarg, &wf_ipv4, &wf_ipv6)) - { - DLOG_ERR("bad value for --wf-l3\n"); - exit_clean(1); - } - break; - case IDX_WF_TCP: - hash_wf_tcp = hash_jen(optarg, strlen(optarg)); - if (!wf_make_pf(optarg, "tcp", "SrcPort", wf_pf_tcp_src, sizeof(wf_pf_tcp_src)) || - !wf_make_pf(optarg, "tcp", "DstPort", wf_pf_tcp_dst, sizeof(wf_pf_tcp_dst))) - { - DLOG_ERR("bad value for --wf-tcp\n"); - exit_clean(1); - } - break; - case IDX_WF_UDP: - hash_wf_udp = hash_jen(optarg, strlen(optarg)); - if (!wf_make_pf(optarg, "udp", "SrcPort", wf_pf_udp_src, sizeof(wf_pf_udp_src)) || - !wf_make_pf(optarg, "udp", "DstPort", wf_pf_udp_dst, sizeof(wf_pf_udp_dst))) - { - DLOG_ERR("bad value for --wf-udp\n"); - exit_clean(1); - } - break; - case IDX_WF_RAW: - hash_wf_raw = hash_jen(optarg, strlen(optarg)); - if (optarg[0] == '@') - { - size_t sz = sizeof(windivert_filter) - 1; - load_file_or_exit(optarg, windivert_filter, &sz, NULL); - windivert_filter[sz] = 0; - } - else - { - strncpy(windivert_filter, optarg, sizeof(windivert_filter)); - windivert_filter[sizeof(windivert_filter) - 1] = '\0'; - } - break; - case IDX_WF_RAW_PART: - hash_wf_raw_part ^= hash_jen(optarg, strlen(optarg)); - { - char wfpart[sizeof(windivert_filter)]; - if (optarg[0] == '@') - { - size_t sz = sizeof(wfpart) - 1; - load_file_or_exit(optarg, wfpart, &sz, NULL); - wfpart[sz] = 0; - } - else - { - strncpy(wfpart, optarg, sizeof(wfpart)); - wfpart[sizeof(wfpart) - 1] = '\0'; - } - if (!strlist_add(¶ms.wf_raw_part, wfpart)) - { - DLOG_ERR("out of memory\n"); - exit_clean(1); - } - } - break; - case IDX_WF_FILTER_LAN: - wf_filter_lan = !!atoi(optarg); - break; - case IDX_WF_SAVE: - strncpy(wf_save_file, optarg, sizeof(wf_save_file)); - wf_save_file[sizeof(wf_save_file) - 1] = '\0'; - break; - case IDX_SSID_FILTER: - hash_ssid_filter = hash_jen(optarg, strlen(optarg)); - if (!parse_strlist(optarg, ¶ms.ssid_filter)) - { - DLOG_ERR("strlist_add failed\n"); - exit_clean(1); - } - break; - case IDX_NLM_FILTER: - hash_nlm_filter = hash_jen(optarg, strlen(optarg)); - if (!parse_strlist(optarg, ¶ms.nlm_filter)) - { - DLOG_ERR("strlist_add failed\n"); - exit_clean(1); - } - break; - case IDX_NLM_LIST: - if (!nlm_list(optarg && !strcmp(optarg, "all"))) - { - DLOG_ERR("could not get list of NLM networks\n"); - exit_clean(1); - } - exit_clean(0); - -#endif - } - } - if (bSkip) - { - LIST_REMOVE(dpl, next); - dp_entry_destroy(dpl); - desync_profile_count--; - } - else - check_dp(dp); - - // do not need args from file anymore -#if !defined( __OpenBSD__) && !defined(__ANDROID__) - cleanup_args(¶ms); -#endif - argv = NULL; argc = 0; - -#ifdef __linux__ - if (params.qnum < 0) - { - DLOG_ERR("Need queue number (--qnum)\n"); - exit_clean(1); - } -#elif defined(BSD) - if (!params.port) - { - DLOG_ERR("Need divert port (--port)\n"); - exit_clean(1); - } -#endif - - DLOG("adding low-priority default empty desync profile\n"); - // add default empty profile - if (!(dpl = dp_list_add(¶ms.desync_profiles))) - { - DLOG_ERR("desync_profile_add: out of memory\n"); - exit_clean(1); - } - - DLOG_CONDUP("we have %d user defined desync profile(s) and default low priority profile 0\n", desync_profile_count); - -#ifndef __CYGWIN__ - if (params.debug_target == LOG_TARGET_FILE && params.droproot && chown(params.debug_logfile, params.uid, -1)) - fprintf(stderr, "could not chown %s. log file may not be writable after privilege drop\n", params.debug_logfile); - if (params.droproot && *params.hostlist_auto_debuglog && chown(params.hostlist_auto_debuglog, params.uid, -1)) - DLOG_ERR("could not chown %s. auto hostlist debug log may not be writable after privilege drop\n", params.hostlist_auto_debuglog); -#endif - LIST_FOREACH(dpl, ¶ms.desync_profiles, next) - { - dp = &dpl->dp; - - // not specified - use desync_ttl value instead - if (dp->desync_ttl6 == 0xFF) dp->desync_ttl6 = dp->desync_ttl; - if (dp->dup_ttl6 == 0xFF) dp->dup_ttl6 = dp->dup_ttl; - if (dp->orig_mod_ttl6 == 0xFF) dp->orig_mod_ttl6 = dp->orig_mod_ttl; - if (!dp->fsplit_pattern) - { - if ((dp->fsplit_pattern=calloc(64,1))) - dp->fsplit_pattern_size=64; - else - { - DLOG_ERR("out of memory\n"); - exit_clean(1); - } - } - if (!AUTOTTL_ENABLED(dp->desync_autottl6)) dp->desync_autottl6 = dp->desync_autottl; - if (!AUTOTTL_ENABLED(dp->orig_autottl6)) dp->orig_autottl6 = dp->orig_autottl; - if (!AUTOTTL_ENABLED(dp->dup_autottl6)) dp->dup_autottl6 = dp->dup_autottl; - if (AUTOTTL_ENABLED(dp->desync_autottl)) - DLOG("profile %d desync autottl ipv4 %s%d:%u-%u\n", dp->n, UNARY_PLUS(dp->desync_autottl.delta), dp->desync_autottl.delta, dp->desync_autottl.min, dp->desync_autottl.max); - if (AUTOTTL_ENABLED(dp->desync_autottl6)) - DLOG("profile %d desync autottl ipv6 %s%d:%u-%u\n", dp->n, UNARY_PLUS(dp->desync_autottl6.delta), dp->desync_autottl6.delta, dp->desync_autottl6.min, dp->desync_autottl6.max); - if (AUTOTTL_ENABLED(dp->orig_autottl)) - DLOG("profile %d orig autottl ipv4 %s%d:%u-%u\n", dp->n, UNARY_PLUS(dp->orig_autottl.delta), dp->orig_autottl.delta, dp->orig_autottl.min, dp->orig_autottl.max); - if (AUTOTTL_ENABLED(dp->orig_autottl6)) - DLOG("profile %d orig autottl ipv6 %s%d:%u-%u\n", dp->n, UNARY_PLUS(dp->orig_autottl6.delta), dp->orig_autottl6.delta, dp->orig_autottl6.min, dp->orig_autottl6.max); - if (AUTOTTL_ENABLED(dp->dup_autottl)) - DLOG("profile %d dup autottl ipv4 %s%d:%u-%u\n", dp->n, UNARY_PLUS(dp->dup_autottl.delta), dp->dup_autottl.delta, dp->dup_autottl.min, dp->dup_autottl.max); - if (AUTOTTL_ENABLED(dp->dup_autottl6)) - DLOG("profile %d dup autottl ipv6 %s%d:%u-%u\n", dp->n, UNARY_PLUS(dp->dup_autottl6.delta), dp->dup_autottl6.delta, dp->dup_autottl6.min, dp->dup_autottl6.max); - split_compat(dp); - if (!dp_fake_defaults(dp)) - { - DLOG_ERR("could not fill fake defaults\n"); - exit_clean(1); - } - if (!onetime_tls_mod(dp)) - { - DLOG_ERR("could not mod tls\n"); - exit_clean(1); - } -#ifndef __CYGWIN__ - if (params.droproot && dp->hostlist_auto && chown(dp->hostlist_auto->filename, params.uid, -1)) - DLOG_ERR("could not chown %s. auto hostlist file may not be writable after privilege drop\n", dp->hostlist_auto->filename); -#endif - } - - if (!test_list_files()) - exit_clean(1); - - if (!LoadAllHostLists()) - { - DLOG_ERR("hostlists load failed\n"); - exit_clean(1); - } - if (!LoadAllIpsets()) - { - DLOG_ERR("ipset load failed\n"); - exit_clean(1); - } - - DLOG("\nlists summary:\n"); - HostlistsDebug(); - IpsetsDebug(); - - DLOG("\nsplits summary:\n"); - SplitDebug(); - DLOG("\n"); - -#ifdef __CYGWIN__ - if (!*windivert_filter) - { - if (!*wf_pf_tcp_src && !*wf_pf_udp_src && LIST_EMPTY(¶ms.wf_raw_part)) - { - DLOG_ERR("windivert filter : must specify port or/and partial raw filter\n"); - exit_clean(1); - } - if (!wf_make_filter(windivert_filter, sizeof(windivert_filter), IfIdx, SubIfIdx, wf_ipv4, wf_ipv6, wf_pf_tcp_src, wf_pf_tcp_dst, wf_pf_udp_src, wf_pf_udp_dst, ¶ms.wf_raw_part, wf_filter_lan)) - { - DLOG_ERR("windivert filter : could not make filter\n"); - exit_clean(1); - } - } - DLOG("windivert filter size: %zu\nwindivert filter:\n%s\n", strlen(windivert_filter), windivert_filter); - if (*wf_save_file) - { - if (save_file(wf_save_file, windivert_filter, strlen(windivert_filter))) - { - DLOG_ERR("windivert filter: raw filter saved to %s\n", wf_save_file); - exit_clean(0); - } - else - { - DLOG_ERR("windivert filter: could not save raw filter to %s\n", wf_save_file); - exit_clean(1); - } - } - HANDLE hMutexArg; - { - char mutex_name[128]; - snprintf(mutex_name, sizeof(mutex_name), "Global\\winws_arg_%u_%u_%u_%u_%u_%u_%u_%u_%u_%u", hash_wf_tcp, hash_wf_udp, hash_wf_raw, hash_wf_raw_part, hash_ssid_filter, hash_nlm_filter, IfIdx, SubIfIdx, wf_ipv4, wf_ipv6); - - hMutexArg = CreateMutexA(NULL, TRUE, mutex_name); - if (hMutexArg && GetLastError() == ERROR_ALREADY_EXISTS) - { - CloseHandle(hMutexArg); hMutexArg = NULL; - DLOG_ERR("A copy of winws is already running with the same filter\n"); - goto exiterr; - } - } -#endif - - if (bDry) - { -#ifndef __CYGWIN__ - if (params.droproot) - { - if (!droproot(params.uid, params.user, params.gid, params.gid_count)) - exit_clean(1); -#ifdef __linux__ - if (!dropcaps()) - exit_clean(1); -#endif - print_id(); - if (!test_list_files()) - exit_clean(1); - } -#endif - DLOG_CONDUP("command line parameters verified\n"); - exit_clean(0); - } - - if (params.ctrack_disable) - DLOG_CONDUP("conntrack disabled ! some functions will not work. make sure it's what you want.\n"); - else - { - DLOG("initializing conntrack with timeouts tcp=%u:%u:%u udp=%u\n", params.ctrack_t_syn, params.ctrack_t_est, params.ctrack_t_fin, params.ctrack_t_udp); - ConntrackPoolInit(¶ms.conntrack, 10, params.ctrack_t_syn, params.ctrack_t_est, params.ctrack_t_fin, params.ctrack_t_udp); - } - if (params.autottl_present || params.cache_hostname) DLOG("ipcache lifetime %us\n", params.ipcache_lifetime); - -#ifdef __linux__ - result = nfq_main(); -#elif defined(BSD) - result = dvt_main(); -#elif defined(__CYGWIN__) - result = win_main(windivert_filter); -#else -#error unsupported OS -#endif -ex: - rawsend_cleanup(); - cleanup_params(¶ms); -#ifdef __CYGWIN__ - if (hMutexArg) - { - ReleaseMutex(hMutexArg); - CloseHandle(hMutexArg); - } -#endif - return result; -exiterr: - result = 1; - goto ex; -} diff --git a/nfq/nfqws.h b/nfq/nfqws.h deleted file mode 100644 index 745f6951..00000000 --- a/nfq/nfqws.h +++ /dev/null @@ -1,12 +0,0 @@ -#pragma once - -#include - -#ifdef __linux__ -#define HAS_FILTER_SSID 1 -#endif - -#ifdef __CYGWIN__ -extern bool bQuit; -#endif -int main(int argc, char *argv[]); diff --git a/nfq/packet_queue.c b/nfq/packet_queue.c deleted file mode 100644 index 3c63b1cd..00000000 --- a/nfq/packet_queue.c +++ /dev/null @@ -1,70 +0,0 @@ -#include -#include -#include - -#include "packet_queue.h" - -void rawpacket_queue_init(struct rawpacket_tailhead *q) -{ - TAILQ_INIT(q); -} -void rawpacket_free(struct rawpacket *rp) -{ - if (rp) free(rp->packet); - free(rp); -} -struct rawpacket *rawpacket_dequeue(struct rawpacket_tailhead *q) -{ - struct rawpacket *rp; - rp = TAILQ_FIRST(q); - if (rp) TAILQ_REMOVE(q, rp, next); - return rp; -} -void rawpacket_queue_destroy(struct rawpacket_tailhead *q) -{ - struct rawpacket *rp; - while((rp = rawpacket_dequeue(q))) rawpacket_free(rp); -} - -struct rawpacket *rawpacket_queue(struct rawpacket_tailhead *q,const struct sockaddr_storage* dst,uint32_t fwmark,const char *ifin,const char *ifout,const void *data,size_t len,size_t len_payload) -{ - struct rawpacket *rp = malloc(sizeof(struct rawpacket)); - if (!rp) return NULL; - - rp->packet = malloc(len); - if (!rp->packet) - { - free(rp); - return NULL; - } - - rp->dst = *dst; - rp->fwmark = fwmark; - if (ifin) - snprintf(rp->ifin,sizeof(rp->ifin),"%s",ifin); - else - *rp->ifin = 0; - if (ifout) - snprintf(rp->ifout,sizeof(rp->ifout),"%s",ifout); - else - *rp->ifout = 0; - memcpy(rp->packet,data,len); - rp->len=len; - rp->len_payload=len_payload; - - TAILQ_INSERT_TAIL(q, rp, next); - - return rp; -} - -unsigned int rawpacket_queue_count(const struct rawpacket_tailhead *q) -{ - const struct rawpacket *rp; - unsigned int ct=0; - TAILQ_FOREACH(rp, q, next) ct++; - return ct; -} -bool rawpacket_queue_empty(const struct rawpacket_tailhead *q) -{ - return !TAILQ_FIRST(q); -} diff --git a/nfq/packet_queue.h b/nfq/packet_queue.h deleted file mode 100644 index 6d2c81c8..00000000 --- a/nfq/packet_queue.h +++ /dev/null @@ -1,26 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include - -struct rawpacket -{ - struct sockaddr_storage dst; - char ifin[IFNAMSIZ], ifout[IFNAMSIZ]; - uint32_t fwmark; - size_t len, len_payload; - uint8_t *packet; - TAILQ_ENTRY(rawpacket) next; -}; -TAILQ_HEAD(rawpacket_tailhead, rawpacket); - -void rawpacket_queue_init(struct rawpacket_tailhead *q); -void rawpacket_queue_destroy(struct rawpacket_tailhead *q); -bool rawpacket_queue_empty(const struct rawpacket_tailhead *q); -unsigned int rawpacket_queue_count(const struct rawpacket_tailhead *q); -struct rawpacket *rawpacket_queue(struct rawpacket_tailhead *q,const struct sockaddr_storage* dst,uint32_t fwmark,const char *ifin,const char *ifout,const void *data,size_t len,size_t len_payload); -struct rawpacket *rawpacket_dequeue(struct rawpacket_tailhead *q); -void rawpacket_free(struct rawpacket *rp); diff --git a/nfq/params.c b/nfq/params.c deleted file mode 100644 index a59a810f..00000000 --- a/nfq/params.c +++ /dev/null @@ -1,385 +0,0 @@ -#include "params.h" - -#include -#include -#include -#ifdef __ANDROID__ -#include -#endif - -#include "pools.h" -#include "desync.h" - -#ifdef BSD -const char *progname = "dvtws"; -#elif defined(__CYGWIN__) -const char *progname = "winws"; -#elif defined(__linux__) -const char *progname = "nfqws"; -#else -#error UNKNOWN_SYSTEM_TIME -#endif - -const char * tld[6] = { "com","org","net","edu","gov","biz" }; - -int DLOG_FILE(FILE *F, const char *format, va_list args) -{ - return vfprintf(F, format, args); -} -int DLOG_CON(const char *format, int syslog_priority, va_list args) -{ - return DLOG_FILE(syslog_priority==LOG_ERR ? stderr : stdout, format, args); -} -int DLOG_FILENAME(const char *filename, const char *format, va_list args) -{ - int r; - FILE *F = fopen(filename,"at"); - if (F) - { - r = DLOG_FILE(F, format, args); - fclose(F); - } - else - r=-1; - return r; -} - -typedef void (*f_log_function)(int priority, const char *line); - -static char log_buf[1024]; -static size_t log_buf_sz=0; -static void syslog_log_function(int priority, const char *line) -{ - syslog(priority,"%s",log_buf); -} -#ifdef __ANDROID__ -static enum android_LogPriority syslog_priority_to_android(int priority) -{ - enum android_LogPriority ap; - switch(priority) - { - case LOG_INFO: - case LOG_NOTICE: ap=ANDROID_LOG_INFO; break; - case LOG_ERR: ap=ANDROID_LOG_ERROR; break; - case LOG_WARNING: ap=ANDROID_LOG_WARN; break; - case LOG_EMERG: - case LOG_ALERT: - case LOG_CRIT: ap=ANDROID_LOG_FATAL; break; - case LOG_DEBUG: ap=ANDROID_LOG_DEBUG; break; - default: ap=ANDROID_LOG_UNKNOWN; - } - return ap; -} -static void android_log_function(int priority, const char *line) -{ - __android_log_print(syslog_priority_to_android(priority), progname, "%s", line); -} -#endif -static void log_buffered(f_log_function log_function, int syslog_priority, const char *format, va_list args) -{ - if (vsnprintf(log_buf+log_buf_sz,sizeof(log_buf)-log_buf_sz,format,args)>0) - { - log_buf_sz=strlen(log_buf); - // log when buffer is full or buffer ends with \n - if (log_buf_sz>=(sizeof(log_buf)-1) || (log_buf_sz && log_buf[log_buf_sz-1]=='\n')) - { - log_function(syslog_priority,log_buf); - log_buf_sz = 0; - } - } -} - -static int DLOG_VA(const char *format, int syslog_priority, bool condup, va_list args) -{ - int r=0; - va_list args2; - - if (condup && !(params.debug && params.debug_target==LOG_TARGET_CONSOLE)) - { - va_copy(args2,args); - DLOG_CON(format,syslog_priority,args2); - va_end(args2); - } - if (params.debug) - { - switch(params.debug_target) - { - case LOG_TARGET_CONSOLE: - r = DLOG_CON(format,syslog_priority,args); - break; - case LOG_TARGET_FILE: - r = DLOG_FILENAME(params.debug_logfile,format,args); - break; - case LOG_TARGET_SYSLOG: - // skip newlines - log_buffered(syslog_log_function,syslog_priority,format,args); - r = 1; - break; -#ifdef __ANDROID__ - case LOG_TARGET_ANDROID: - // skip newlines - log_buffered(android_log_function,syslog_priority,format,args); - r = 1; - break; -#endif - default: - break; - } - } - return r; -} - -int DLOG(const char *format, ...) -{ - int r; - va_list args; - va_start(args, format); - r = DLOG_VA(format, LOG_DEBUG, false, args); - va_end(args); - return r; -} -int DLOG_CONDUP(const char *format, ...) -{ - int r; - va_list args; - va_start(args, format); - r = DLOG_VA(format, LOG_DEBUG, true, args); - va_end(args); - return r; -} -int DLOG_ERR(const char *format, ...) -{ - int r; - va_list args; - va_start(args, format); - r = DLOG_VA(format, LOG_ERR, true, args); - va_end(args); - return r; -} -int DLOG_PERROR(const char *s) -{ - return DLOG_ERR("%s: %s\n", s, strerror(errno)); -} - - -int LOG_APPEND(const char *filename, const char *format, va_list args) -{ - int r; - FILE *F = fopen(filename,"at"); - if (F) - { - fprint_localtime(F); - fprintf(F, " : "); - r = vfprintf(F, format, args); - fprintf(F, "\n"); - fclose(F); - } - else - r=-1; - return r; -} - -int HOSTLIST_DEBUGLOG_APPEND(const char *format, ...) -{ - if (*params.hostlist_auto_debuglog) - { - int r; - va_list args; - - va_start(args, format); - r = LOG_APPEND(params.hostlist_auto_debuglog, format, args); - va_end(args); - return r; - } - else - return 0; -} - -void hexdump_limited_dlog(const uint8_t *data, size_t size, size_t limit) -{ - size_t k; - bool bcut = false; - if (size > limit) - { - size = limit; - bcut = true; - } - if (!size) return; - for (k = 0; k < size; k++) DLOG("%02X ", data[k]); - DLOG(bcut ? "... : " : ": "); - for (k = 0; k < size; k++) DLOG("%c", data[k] >= 0x20 && data[k] <= 0x7F ? (char)data[k] : '.'); - if (bcut) DLOG(" ..."); -} - -void dp_init(struct desync_profile *dp) -{ - LIST_INIT(&dp->hl_collection); - LIST_INIT(&dp->hl_collection_exclude); - LIST_INIT(&dp->ips_collection); - LIST_INIT(&dp->ips_collection_exclude); - LIST_INIT(&dp->pf_tcp); - LIST_INIT(&dp->pf_udp); -#ifdef HAS_FILTER_SSID - LIST_INIT(&dp->filter_ssid); -#endif - - memcpy(dp->hostspell, "host", 4); // default hostspell - dp->desync_skip_nosni = true; - dp->desync_ipfrag_pos_udp = IPFRAG_UDP_DEFAULT; - dp->desync_ipfrag_pos_tcp = IPFRAG_TCP_DEFAULT; - dp->desync_repeats = 1; - dp->fake_syndata_size = 16; - dp->wscale=-1; // default - dont change scale factor (client) - dp->desync_ttl6 = dp->dup_ttl6 = dp->orig_mod_ttl6 = 0xFF; // unused - dp->desync_ts_increment = dp->dup_ts_increment = TS_INCREMENT_DEFAULT; - dp->desync_badseq_increment = dp->dup_badseq_increment = BADSEQ_INCREMENT_DEFAULT; - dp->desync_badseq_ack_increment = dp->dup_badseq_ack_increment = BADSEQ_ACK_INCREMENT_DEFAULT; - dp->wssize_cutoff_mode = dp->desync_start_mode = dp->desync_cutoff_mode = dp->dup_start_mode = dp->dup_cutoff_mode = dp->orig_mod_start_mode = dp->orig_mod_cutoff_mode = 'n'; // packet number by default - dp->udplen_increment = UDPLEN_INCREMENT_DEFAULT; - dp->hostlist_auto_fail_threshold = HOSTLIST_AUTO_FAIL_THRESHOLD_DEFAULT; - dp->hostlist_auto_fail_time = HOSTLIST_AUTO_FAIL_TIME_DEFAULT; - dp->hostlist_auto_retrans_threshold = HOSTLIST_AUTO_RETRANS_THRESHOLD_DEFAULT; - dp->filter_ipv4 = dp->filter_ipv6 = true; - dp->dup_ip_id_mode = IPID_SAME; -} -bool dp_fake_defaults(struct desync_profile *dp) -{ - struct blob_item *item; - if (blob_collection_empty(&dp->fake_http)) - if (!blob_collection_add_blob(&dp->fake_http,fake_http_request_default,strlen(fake_http_request_default),0,0)) - return false; - if (blob_collection_empty(&dp->fake_tls)) - { - if (!(item=blob_collection_add_blob(&dp->fake_tls,fake_tls_clienthello_default,sizeof(fake_tls_clienthello_default),4+sizeof(((struct fake_tls_mod*)0)->sni),0))) - return false; - if (!(item->extra2 = malloc(sizeof(struct fake_tls_mod)))) - return false; - *(struct fake_tls_mod*)item->extra2 = dp->tls_mod_last; - } - if (blob_collection_empty(&dp->fake_unknown)) - { - if (!(item=blob_collection_add_blob(&dp->fake_unknown,NULL,256,0,0))) - return false; - memset(item->data,0,item->size); - } - if (blob_collection_empty(&dp->fake_quic)) - { - if (!(item=blob_collection_add_blob(&dp->fake_quic,NULL,620,0,0))) - return false; - memset(item->data,0,item->size); - item->data[0] = 0x40; - } - struct blob_collection_head **fake,*fakes_z64[] = {&dp->fake_wg, &dp->fake_dht, &dp->fake_discord, &dp->fake_stun, &dp->fake_unknown_udp,NULL}; - for(fake=fakes_z64;*fake;fake++) - { - if (blob_collection_empty(*fake)) - { - if (!(item=blob_collection_add_blob(*fake,NULL,64,0,0))) - return false; - memset(item->data,0,item->size); - } - } - return true; -} -struct desync_profile_list *dp_list_add(struct desync_profile_list_head *head) -{ - struct desync_profile_list *entry = calloc(1,sizeof(struct desync_profile_list)); - if (!entry) return NULL; - - dp_init(&entry->dp); - - // add to the tail - struct desync_profile_list *dpn,*dpl=LIST_FIRST(head); - if (dpl) - { - while ((dpn=LIST_NEXT(dpl,next))) dpl = dpn; - LIST_INSERT_AFTER(dpl, entry, next); - } - else - LIST_INSERT_HEAD(head, entry, next); - - return entry; -} -static void dp_clear_dynamic(struct desync_profile *dp) -{ - free(dp->fsplit_pattern); - - hostlist_collection_destroy(&dp->hl_collection); - hostlist_collection_destroy(&dp->hl_collection_exclude); - ipset_collection_destroy(&dp->ips_collection); - ipset_collection_destroy(&dp->ips_collection_exclude); - port_filters_destroy(&dp->pf_tcp); - port_filters_destroy(&dp->pf_udp); -#ifdef HAS_FILTER_SSID - strlist_destroy(&dp->filter_ssid); -#endif - HostFailPoolDestroy(&dp->hostlist_auto_fail_counters); - struct blob_collection_head **fake,*fakes[] = {&dp->fake_http, &dp->fake_tls, &dp->fake_unknown, &dp->fake_unknown_udp, &dp->fake_quic, &dp->fake_wg, &dp->fake_dht, &dp->fake_discord, &dp->fake_stun, NULL}; - for(fake=fakes;*fake;fake++) blob_collection_destroy(*fake); -} -void dp_clear(struct desync_profile *dp) -{ - dp_clear_dynamic(dp); - memset(dp,0,sizeof(*dp)); -} -void dp_entry_destroy(struct desync_profile_list *entry) -{ - dp_clear_dynamic(&entry->dp); - free(entry); -} -void dp_list_destroy(struct desync_profile_list_head *head) -{ - struct desync_profile_list *entry; - while ((entry = LIST_FIRST(head))) - { - LIST_REMOVE(entry, next); - dp_entry_destroy(entry); - } -} -bool dp_list_have_autohostlist(struct desync_profile_list_head *head) -{ - struct desync_profile_list *dpl; - LIST_FOREACH(dpl, head, next) - if (dpl->dp.hostlist_auto) - return true; - return false; -} -// check if we need empty outgoing ACK -bool dp_list_need_all_out(struct desync_profile_list_head *head) -{ - struct desync_profile_list *dpl; - LIST_FOREACH(dpl, head, next) - if (dpl->dp.dup_repeats || PROFILE_HAS_ORIG_MOD(&dpl->dp)) - return true; - return false; -} - - -#if !defined( __OpenBSD__) && !defined(__ANDROID__) -void cleanup_args(struct params_s *params) -{ - wordfree(¶ms->wexp); -} -#endif - -void cleanup_params(struct params_s *params) -{ -#if !defined( __OpenBSD__) && !defined(__ANDROID__) - cleanup_args(params); -#endif - - ConntrackPoolDestroy(¶ms->conntrack); - - dp_list_destroy(¶ms->desync_profiles); - - hostlist_files_destroy(¶ms->hostlists); - ipset_files_destroy(¶ms->ipsets); - ipcacheDestroy(¶ms->ipcache); -#ifdef __CYGWIN__ - strlist_destroy(¶ms->ssid_filter); - strlist_destroy(¶ms->nlm_filter); - strlist_destroy(¶ms->wf_raw_part); -#else - free(params->user); params->user=NULL; -#endif -} diff --git a/nfq/params.h b/nfq/params.h deleted file mode 100644 index 0d30055c..00000000 --- a/nfq/params.h +++ /dev/null @@ -1,275 +0,0 @@ -#pragma once - -#include "nfqws.h" -#include "pools.h" -#include "conntrack.h" -#include "desync.h" -#include "protocol.h" -#include "helpers.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#if !defined( __OpenBSD__) && !defined(__ANDROID__) -#include -#endif - -#define TLS_PARTIALS_ENABLE true - -#define RAW_SNDBUF (64*1024) // in bytes - -#define Q_MAXLEN 1024 // in packets - -#define BADSEQ_INCREMENT_DEFAULT -10000 -#define BADSEQ_ACK_INCREMENT_DEFAULT -66000 - -#define TS_INCREMENT_DEFAULT -600000 - -#define IPFRAG_UDP_DEFAULT 8 -#define IPFRAG_TCP_DEFAULT 32 - -#define UDPLEN_INCREMENT_DEFAULT 2 - -#define HOSTLIST_AUTO_FAIL_THRESHOLD_DEFAULT 3 -#define HOSTLIST_AUTO_FAIL_TIME_DEFAULT 60 -#define HOSTLIST_AUTO_RETRANS_THRESHOLD_DEFAULT 3 - -#define IPCACHE_LIFETIME 7200 - -#define AUTOTTL_DEFAULT_DESYNC_DELTA -1 -#define AUTOTTL_DEFAULT_DESYNC_MIN 3 -#define AUTOTTL_DEFAULT_DESYNC_MAX 20 -#define AUTOTTL_DEFAULT_ORIG_DELTA +5 -#define AUTOTTL_DEFAULT_ORIG_MIN 3 -#define AUTOTTL_DEFAULT_ORIG_MAX 64 -#define AUTOTTL_DEFAULT_DUP_DELTA -1 -#define AUTOTTL_DEFAULT_DUP_MIN 3 -#define AUTOTTL_DEFAULT_DUP_MAX 64 - - -#define MAX_SPLITS 64 - -#define FAKE_TLS_MOD_SAVE_MASK 0x0F -#define FAKE_TLS_MOD_SET 0x01 -#define FAKE_TLS_MOD_CUSTOM_FAKE 0x02 -#define FAKE_TLS_MOD_RND 0x10 -#define FAKE_TLS_MOD_DUP_SID 0x20 -#define FAKE_TLS_MOD_RND_SNI 0x40 -#define FAKE_TLS_MOD_SNI 0x80 -#define FAKE_TLS_MOD_PADENCAP 0x100 - -#define FAKE_MAX_TCP 1460 -#define FAKE_MAX_UDP 1472 - -#define MAX_GIDS 64 - -extern const char * tld[6]; - -enum log_target { LOG_TARGET_CONSOLE=0, LOG_TARGET_FILE, LOG_TARGET_SYSLOG, LOG_TARGET_ANDROID }; - -struct fake_tls_mod_cache -{ - size_t extlen_offset, padlen_offset; -}; -struct fake_tls_mod -{ - char sni[128]; - uint32_t mod; -}; -struct hostfakesplit_mod -{ - char host[128]; - size_t host_size; - int ordering; -}; -struct fakedsplit_mod -{ - int ordering; -}; -struct tcp_mod -{ - bool seq; -}; - -typedef enum {SS_NONE=0,SS_SYN,SS_SYNACK,SS_ACKSYN} t_synack_split; -typedef enum {IPID_SEQ=0,IPID_SEQ_GROUP,IPID_RND,IPID_ZERO,IPID_SAME} t_ip_id_mode; - -struct desync_profile -{ - int n; // number of the profile - - uint16_t wsize,wssize; - uint8_t wscale,wsscale; - char wssize_cutoff_mode; // n - packets, d - data packets, s - relative sequence - bool wssize_no_forced_cutoff; - unsigned int wssize_cutoff; - - t_synack_split synack_split; - - t_ip_id_mode ip_id_mode; - - bool hostcase, hostnospace, domcase, methodeol; - char hostspell[4]; - enum dpi_desync_mode desync_mode0,desync_mode,desync_mode2; - bool desync_retrans,desync_skip_nosni,desync_any_proto; - unsigned int desync_repeats,desync_ipfrag_pos_tcp,desync_ipfrag_pos_udp; - - // multisplit - struct proto_pos splits[MAX_SPLITS]; - int split_count; - struct proto_pos seqovl,hostfakesplit_midhost; - - char dup_start_mode, dup_cutoff_mode; // n - packets, d - data packets, s - relative sequence - bool dup_replace; - unsigned int dup_start, dup_cutoff; - unsigned int dup_repeats; - uint8_t dup_ttl, dup_ttl6; - uint32_t dup_fooling_mode; - uint32_t dup_ts_increment, dup_badseq_increment, dup_badseq_ack_increment; - autottl dup_autottl, dup_autottl6; - uint16_t dup_tcp_flags_set, dup_tcp_flags_unset; - t_ip_id_mode dup_ip_id_mode; - - char orig_mod_start_mode, orig_mod_cutoff_mode; // n - packets, d - data packets, s - relative sequence - unsigned int orig_mod_start, orig_mod_cutoff; - uint8_t orig_mod_ttl, orig_mod_ttl6; - autottl orig_autottl, orig_autottl6; - uint16_t orig_tcp_flags_set, orig_tcp_flags_unset; - - char desync_start_mode, desync_cutoff_mode; // n - packets, d - data packets, s - relative sequence - unsigned int desync_start, desync_cutoff; - uint8_t desync_ttl, desync_ttl6; - autottl desync_autottl, desync_autottl6; - uint32_t desync_fooling_mode; - uint32_t desync_ts_increment, desync_badseq_increment, desync_badseq_ack_increment; - uint16_t desync_tcp_flags_set, desync_tcp_flags_unset; - - struct blob_collection_head fake_http,fake_tls,fake_unknown,fake_unknown_udp,fake_quic,fake_wg,fake_dht,fake_discord,fake_stun; - uint8_t fake_syndata[FAKE_MAX_TCP],seqovl_pattern[FAKE_MAX_TCP],udplen_pattern[FAKE_MAX_UDP]; - uint8_t *fsplit_pattern; - size_t fake_syndata_size, fsplit_pattern_size; - - struct fake_tls_mod tls_mod_last; - struct blob_item *tls_fake_last; - - struct hostfakesplit_mod hfs_mod; - struct fakedsplit_mod fs_mod; - struct tcp_mod tcp_mod; - - int udplen_increment; - - bool filter_ipv4,filter_ipv6; - struct port_filters_head pf_tcp,pf_udp; - uint32_t filter_l7; // L7_PROTO_* bits - -#ifdef HAS_FILTER_SSID - // per profile ssid filter - // annot use global filter because it's not possible to bind multiple instances to a single queue - // it's possible to run multiple winws instances on the same windivert filter, but it's not the case for linux - struct str_list_head filter_ssid; -#endif - - // list of pointers to ipsets - struct ipset_collection_head ips_collection, ips_collection_exclude; - - // list of pointers to hostlist files - struct hostlist_collection_head hl_collection, hl_collection_exclude; - // pointer to autohostlist. NULL if no autohostlist for the profile. - struct hostlist_file *hostlist_auto; - int hostlist_auto_fail_threshold, hostlist_auto_fail_time, hostlist_auto_retrans_threshold; - - hostfail_pool *hostlist_auto_fail_counters; -}; - -#define PROFILE_IPSETS_ABSENT(dp) (!LIST_FIRST(&(dp)->ips_collection) && !LIST_FIRST(&(dp)->ips_collection_exclude)) -#define PROFILE_IPSETS_EMPTY(dp) (ipset_collection_is_empty(&(dp)->ips_collection) && ipset_collection_is_empty(&(dp)->ips_collection_exclude)) -#define PROFILE_HOSTLISTS_EMPTY(dp) (hostlist_collection_is_empty(&(dp)->hl_collection) && hostlist_collection_is_empty(&(dp)->hl_collection_exclude)) -#define PROFILE_HAS_ORIG_MOD(dp) ((dp)->orig_mod_ttl || (dp)->orig_mod_ttl6 || (dp)->orig_tcp_flags_set || (dp)->orig_tcp_flags_unset) - -struct desync_profile_list { - struct desync_profile dp; - LIST_ENTRY(desync_profile_list) next; -}; -LIST_HEAD(desync_profile_list_head, desync_profile_list); -struct desync_profile_list *dp_list_add(struct desync_profile_list_head *head); -void dp_entry_destroy(struct desync_profile_list *entry); -void dp_list_destroy(struct desync_profile_list_head *head); -bool dp_list_have_autohostlist(struct desync_profile_list_head *head); -bool dp_list_need_all_out(struct desync_profile_list_head *head); -void dp_init(struct desync_profile *dp); -bool dp_fake_defaults(struct desync_profile *dp); -void dp_clear(struct desync_profile *dp); - -struct params_s -{ -#if !defined( __OpenBSD__) && !defined(__ANDROID__) - wordexp_t wexp; // for file based config -#endif - - enum log_target debug_target; - char debug_logfile[PATH_MAX]; - bool debug; - - bool daemon; - -#ifdef __linux__ - int qnum; -#elif defined(BSD) - uint16_t port; // divert port -#endif - char bind_fix4,bind_fix6; - uint32_t desync_fwmark; // unused in BSD - - struct desync_profile_list_head desync_profiles; - -#ifdef __CYGWIN__ - struct str_list_head ssid_filter,nlm_filter; - struct str_list_head wf_raw_part; -#else - bool droproot; - char *user; - uid_t uid; - gid_t gid[MAX_GIDS]; - int gid_count; -#endif - char pidfile[PATH_MAX]; - - char hostlist_auto_debuglog[PATH_MAX]; - - // hostlist files with data for all profiles - struct hostlist_files_head hostlists; - // ipset files with data for all profiles - struct ipset_files_head ipsets; - - unsigned int ctrack_t_syn, ctrack_t_est, ctrack_t_fin, ctrack_t_udp; - t_conntrack conntrack; - bool ctrack_disable; - - bool autottl_present; -#ifdef HAS_FILTER_SSID - bool filter_ssid_present; -#endif - - bool cache_hostname; - unsigned int ipcache_lifetime; - ip_cache ipcache; -}; - -extern struct params_s params; -extern const char *progname; -#if !defined( __OpenBSD__) && !defined(__ANDROID__) -void cleanup_args(struct params_s *params); -#endif -void cleanup_params(struct params_s *params); - -int DLOG(const char *format, ...); -int DLOG_ERR(const char *format, ...); -int DLOG_PERROR(const char *s); -int DLOG_CONDUP(const char *format, ...); -int HOSTLIST_DEBUGLOG_APPEND(const char *format, ...); -void hexdump_limited_dlog(const uint8_t *data, size_t size, size_t limit); diff --git a/nfq/pools.c b/nfq/pools.c deleted file mode 100644 index a704ff48..00000000 --- a/nfq/pools.c +++ /dev/null @@ -1,839 +0,0 @@ -#define _GNU_SOURCE -#include "pools.h" -#include -#include -#include -#include - -#define DESTROY_STR_POOL(etype, ppool) \ - etype *elem, *tmp; \ - HASH_ITER(hh, *ppool, elem, tmp) { \ - free(elem->str); \ - HASH_DEL(*ppool, elem); \ - free(elem); \ - } - -#define ADD_STR_POOL(etype, ppool, keystr, keystr_len) \ - etype *elem; \ - if (!(elem = (etype*)malloc(sizeof(etype)))) \ - return false; \ - if (!(elem->str = malloc(keystr_len + 1))) \ - { \ - free(elem); \ - return false; \ - } \ - memcpy(elem->str, keystr, keystr_len); \ - elem->str[keystr_len] = 0; \ - oom = false; \ - HASH_ADD_KEYPTR(hh, *ppool, elem->str, keystr_len, elem); \ - if (oom) \ - { \ - free(elem->str); \ - free(elem); \ - return false; \ - } -#define ADD_HOSTLIST_POOL(etype, ppool, keystr, keystr_len, flg) \ - etype *elem_find; \ - HASH_FIND(hh, *ppool, keystr, keystr_len, elem_find); \ - if (!elem_find) { \ - ADD_STR_POOL(etype,ppool,keystr,keystr_len); \ - elem->flags = flg; \ - } - -#undef uthash_nonfatal_oom -#define uthash_nonfatal_oom(elt) ut_oom_recover(elt) -static bool oom = false; -static void ut_oom_recover(void *elem) -{ - oom = true; -} - -// for not zero terminated strings -bool HostlistPoolAddStrLen(hostlist_pool **pp, const char *s, size_t slen, uint32_t flags) -{ - ADD_HOSTLIST_POOL(hostlist_pool, pp, s, slen, flags) - return true; -} -// for zero terminated strings -bool HostlistPoolAddStr(hostlist_pool **pp, const char *s, uint32_t flags) -{ - return HostlistPoolAddStrLen(pp, s, strlen(s), flags); -} - -hostlist_pool *HostlistPoolGetStr(hostlist_pool *p, const char *s) -{ - hostlist_pool *elem; - HASH_FIND_STR(p, s, elem); - return elem; -} -bool HostlistPoolCheckStr(hostlist_pool *p, const char *s) -{ - return !!HostlistPoolGetStr(p,s); -} - -void HostlistPoolDestroy(hostlist_pool **pp) -{ - DESTROY_STR_POOL(hostlist_pool, pp) -} - - - -void HostFailPoolDestroy(hostfail_pool **pp) -{ - DESTROY_STR_POOL(hostfail_pool, pp) -} -hostfail_pool * HostFailPoolAdd(hostfail_pool **pp,const char *s,int fail_time) -{ - size_t slen = strlen(s); - ADD_STR_POOL(hostfail_pool, pp, s, slen) - elem->expire = time(NULL) + fail_time; - elem->counter = 0; - return elem; -} -hostfail_pool *HostFailPoolFind(hostfail_pool *p,const char *s) -{ - hostfail_pool *elem; - HASH_FIND_STR(p, s, elem); - return elem; -} -void HostFailPoolDel(hostfail_pool **p, hostfail_pool *elem) -{ - free(elem->str); - HASH_DEL(*p, elem); - free(elem); -} -void HostFailPoolPurge(hostfail_pool **pp) -{ - hostfail_pool *elem, *tmp; - time_t now = time(NULL); - HASH_ITER(hh, *pp, elem, tmp) - { - if (now >= elem->expire) - HostFailPoolDel(pp, elem); - } -} -static time_t host_fail_purge_prev=0; -void HostFailPoolPurgeRateLimited(hostfail_pool **pp) -{ - time_t now = time(NULL); - // do not purge too often to save resources - if (host_fail_purge_prev != now) - { - HostFailPoolPurge(pp); - host_fail_purge_prev = now; - } -} -void HostFailPoolDump(hostfail_pool *p) -{ - hostfail_pool *elem, *tmp; - time_t now = time(NULL); - HASH_ITER(hh, p, elem, tmp) - printf("host=%s counter=%d time_left=%lld\n",elem->str,elem->counter,(long long int)elem->expire-now); -} - - -bool strlist_add(struct str_list_head *head, const char *filename) -{ - struct str_list *entry = malloc(sizeof(struct str_list)); - if (!entry) return false; - entry->str = strdup(filename); - if (!entry->str) - { - free(entry); - return false; - } - LIST_INSERT_HEAD(head, entry, next); - return true; -} -static void strlist_entry_destroy(struct str_list *entry) -{ - free(entry->str); - free(entry); -} -void strlist_destroy(struct str_list_head *head) -{ - struct str_list *entry; - while ((entry = LIST_FIRST(head))) - { - LIST_REMOVE(entry, next); - strlist_entry_destroy(entry); - } -} -bool strlist_search(const struct str_list_head *head, const char *str) -{ - struct str_list *entry; - if (str) - { - LIST_FOREACH(entry, head, next) - { - if (!strcmp(entry->str, str)) - return true; - } - } - return false; -} - - - -struct hostlist_file *hostlist_files_add(struct hostlist_files_head *head, const char *filename) -{ - struct hostlist_file *entry = malloc(sizeof(struct hostlist_file)); - if (entry) - { - if (filename) - { - if (!(entry->filename = strdup(filename))) - { - free(entry); - return false; - } - } - else - entry->filename = NULL; - FILE_MOD_RESET(&entry->mod_sig); - entry->hostlist = NULL; - LIST_INSERT_HEAD(head, entry, next); - } - return entry; -} -static void hostlist_files_entry_destroy(struct hostlist_file *entry) -{ - free(entry->filename); - HostlistPoolDestroy(&entry->hostlist); - free(entry); -} -void hostlist_files_destroy(struct hostlist_files_head *head) -{ - struct hostlist_file *entry; - while ((entry = LIST_FIRST(head))) - { - LIST_REMOVE(entry, next); - hostlist_files_entry_destroy(entry); - } -} -struct hostlist_file *hostlist_files_search(struct hostlist_files_head *head, const char *filename) -{ - struct hostlist_file *hfile; - - LIST_FOREACH(hfile, head, next) - { - if (hfile->filename && !strcmp(hfile->filename,filename)) - return hfile; - } - return NULL; -} -void hostlist_files_reset_modtime(struct hostlist_files_head *list) -{ - struct hostlist_file *hfile; - - LIST_FOREACH(hfile, list, next) - FILE_MOD_RESET(&hfile->mod_sig); -} - -struct hostlist_item *hostlist_collection_add(struct hostlist_collection_head *head, struct hostlist_file *hfile) -{ - struct hostlist_item *entry = malloc(sizeof(struct hostlist_item)); - if (entry) - { - entry->hfile = hfile; - LIST_INSERT_HEAD(head, entry, next); - } - return entry; -} -void hostlist_collection_destroy(struct hostlist_collection_head *head) -{ - struct hostlist_item *entry; - while ((entry = LIST_FIRST(head))) - { - LIST_REMOVE(entry, next); - free(entry); - } -} -struct hostlist_item *hostlist_collection_search(struct hostlist_collection_head *head, const char *filename) -{ - struct hostlist_item *item; - - LIST_FOREACH(item, head, next) - { - if (item->hfile->filename && !strcmp(item->hfile->filename,filename)) - return item; - } - return NULL; -} -bool hostlist_collection_is_empty(const struct hostlist_collection_head *head) -{ - const struct hostlist_item *item; - - LIST_FOREACH(item, head, next) - { - if (item->hfile->hostlist) - return false; - } - return true; -} - - -static int kavl_bit_cmp(const struct kavl_bit_elem *p, const struct kavl_bit_elem *q) -{ - unsigned int bitlen = q->bitlen < p->bitlen ? q->bitlen : p->bitlen; - unsigned int df = bitlen & 7, bytes = bitlen >> 3; - int cmp = memcmp(p->data, q->data, bytes); - - if (cmp || !df) return cmp; - - uint8_t c1 = p->data[bytes] >> (8 - df); - uint8_t c2 = q->data[bytes] >> (8 - df); - return c1data); - free(e); - } -} -void kavl_bit_delete(struct kavl_bit_elem **hdr, const void *data, unsigned int bitlen) -{ - struct kavl_bit_elem temp = { - .bitlen = bitlen, .data = (uint8_t*)data - }; - kavl_bit_destroy_elem(kavl_erase(kavl_bit, hdr, &temp, 0)); -} -void kavl_bit_destroy(struct kavl_bit_elem **hdr) -{ - while (*hdr) - { - struct kavl_bit_elem *e = kavl_erase_first(kavl_bit, hdr); - if (!e) break; - kavl_bit_destroy_elem(e); - } - free(*hdr); -} -struct kavl_bit_elem *kavl_bit_add(struct kavl_bit_elem **hdr, void *data, unsigned int bitlen, size_t struct_size) -{ - if (!struct_size) struct_size=sizeof(struct kavl_bit_elem); - - struct kavl_bit_elem *v, *e = calloc(1, struct_size); - if (!e) return 0; - - e->bitlen = bitlen; - e->data = data; - - v = kavl_insert(kavl_bit, hdr, e, 0); - while (e != v && e->bitlen < v->bitlen) - { - kavl_bit_delete(hdr, v->data, v->bitlen); - v = kavl_insert(kavl_bit, hdr, e, 0); - } - if (e != v) kavl_bit_destroy_elem(e); - return v; -} -struct kavl_bit_elem *kavl_bit_get(const struct kavl_bit_elem *hdr, const void *data, unsigned int bitlen) -{ - struct kavl_bit_elem temp = { - .bitlen = bitlen, .data = (uint8_t*)data - }; - return kavl_find(kavl_bit, hdr, &temp, 0); -} - -static bool ipset_kavl_add(struct kavl_bit_elem **ipset, const void *a, uint8_t preflen) -{ - uint8_t bytelen = (preflen+7)>>3; - uint8_t *abuf = malloc(bytelen); - if (!abuf) return false; - memcpy(abuf,a,bytelen); - if (!kavl_bit_add(ipset,abuf,preflen,0)) - { - free(abuf); - return false; - } - return true; -} - - -bool ipset4Check(const struct kavl_bit_elem *ipset, const struct in_addr *a, uint8_t preflen) -{ - return !!kavl_bit_get(ipset,a,preflen); -} -bool ipset4Add(struct kavl_bit_elem **ipset, const struct in_addr *a, uint8_t preflen) -{ - if (preflen>32) return false; - return ipset_kavl_add(ipset,a,preflen); -} -void ipset4Print(struct kavl_bit_elem *ipset) -{ - if (!ipset) return; - - struct cidr4 c; - const struct kavl_bit_elem *elem; - kavl_itr_t(kavl_bit) itr; - kavl_itr_first(kavl_bit, ipset, &itr); - do - { - elem = kavl_at(&itr); - c.preflen = elem->bitlen; - expand_bits(&c.addr, elem->data, elem->bitlen, sizeof(c.addr)); - print_cidr4(&c); - printf("\n"); - } - while (kavl_itr_next(kavl_bit, &itr)); -} - -bool ipset6Check(const struct kavl_bit_elem *ipset, const struct in6_addr *a, uint8_t preflen) -{ - return !!kavl_bit_get(ipset,a,preflen); -} -bool ipset6Add(struct kavl_bit_elem **ipset, const struct in6_addr *a, uint8_t preflen) -{ - if (preflen>128) return false; - return ipset_kavl_add(ipset,a,preflen); -} -void ipset6Print(struct kavl_bit_elem *ipset) -{ - if (!ipset) return; - - struct cidr6 c; - const struct kavl_bit_elem *elem; - kavl_itr_t(kavl_bit) itr; - kavl_itr_first(kavl_bit, ipset, &itr); - do - { - elem = kavl_at(&itr); - c.preflen = elem->bitlen; - expand_bits(&c.addr, elem->data, elem->bitlen, sizeof(c.addr)); - print_cidr6(&c); - printf("\n"); - } - while (kavl_itr_next(kavl_bit, &itr)); -} - -void ipsetDestroy(ipset *ipset) -{ - kavl_bit_destroy(&ipset->ips4); - kavl_bit_destroy(&ipset->ips6); -} -void ipsetPrint(ipset *ipset) -{ - ipset4Print(ipset->ips4); - ipset6Print(ipset->ips6); -} - - -struct ipset_file *ipset_files_add(struct ipset_files_head *head, const char *filename) -{ - struct ipset_file *entry = malloc(sizeof(struct ipset_file)); - if (entry) - { - if (filename) - { - if (!(entry->filename = strdup(filename))) - { - free(entry); - return false; - } - } - else - entry->filename = NULL; - FILE_MOD_RESET(&entry->mod_sig); - memset(&entry->ipset,0,sizeof(entry->ipset)); - LIST_INSERT_HEAD(head, entry, next); - } - return entry; -} -static void ipset_files_entry_destroy(struct ipset_file *entry) -{ - free(entry->filename); - ipsetDestroy(&entry->ipset); - free(entry); -} -void ipset_files_destroy(struct ipset_files_head *head) -{ - struct ipset_file *entry; - while ((entry = LIST_FIRST(head))) - { - LIST_REMOVE(entry, next); - ipset_files_entry_destroy(entry); - } -} -struct ipset_file *ipset_files_search(struct ipset_files_head *head, const char *filename) -{ - struct ipset_file *hfile; - - LIST_FOREACH(hfile, head, next) - { - if (hfile->filename && !strcmp(hfile->filename,filename)) - return hfile; - } - return NULL; -} -void ipset_files_reset_modtime(struct ipset_files_head *list) -{ - struct ipset_file *hfile; - - LIST_FOREACH(hfile, list, next) - FILE_MOD_RESET(&hfile->mod_sig); -} - -struct ipset_item *ipset_collection_add(struct ipset_collection_head *head, struct ipset_file *hfile) -{ - struct ipset_item *entry = malloc(sizeof(struct ipset_item)); - if (entry) - { - entry->hfile = hfile; - LIST_INSERT_HEAD(head, entry, next); - } - return entry; -} -void ipset_collection_destroy(struct ipset_collection_head *head) -{ - struct ipset_item *entry; - while ((entry = LIST_FIRST(head))) - { - LIST_REMOVE(entry, next); - free(entry); - } -} -struct ipset_item *ipset_collection_search(struct ipset_collection_head *head, const char *filename) -{ - struct ipset_item *item; - - LIST_FOREACH(item, head, next) - { - if (item->hfile->filename && !strcmp(item->hfile->filename,filename)) - return item; - } - return NULL; -} -bool ipset_collection_is_empty(const struct ipset_collection_head *head) -{ - const struct ipset_item *item; - - LIST_FOREACH(item, head, next) - { - if (!IPSET_EMPTY(&item->hfile->ipset)) - return false; - } - return true; -} - - -bool port_filter_add(struct port_filters_head *head, const port_filter *pf) -{ - struct port_filter_item *entry = malloc(sizeof(struct port_filter_item)); - if (entry) - { - entry->pf = *pf; - LIST_INSERT_HEAD(head, entry, next); - } - return entry; -} -void port_filters_destroy(struct port_filters_head *head) -{ - struct port_filter_item *entry; - while ((entry = LIST_FIRST(head))) - { - LIST_REMOVE(entry, next); - free(entry); - } -} -bool port_filters_in_range(const struct port_filters_head *head, uint16_t port) -{ - const struct port_filter_item *item; - - if (!LIST_FIRST(head)) return true; - LIST_FOREACH(item, head, next) - { - if (pf_in_range(port, &item->pf)) - return true; - } - return false; -} -bool port_filters_deny_if_empty(struct port_filters_head *head) -{ - port_filter pf; - if (LIST_FIRST(head)) return true; - return pf_parse("0",&pf) && port_filter_add(head,&pf); -} - - - -struct blob_item *blob_collection_add(struct blob_collection_head *head) -{ - struct blob_item *entry = calloc(1,sizeof(struct blob_item)); - if (entry) - { - // insert to the end - struct blob_item *itemc,*iteml=LIST_FIRST(head); - if (iteml) - { - while ((itemc=LIST_NEXT(iteml,next))) iteml = itemc; - LIST_INSERT_AFTER(iteml, entry, next); - } - else - LIST_INSERT_HEAD(head, entry, next); - } - return entry; -} -struct blob_item *blob_collection_add_blob(struct blob_collection_head *head, const void *data, size_t size, size_t size_reserve, size_t offset) -{ - if (offset>=size) return NULL; - struct blob_item *entry = calloc(1,sizeof(struct blob_item)); - if (!entry) return NULL; - if (!(entry->data = malloc(size+size_reserve))) - { - free(entry); - return NULL; - } - if (data) memcpy(entry->data,data,size); - entry->size = size; - entry->size_buf = size+size_reserve; - entry->offset = offset; - - // insert to the end - struct blob_item *itemc,*iteml=LIST_FIRST(head); - if (iteml) - { - while ((itemc=LIST_NEXT(iteml,next))) iteml = itemc; - LIST_INSERT_AFTER(iteml, entry, next); - } - else - LIST_INSERT_HEAD(head, entry, next); - - return entry; -} - -void blob_collection_destroy(struct blob_collection_head *head) -{ - struct blob_item *entry; - while ((entry = LIST_FIRST(head))) - { - LIST_REMOVE(entry, next); - free(entry->extra); - free(entry->extra2); - free(entry->data); - free(entry); - } -} -bool blob_collection_empty(const struct blob_collection_head *head) -{ - return !LIST_FIRST(head); -} - - - -static void ipcache_item_touch(ip_cache_item *item) -{ - time(&item->last); -} -static void ipcache_item_init(ip_cache_item *item) -{ - ipcache_item_touch(item); - item->hostname = NULL; - item->hostname_is_ip = false; - item->hops = 0; -} -static void ipcache_item_destroy(ip_cache_item *item) -{ - free(item->hostname); -} - -static void ipcache4Destroy(ip_cache4 **ipcache) -{ - ip_cache4 *elem, *tmp; - HASH_ITER(hh, *ipcache, elem, tmp) - { - HASH_DEL(*ipcache, elem); - ipcache_item_destroy(&elem->data); - free(elem); - } -} -static void ipcache4Key(ip4if *key, const struct in_addr *a, const char *iface) -{ - memset(key,0,sizeof(*key)); // make sure everything is zero - key->addr = *a; - if (iface) snprintf(key->iface,sizeof(key->iface),"%s",iface); -} -static ip_cache4 *ipcache4Find(ip_cache4 *ipcache, const struct in_addr *a, const char *iface) -{ - ip_cache4 *entry; - struct ip4if key; - - ipcache4Key(&key,a,iface); - HASH_FIND(hh, ipcache, &key, sizeof(key), entry); - return entry; -} -static ip_cache4 *ipcache4Add(ip_cache4 **ipcache, const struct in_addr *a, const char *iface) -{ - // avoid dups - ip_cache4 *entry = ipcache4Find(*ipcache,a,iface); - if (entry) return entry; // already included - - entry = malloc(sizeof(ip_cache4)); - if (!entry) return NULL; - ipcache4Key(&entry->key,a,iface); - - oom = false; - HASH_ADD(hh, *ipcache, key, sizeof(entry->key), entry); - if (oom) { free(entry); return NULL; } - - ipcache_item_init(&entry->data); - - return entry; -} -static void ipcache4Print(ip_cache4 *ipcache) -{ - char s_ip[16]; - time_t now; - ip_cache4 *ipc, *tmp; - - time(&now); - HASH_ITER(hh, ipcache , ipc, tmp) - { - *s_ip=0; - inet_ntop(AF_INET, &ipc->key.addr, s_ip, sizeof(s_ip)); - printf("%s iface=%s : hops %u hostname=%s hostname_is_ip=%u now=last+%llu\n", s_ip, ipc->key.iface, ipc->data.hops, ipc->data.hostname ? ipc->data.hostname : "", ipc->data.hostname_is_ip, (unsigned long long)(now-ipc->data.last)); - } -} - -static void ipcache6Destroy(ip_cache6 **ipcache) -{ - ip_cache6 *elem, *tmp; - HASH_ITER(hh, *ipcache, elem, tmp) - { - HASH_DEL(*ipcache, elem); - ipcache_item_destroy(&elem->data); - free(elem); - } -} -static void ipcache6Key(ip6if *key, const struct in6_addr *a, const char *iface) -{ - memset(key,0,sizeof(*key)); // make sure everything is zero - key->addr = *a; - if (iface) snprintf(key->iface,sizeof(key->iface),"%s",iface); -} -static ip_cache6 *ipcache6Find(ip_cache6 *ipcache, const struct in6_addr *a, const char *iface) -{ - ip_cache6 *entry; - ip6if key; - - ipcache6Key(&key,a,iface); - HASH_FIND(hh, ipcache, &key, sizeof(key), entry); - return entry; -} -static ip_cache6 *ipcache6Add(ip_cache6 **ipcache, const struct in6_addr *a, const char *iface) -{ - // avoid dups - ip_cache6 *entry = ipcache6Find(*ipcache,a,iface); - if (entry) return entry; // already included - - entry = malloc(sizeof(ip_cache6)); - if (!entry) return NULL; - ipcache6Key(&entry->key,a,iface); - - oom = false; - HASH_ADD(hh, *ipcache, key, sizeof(entry->key), entry); - if (oom) { free(entry); return NULL; } - - ipcache_item_init(&entry->data); - - return entry; -} -static void ipcache6Print(ip_cache6 *ipcache) -{ - char s_ip[40]; - time_t now; - ip_cache6 *ipc, *tmp; - - time(&now); - HASH_ITER(hh, ipcache , ipc, tmp) - { - *s_ip=0; - inet_ntop(AF_INET6, &ipc->key.addr, s_ip, sizeof(s_ip)); - printf("%s iface=%s : hops %u hostname=%s hostname_is_ip=%u now=last+%llu\n", s_ip, ipc->key.iface, ipc->data.hops, ipc->data.hostname ? ipc->data.hostname : "", ipc->data.hostname_is_ip, (unsigned long long)(now-ipc->data.last)); - } -} - -void ipcacheDestroy(ip_cache *ipcache) -{ - ipcache4Destroy(&ipcache->ipcache4); - ipcache6Destroy(&ipcache->ipcache6); -} -void ipcachePrint(ip_cache *ipcache) -{ - ipcache4Print(ipcache->ipcache4); - ipcache6Print(ipcache->ipcache6); -} - -ip_cache_item *ipcacheTouch(ip_cache *ipcache, const struct in_addr *a4, const struct in6_addr *a6, const char *iface) -{ - ip_cache4 *ipcache4; - ip_cache6 *ipcache6; - if (a4) - { - if ((ipcache4 = ipcache4Add(&ipcache->ipcache4,a4,iface))) - { - ipcache_item_touch(&ipcache4->data); - return &ipcache4->data; - } - } - else if (a6) - { - if ((ipcache6 = ipcache6Add(&ipcache->ipcache6,a6,iface))) - { - ipcache_item_touch(&ipcache6->data); - return &ipcache6->data; - } - } - return NULL; -} - -static void ipcache4_purge(ip_cache4 **ipcache, time_t lifetime) -{ - ip_cache4 *elem, *tmp; - time_t now = time(NULL); - HASH_ITER(hh, *ipcache, elem, tmp) - { - if (now >= (elem->data.last + lifetime)) - { - HASH_DEL(*ipcache, elem); - ipcache_item_destroy(&elem->data); - free(elem); - } - } -} -static void ipcache6_purge(ip_cache6 **ipcache, time_t lifetime) -{ - ip_cache6 *elem, *tmp; - time_t now = time(NULL); - HASH_ITER(hh, *ipcache, elem, tmp) - { - if (now >= (elem->data.last + lifetime)) - { - HASH_DEL(*ipcache, elem); - ipcache_item_destroy(&elem->data); - free(elem); - } - } -} -static void ipcache_purge(ip_cache *ipcache, time_t lifetime) -{ - if (lifetime) // 0 = no expire - { - ipcache4_purge(&ipcache->ipcache4, lifetime); - ipcache6_purge(&ipcache->ipcache6, lifetime); - } -} -static time_t ipcache_purge_prev=0; -void ipcachePurgeRateLimited(ip_cache *ipcache, time_t lifetime) -{ - time_t now = time(NULL); - // do not purge too often to save resources - if (ipcache_purge_prev != now) - { - ipcache_purge(ipcache, lifetime); - ipcache_purge_prev = now; - } -} - diff --git a/nfq/pools.h b/nfq/pools.h deleted file mode 100644 index e4a6e45c..00000000 --- a/nfq/pools.h +++ /dev/null @@ -1,210 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include - -#include "helpers.h" - -//#define HASH_BLOOM 20 -#define HASH_NONFATAL_OOM 1 -#define HASH_FUNCTION HASH_BER -#include "uthash.h" - -#include "kavl.h" - -#define HOSTLIST_POOL_FLAG_STRICT_MATCH 1 - -typedef struct hostlist_pool { - char *str; /* key */ - uint32_t flags; /* custom data */ - UT_hash_handle hh; /* makes this structure hashable */ -} hostlist_pool; - -void HostlistPoolDestroy(hostlist_pool **pp); -bool HostlistPoolAddStr(hostlist_pool **pp, const char *s, uint32_t flags); -bool HostlistPoolAddStrLen(hostlist_pool **pp, const char *s, size_t slen, uint32_t flags); -hostlist_pool *HostlistPoolGetStr(hostlist_pool *p, const char *s); - -struct str_list { - char *str; - LIST_ENTRY(str_list) next; -}; -LIST_HEAD(str_list_head, str_list); - -bool strlist_add(struct str_list_head *head, const char *filename); -void strlist_destroy(struct str_list_head *head); -bool strlist_search(const struct str_list_head *head, const char *str); - - -typedef struct hostfail_pool { - char *str; /* key */ - int counter; /* value */ - time_t expire; /* when to expire record (unixtime) */ - UT_hash_handle hh; /* makes this structure hashable */ -} hostfail_pool; - -void HostFailPoolDestroy(hostfail_pool **pp); -hostfail_pool *HostFailPoolAdd(hostfail_pool **pp,const char *s,int fail_time); -hostfail_pool *HostFailPoolFind(hostfail_pool *p,const char *s); -void HostFailPoolDel(hostfail_pool **pp, hostfail_pool *elem); -void HostFailPoolPurge(hostfail_pool **pp); -void HostFailPoolPurgeRateLimited(hostfail_pool **pp); -void HostFailPoolDump(hostfail_pool *p); - - -struct hostlist_file { - char *filename; - file_mod_sig mod_sig; - hostlist_pool *hostlist; - LIST_ENTRY(hostlist_file) next; -}; -LIST_HEAD(hostlist_files_head, hostlist_file); - -struct hostlist_file *hostlist_files_add(struct hostlist_files_head *head, const char *filename); -void hostlist_files_destroy(struct hostlist_files_head *head); -struct hostlist_file *hostlist_files_search(struct hostlist_files_head *head, const char *filename); -void hostlist_files_reset_modtime(struct hostlist_files_head *list); - -struct hostlist_item { - struct hostlist_file *hfile; - LIST_ENTRY(hostlist_item) next; -}; -LIST_HEAD(hostlist_collection_head, hostlist_item); -struct hostlist_item *hostlist_collection_add(struct hostlist_collection_head *head, struct hostlist_file *hfile); -void hostlist_collection_destroy(struct hostlist_collection_head *head); -struct hostlist_item *hostlist_collection_search(struct hostlist_collection_head *head, const char *filename); -bool hostlist_collection_is_empty(const struct hostlist_collection_head *head); - - -struct kavl_bit_elem -{ - unsigned int bitlen; - uint8_t *data; - KAVL_HEAD(struct kavl_bit_elem) head; -}; - -struct kavl_bit_elem *kavl_bit_get(const struct kavl_bit_elem *hdr, const void *data, unsigned int bitlen); -struct kavl_bit_elem *kavl_bit_add(struct kavl_bit_elem **hdr, void *data, unsigned int bitlen, size_t struct_size); -void kavl_bit_delete(struct kavl_bit_elem **hdr, const void *data, unsigned int bitlen); -void kavl_bit_destroy(struct kavl_bit_elem **hdr); - -// combined ipset ipv4 and ipv6 -typedef struct ipset { - struct kavl_bit_elem *ips4,*ips6; -} ipset; - -#define IPSET_EMPTY(ips) (!(ips)->ips4 && !(ips)->ips6) - -bool ipset4Add(struct kavl_bit_elem **ipset, const struct in_addr *a, uint8_t preflen); -static inline bool ipset4AddCidr(struct kavl_bit_elem **ipset, const struct cidr4 *cidr) -{ - return ipset4Add(ipset,&cidr->addr,cidr->preflen); -} -bool ipset4Check(const struct kavl_bit_elem *ipset, const struct in_addr *a, uint8_t preflen); -void ipset4Print(struct kavl_bit_elem *ipset); - -bool ipset6Add(struct kavl_bit_elem **ipset, const struct in6_addr *a, uint8_t preflen); -static inline bool ipset6AddCidr(struct kavl_bit_elem **ipset, const struct cidr6 *cidr) -{ - return ipset6Add(ipset,&cidr->addr,cidr->preflen); -} -bool ipset6Check(const struct kavl_bit_elem *ipset, const struct in6_addr *a, uint8_t preflen); -void ipset6Print(struct kavl_bit_elem *ipset); - -void ipsetDestroy(ipset *ipset); -void ipsetPrint(ipset *ipset); - - -struct ipset_file { - char *filename; - file_mod_sig mod_sig; - ipset ipset; - LIST_ENTRY(ipset_file) next; -}; -LIST_HEAD(ipset_files_head, ipset_file); - -struct ipset_file *ipset_files_add(struct ipset_files_head *head, const char *filename); -void ipset_files_destroy(struct ipset_files_head *head); -struct ipset_file *ipset_files_search(struct ipset_files_head *head, const char *filename); -void ipset_files_reset_modtime(struct ipset_files_head *list); - -struct ipset_item { - struct ipset_file *hfile; - LIST_ENTRY(ipset_item) next; -}; -LIST_HEAD(ipset_collection_head, ipset_item); -struct ipset_item * ipset_collection_add(struct ipset_collection_head *head, struct ipset_file *hfile); -void ipset_collection_destroy(struct ipset_collection_head *head); -struct ipset_item *ipset_collection_search(struct ipset_collection_head *head, const char *filename); -bool ipset_collection_is_empty(const struct ipset_collection_head *head); - - -struct port_filter_item { - port_filter pf; - LIST_ENTRY(port_filter_item) next; -}; -LIST_HEAD(port_filters_head, port_filter_item); -bool port_filter_add(struct port_filters_head *head, const port_filter *pf); -void port_filters_destroy(struct port_filters_head *head); -bool port_filters_in_range(const struct port_filters_head *head, uint16_t port); -bool port_filters_deny_if_empty(struct port_filters_head *head); - - -struct blob_item { - uint8_t *data; // main data blob - size_t size; // main data blob size - size_t size_buf;// main data blob allocated size - size_t offset; // optional offset to useful data - void *extra; // any data without size - void *extra2; // any data without size - LIST_ENTRY(blob_item) next; -}; -LIST_HEAD(blob_collection_head, blob_item); -struct blob_item *blob_collection_add(struct blob_collection_head *head); -struct blob_item *blob_collection_add_blob(struct blob_collection_head *head, const void *data, size_t size, size_t size_reserve, size_t offset); -void blob_collection_destroy(struct blob_collection_head *head); -bool blob_collection_empty(const struct blob_collection_head *head); - - -typedef struct ip4if -{ - char iface[IFNAMSIZ]; - struct in_addr addr; -} ip4if; -typedef struct ip6if -{ - char iface[IFNAMSIZ]; - struct in6_addr addr; -} ip6if; -typedef struct ip_cache_item -{ - time_t last; - char *hostname; - bool hostname_is_ip; - uint8_t hops; -} ip_cache_item; -typedef struct ip_cache4 -{ - ip4if key; - ip_cache_item data; - UT_hash_handle hh; /* makes this structure hashable */ -} ip_cache4; -typedef struct ip_cache6 -{ - ip6if key; - ip_cache_item data; - UT_hash_handle hh; /* makes this structure hashable */ -} ip_cache6; -typedef struct ip_cache -{ - ip_cache4 *ipcache4; - ip_cache6 *ipcache6; -} ip_cache; - -ip_cache_item *ipcacheTouch(ip_cache *ipcache, const struct in_addr *a4, const struct in6_addr *a6, const char *iface); -void ipcachePurgeRateLimited(ip_cache *ipcache, time_t lifetime); -void ipcacheDestroy(ip_cache *ipcache); -void ipcachePrint(ip_cache *ipcache); diff --git a/nfq/protocol.c b/nfq/protocol.c deleted file mode 100644 index 7708de27..00000000 --- a/nfq/protocol.c +++ /dev/null @@ -1,1033 +0,0 @@ -#define _GNU_SOURCE - -#include "protocol.h" -#include "helpers.h" -#include "params.h" - -#include -#include -#include -#include - -// find N level domain -static bool FindNLD(const uint8_t *dom, size_t dlen, int level, const uint8_t **p, size_t *len) -{ - int i; - const uint8_t *p1,*p2; - for (i=1,p2=dom+dlen;idom && *p2!='.'; p2--); - if (p2<=dom) return false; - } - for (p1=p2-1 ; p1>dom && *p1!='.'; p1--); - if (*p1=='.') p1++; - if (p) *p = p1; - if (len) *len = p2-p1; - return true; -} - -const char *l7proto_str(t_l7proto l7) -{ - switch(l7) - { - case HTTP: return "http"; - case TLS: return "tls"; - case QUIC: return "quic"; - case WIREGUARD: return "wireguard"; - case DHT: return "dht"; - case DISCORD: return "discord"; - case STUN: return "stun"; - default: return "unknown"; - } -} -bool l7_proto_match(t_l7proto l7proto, uint32_t filter_l7) -{ - return (l7proto==UNKNOWN && (filter_l7 & L7_PROTO_UNKNOWN)) || - (l7proto==HTTP && (filter_l7 & L7_PROTO_HTTP)) || - (l7proto==TLS && (filter_l7 & L7_PROTO_TLS)) || - (l7proto==QUIC && (filter_l7 & L7_PROTO_QUIC)) || - (l7proto==WIREGUARD && (filter_l7 & L7_PROTO_WIREGUARD)) || - (l7proto==DHT && (filter_l7 & L7_PROTO_DHT)) || - (l7proto==DISCORD && (filter_l7 & L7_PROTO_DISCORD)) || - (l7proto==STUN && (filter_l7 & L7_PROTO_STUN)); -} - -bool IsHostMarker(uint8_t posmarker) -{ - switch(posmarker) - { - case PM_HOST: - case PM_HOST_END: - case PM_HOST_SLD: - case PM_HOST_MIDSLD: - case PM_HOST_ENDSLD: - return true; - default: - return false; - } -} -const char *posmarker_name(uint8_t posmarker) -{ - switch(posmarker) - { - case PM_ABS: return "abs"; - case PM_HOST: return "host"; - case PM_HOST_END: return "endhost"; - case PM_HOST_SLD: return "sld"; - case PM_HOST_MIDSLD: return "midsld"; - case PM_HOST_ENDSLD: return "endsld"; - case PM_HTTP_METHOD: return "method"; - case PM_SNI_EXT: return "sniext"; - default: return "?"; - } -} - -static size_t CheckPos(size_t sz, ssize_t offset) -{ - return (offset>=0 && offsetmarker, sp->pos, data, sz); - case TLS: - return TLSPos(sp->marker, sp->pos, data, sz); - default: - return AnyProtoPos(sp->marker, sp->pos, data, sz); - } -} -void ResolveMultiPos(const uint8_t *data, size_t sz, t_l7proto l7proto, const struct proto_pos *splits, int split_count, size_t *pos, int *pos_count) -{ - int i,j; - for(i=j=0;i14 && !memcmp(data,"HTTP/1.",7) && (data[7]=='0' || data[7]=='1') && data[8]==' ' && - data[9]>='0' && data[9]<='9' && - data[10]>='0' && data[10]<='9' && - data[11]>='0' && data[11]<='9'; -} -int HttpReplyCode(const uint8_t *data, size_t len) -{ - return (data[9]-'0')*100 + (data[10]-'0')*10 + (data[11]-'0'); -} -bool HttpExtractHeader(const uint8_t *data, size_t len, const char *header, char *buf, size_t len_buf) -{ - const uint8_t *p, *s, *e = data + len; - - p = (uint8_t*)strncasestr((char*)data, header, len); - if (!p) return false; - p += strlen(header); - while (p < e && (*p == ' ' || *p == '\t')) p++; - s = p; - while (s < e && (*s != '\r' && *s != '\n' && *s != ' ' && *s != '\t')) s++; - if (s > p) - { - size_t slen = s - p; - if (buf && len_buf) - { - if (slen >= len_buf) slen = len_buf - 1; - for (size_t i = 0; i < slen; i++) buf[i] = tolower(p[i]); - buf[slen] = 0; - } - return true; - } - return false; -} -bool HttpExtractHost(const uint8_t *data, size_t len, char *host, size_t len_host) -{ - return HttpExtractHeader(data, len, "\nHost:", host, len_host); -} -// DPI redirects are global redirects to another domain -bool HttpReplyLooksLikeDPIRedirect(const uint8_t *data, size_t len, const char *host) -{ - char loc[256],*redirect_host, *p; - int code; - - if (!host || !*host) return false; - - code = HttpReplyCode(data,len); - - if ((code!=302 && code!=307) || !HttpExtractHeader(data,len,"\nLocation:",loc,sizeof(loc))) return false; - - // something like : https://censor.net/badpage.php?reason=denied&source=RKN - - if (!strncmp(loc,"http://",7)) - redirect_host=loc+7; - else if (!strncmp(loc,"https://",8)) - redirect_host=loc+8; - else - return false; - - // somethinkg like : censor.net/badpage.php?reason=denied&source=RKN - - for(p=redirect_host; *p && *p!='/' ; p++); - *p=0; - if (!*redirect_host) return false; - - // somethinkg like : censor.net - - // extract 2nd level domains - const char *dhost, *drhost; - if (!FindNLD((uint8_t*)host,strlen(host),2,(const uint8_t**)&dhost,NULL) || !FindNLD((uint8_t*)redirect_host,strlen(redirect_host),2,(const uint8_t**)&drhost,NULL)) - return false; - - // compare 2nd level domains - return strcasecmp(dhost, drhost)!=0; -} -size_t HttpPos(uint8_t posmarker, int16_t pos, const uint8_t *data, size_t sz) -{ - const uint8_t *method, *host=NULL, *p; - size_t offset_host,len_host; - ssize_t offset; - int i; - - switch(posmarker) - { - case PM_HTTP_METHOD: - // recognize some tpws pre-applied hacks - method=data; - if (sz<10) break; - if (*method=='\n' || *method=='\r') method++; - if (*method=='\n' || *method=='\r') method++; - for (p=method,i=0;i<7;i++) if (*p>='A' && *p<='Z') p++; - if (i<3 || *p!=' ') break; - return CheckPos(sz,method-data+pos); - case PM_HOST: - case PM_HOST_END: - case PM_HOST_SLD: - case PM_HOST_MIDSLD: - case PM_HOST_ENDSLD: - if (HttpFindHostConst(&host,data,sz) && (host-data+7)>12)==((tlsver>>4)&0xF))) ? "GREASE" : "UNKNOWN"; - } -} - -uint16_t TLSRecordDataLen(const uint8_t *data) -{ - return pntoh16(data + 3); -} -size_t TLSRecordLen(const uint8_t *data) -{ - return TLSRecordDataLen(data) + 5; -} -bool IsTLSRecordFull(const uint8_t *data, size_t len) -{ - return TLSRecordLen(data)<=len; -} -bool IsTLSClientHello(const uint8_t *data, size_t len, bool bPartialIsOK) -{ - return len >= 6 && data[0] == 0x16 && data[1] == 0x03 && data[2] <= 0x03 && data[5] == 0x01 && (bPartialIsOK || TLSRecordLen(data) <= len); -} - -size_t TLSHandshakeLen(const uint8_t *data) -{ - return data[1] << 16 | data[2] << 8 | data[3]; // HandshakeProtocol length -} -bool IsTLSHandshakeClientHello(const uint8_t *data, size_t len) -{ - return len>=4 && data[0]==0x01 && TLSHandshakeLen(data)>0; -} -bool IsTLSHandshakeFull(const uint8_t *data, size_t len) -{ - return (4+TLSHandshakeLen(data))<=len; -} - - -bool TLSFindExtLenOffsetInHandshake(const uint8_t *data, size_t len, size_t *off) -{ - // +0 - // u8 HandshakeType: ClientHello - // u24 Length - // u16 Version - // c[32] random - // u8 SessionIDLength - // - // u16 CipherSuitesLength - // - // u8 CompressionMethodsLength - // - // u16 ExtensionsLength - - size_t l; - - l = 1 + 3 + 2 + 32; - // SessionIDLength - if (len < (l + 1)) return false; - l += data[l] + 1; - // CipherSuitesLength - if (len < (l + 2)) return false; - l += pntoh16(data + l) + 2; - // CompressionMethodsLength - if (len < (l + 1)) return false; - l += data[l] + 1; - // ExtensionsLength - if (len < (l + 2)) return false; - *off = l; - return true; -} -bool TLSFindExtLen(const uint8_t *data, size_t len, size_t *off) -{ - if (!TLSFindExtLenOffsetInHandshake(data+5,len-5,off)) - return false; - *off+=5; - return true; -} - -// bPartialIsOK=true - accept partial packets not containing the whole TLS message -bool TLSFindExtInHandshake(const uint8_t *data, size_t len, uint16_t type, const uint8_t **ext, size_t *len_ext, bool bPartialIsOK) -{ - // +0 - // u8 HandshakeType: ClientHello - // u24 Length - // u16 Version - // c[32] random - // u8 SessionIDLength - // - // u16 CipherSuitesLength - // - // u8 CompressionMethodsLength - // - // u16 ExtensionsLength - - size_t l; - - if (!bPartialIsOK && !IsTLSHandshakeFull(data,len)) return false; - - if (!TLSFindExtLenOffsetInHandshake(data,len,&l)) return false; - - data += l; len -= l; - l = pntoh16(data); - data += 2; len -= 2; - - if (bPartialIsOK) - { - if (len < l) l = len; - } - else - { - if (len < l) return false; - } - - while (l >= 4) - { - uint16_t etype = pntoh16(data); - size_t elen = pntoh16(data + 2); - data += 4; l -= 4; - if (l < elen) break; - if (etype == type) - { - if (ext && len_ext) - { - *ext = data; - *len_ext = elen; - } - return true; - } - data += elen; l -= elen; - } - - return false; -} -bool TLSFindExt(const uint8_t *data, size_t len, uint16_t type, const uint8_t **ext, size_t *len_ext, bool bPartialIsOK) -{ - // +0 - // u8 ContentType: Handshake - // u16 Version: TLS1.0 - // u16 Length - size_t reclen; - if (!IsTLSClientHello(data, len, bPartialIsOK)) return false; - reclen=TLSRecordLen(data); - if (reclen= len_host) slen = len_host - 1; - for (size_t i = 0; i < slen; i++) host[i] = tolower(ext[i]); - host[slen] = 0; - } - return true; -} -bool TLSHelloExtractHost(const uint8_t *data, size_t len, char *host, size_t len_host, bool bPartialIsOK) -{ - const uint8_t *ext; - size_t elen; - - if (!TLSFindExt(data, len, 0, &ext, &elen, bPartialIsOK)) return false; - return TLSExtractHostFromExt(ext, elen, host, len_host); -} -bool TLSHelloExtractHostFromHandshake(const uint8_t *data, size_t len, char *host, size_t len_host, bool bPartialIsOK) -{ - const uint8_t *ext; - size_t elen; - - if (!TLSFindExtInHandshake(data, len, 0, &ext, &elen, bPartialIsOK)) return false; - return TLSExtractHostFromExt(ext, elen, host, len_host); -} - -size_t TLSPos(uint8_t posmarker, int16_t pos, const uint8_t *data, size_t sz) -{ - size_t elen; - const uint8_t *ext, *p; - size_t offset_host,len_host; - ssize_t offset; - - switch(posmarker) - { - case PM_HOST: - case PM_HOST_END: - case PM_HOST_SLD: - case PM_HOST_MIDSLD: - case PM_HOST_ENDSLD: - case PM_SNI_EXT: - if (TLSFindExt(data,sz,0,&ext,&elen,TLS_PARTIALS_ENABLE)) - { - if (posmarker==PM_SNI_EXT) - { - return CheckPos(sz,ext-data+pos); - } - else - { - if (!TLSAdvanceToHostInSNI(&ext,&elen,&len_host)) - return 0; - offset_host = ext-data; - return HostPos(posmarker,pos,data,sz,offset_host,len_host); - } - } - return 0; - default: - return AnyProtoPos(posmarker,pos,data,sz); - } -} - - - -static uint8_t tvb_get_varint(const uint8_t *tvb, uint64_t *value) -{ - switch (*tvb >> 6) - { - case 0: /* 0b00 => 1 byte length (6 bits Usable) */ - if (value) *value = *tvb & 0x3F; - return 1; - case 1: /* 0b01 => 2 bytes length (14 bits Usable) */ - if (value) *value = pntoh16(tvb) & 0x3FFF; - return 2; - case 2: /* 0b10 => 4 bytes length (30 bits Usable) */ - if (value) *value = pntoh32(tvb) & 0x3FFFFFFF; - return 4; - case 3: /* 0b11 => 8 bytes length (62 bits Usable) */ - if (value) *value = pntoh64(tvb) & 0x3FFFFFFFFFFFFFFF; - return 8; - } - // impossible case - if (*value) *value = 0; - return 0; -} -static uint8_t tvb_get_size(uint8_t tvb) -{ - return 1 << (tvb >> 6); -} - -bool IsQUICCryptoHello(const uint8_t *data, size_t len, size_t *hello_offset, size_t *hello_len) -{ - size_t offset = 1; - uint64_t coff, clen; - if (len < 3 || *data != 6) return false; - if ((offset+tvb_get_size(data[offset])) >= len) return false; - offset += tvb_get_varint(data + offset, &coff); - // offset must be 0 if it's a full segment, not just a chunk - if (coff || (offset+tvb_get_size(data[offset])) >= len) return false; - offset += tvb_get_varint(data + offset, &clen); - if ((offset + clen) > len || !IsTLSHandshakeClientHello(data+offset,clen)) return false; - if (hello_offset) *hello_offset = offset; - if (hello_len) *hello_len = (size_t)clen; - return true; -} - -/* Returns the QUIC draft version or 0 if not applicable. */ -uint8_t QUICDraftVersion(uint32_t version) -{ - /* IETF Draft versions */ - if ((version >> 8) == 0xff0000) { - return (uint8_t)version; - } - /* Facebook mvfst, based on draft -22. */ - if (version == 0xfaceb001) { - return 22; - } - /* Facebook mvfst, based on draft -27. */ - if (version == 0xfaceb002 || version == 0xfaceb00e) { - return 27; - } - /* GQUIC Q050, T050 and T051: they are not really based on any drafts, - * but we must return a sensible value */ - if (version == 0x51303530 || - version == 0x54303530 || - version == 0x54303531) { - return 27; - } - /* https://tools.ietf.org/html/draft-ietf-quic-transport-32#section-15 - "Versions that follow the pattern 0x?a?a?a?a are reserved for use in - forcing version negotiation to be exercised" - It is tricky to return a correct draft version: such number is primarily - used to select a proper salt (which depends on the version itself), but - we don't have a real version here! Let's hope that we need to handle - only latest drafts... */ - if ((version & 0x0F0F0F0F) == 0x0a0a0a0a) { - return 29; - } - /* QUIC (final?) constants for v1 are defined in draft-33, but draft-34 is the - final draft version */ - if (version == 0x00000001) { - return 34; - } - /* QUIC Version 2 */ - /* TODO: for the time being use 100 as a number for V2 and let see how v2 drafts evolve */ - if (version == 0x709A50C4) { - return 100; - } - return 0; -} - -static bool is_quic_draft_max(uint32_t draft_version, uint8_t max_version) -{ - return draft_version && draft_version <= max_version; -} -static bool is_quic_v2(uint32_t version) -{ - return version == 0x6b3343cf; -} - -static bool quic_hkdf_expand_label(const uint8_t *secret, uint8_t secret_len, const char *label, uint8_t *out, size_t out_len) -{ - uint8_t hkdflabel[64]; - - size_t label_size = strlen(label); - if (label_size > 255) return false; - size_t hkdflabel_size = 2 + 1 + label_size + 1; - if (hkdflabel_size > sizeof(hkdflabel)) return false; - - phton16(hkdflabel, out_len); - hkdflabel[2] = (uint8_t)label_size; - memcpy(hkdflabel + 3, label, label_size); - hkdflabel[3 + label_size] = 0; - return !hkdfExpand(SHA256, secret, secret_len, hkdflabel, hkdflabel_size, out, out_len); -} - -static bool quic_derive_initial_secret(const quic_cid_t *cid, uint8_t *client_initial_secret, uint32_t version) -{ - /* - * https://tools.ietf.org/html/draft-ietf-quic-tls-29#section-5.2 - * - * initial_salt = 0xafbfec289993d24c9e9786f19c6111e04390a899 - * initial_secret = HKDF-Extract(initial_salt, client_dst_connection_id) - * - * client_initial_secret = HKDF-Expand-Label(initial_secret, - * "client in", "", Hash.length) - * server_initial_secret = HKDF-Expand-Label(initial_secret, - * "server in", "", Hash.length) - * - * Hash for handshake packets is SHA-256 (output size 32). - */ - static const uint8_t handshake_salt_draft_22[20] = { - 0x7f, 0xbc, 0xdb, 0x0e, 0x7c, 0x66, 0xbb, 0xe9, 0x19, 0x3a, - 0x96, 0xcd, 0x21, 0x51, 0x9e, 0xbd, 0x7a, 0x02, 0x64, 0x4a - }; - static const uint8_t handshake_salt_draft_23[20] = { - 0xc3, 0xee, 0xf7, 0x12, 0xc7, 0x2e, 0xbb, 0x5a, 0x11, 0xa7, - 0xd2, 0x43, 0x2b, 0xb4, 0x63, 0x65, 0xbe, 0xf9, 0xf5, 0x02, - }; - static const uint8_t handshake_salt_draft_29[20] = { - 0xaf, 0xbf, 0xec, 0x28, 0x99, 0x93, 0xd2, 0x4c, 0x9e, 0x97, - 0x86, 0xf1, 0x9c, 0x61, 0x11, 0xe0, 0x43, 0x90, 0xa8, 0x99 - }; - static const uint8_t handshake_salt_v1[20] = { - 0x38, 0x76, 0x2c, 0xf7, 0xf5, 0x59, 0x34, 0xb3, 0x4d, 0x17, - 0x9a, 0xe6, 0xa4, 0xc8, 0x0c, 0xad, 0xcc, 0xbb, 0x7f, 0x0a - }; - static const uint8_t hanshake_salt_draft_q50[20] = { - 0x50, 0x45, 0x74, 0xEF, 0xD0, 0x66, 0xFE, 0x2F, 0x9D, 0x94, - 0x5C, 0xFC, 0xDB, 0xD3, 0xA7, 0xF0, 0xD3, 0xB5, 0x6B, 0x45 - }; - static const uint8_t hanshake_salt_draft_t50[20] = { - 0x7f, 0xf5, 0x79, 0xe5, 0xac, 0xd0, 0x72, 0x91, 0x55, 0x80, - 0x30, 0x4c, 0x43, 0xa2, 0x36, 0x7c, 0x60, 0x48, 0x83, 0x10 - }; - static const uint8_t hanshake_salt_draft_t51[20] = { - 0x7a, 0x4e, 0xde, 0xf4, 0xe7, 0xcc, 0xee, 0x5f, 0xa4, 0x50, - 0x6c, 0x19, 0x12, 0x4f, 0xc8, 0xcc, 0xda, 0x6e, 0x03, 0x3d - }; - static const uint8_t handshake_salt_v2[20] = { - 0x0d, 0xed, 0xe3, 0xde, 0xf7, 0x00, 0xa6, 0xdb, 0x81, 0x93, - 0x81, 0xbe, 0x6e, 0x26, 0x9d, 0xcb, 0xf9, 0xbd, 0x2e, 0xd9 - }; - - int err; - const uint8_t *salt; - uint8_t secret[USHAMaxHashSize]; - uint8_t draft_version = QUICDraftVersion(version); - - if (version == 0x51303530) { - salt = hanshake_salt_draft_q50; - } - else if (version == 0x54303530) { - salt = hanshake_salt_draft_t50; - } - else if (version == 0x54303531) { - salt = hanshake_salt_draft_t51; - } - else if (is_quic_draft_max(draft_version, 22)) { - salt = handshake_salt_draft_22; - } - else if (is_quic_draft_max(draft_version, 28)) { - salt = handshake_salt_draft_23; - } - else if (is_quic_draft_max(draft_version, 32)) { - salt = handshake_salt_draft_29; - } - else if (is_quic_draft_max(draft_version, 34)) { - salt = handshake_salt_v1; - } - else { - salt = handshake_salt_v2; - } - - err = hkdfExtract(SHA256, salt, 20, cid->cid, cid->len, secret); - if (err) return false; - - if (client_initial_secret && !quic_hkdf_expand_label(secret, SHA256HashSize, "tls13 client in", client_initial_secret, SHA256HashSize)) - return false; - - return true; -} -bool QUICIsLongHeader(const uint8_t *data, size_t len) -{ - return len>=9 && !!(*data & 0x80); -} -uint32_t QUICExtractVersion(const uint8_t *data, size_t len) -{ - // long header, fixed bit, type=initial - return QUICIsLongHeader(data, len) ? ntohl(*(uint32_t*)(data + 1)) : 0; -} -bool QUICExtractDCID(const uint8_t *data, size_t len, quic_cid_t *cid) -{ - if (!QUICIsLongHeader(data,len) || !data[5] || data[5] > QUIC_MAX_CID_LENGTH || (6+data[5])>len) return false; - cid->len = data[5]; - memcpy(&cid->cid, data + 6, data[5]); - return true; -} -bool QUICDecryptInitial(const uint8_t *data, size_t data_len, uint8_t *clean, size_t *clean_len) -{ - uint32_t ver = QUICExtractVersion(data, data_len); - if (!ver) return false; - - quic_cid_t dcid; - if (!QUICExtractDCID(data, data_len, &dcid)) return false; - - uint8_t client_initial_secret[SHA256HashSize]; - if (!quic_derive_initial_secret(&dcid, client_initial_secret, ver)) return false; - - uint8_t aeskey[16], aesiv[12], aeshp[16]; - bool v1_label = !is_quic_v2(ver); - if (!quic_hkdf_expand_label(client_initial_secret, SHA256HashSize, v1_label ? "tls13 quic key" : "tls13 quicv2 key", aeskey, sizeof(aeskey)) || - !quic_hkdf_expand_label(client_initial_secret, SHA256HashSize, v1_label ? "tls13 quic iv" : "tls13 quicv2 iv", aesiv, sizeof(aesiv)) || - !quic_hkdf_expand_label(client_initial_secret, SHA256HashSize, v1_label ? "tls13 quic hp" : "tls13 quicv2 hp", aeshp, sizeof(aeshp))) - { - return false; - } - - uint64_t payload_len,token_len; - size_t pn_offset; - pn_offset = 1 + 4 + 1 + data[5]; - if (pn_offset >= data_len) return false; - pn_offset += 1 + data[pn_offset]; - if ((pn_offset + tvb_get_size(data[pn_offset])) >= data_len) return false; - pn_offset += tvb_get_varint(data + pn_offset, &token_len); - pn_offset += token_len; - if ((pn_offset + tvb_get_size(data[pn_offset])) >= data_len) return false; - pn_offset += tvb_get_varint(data + pn_offset, &payload_len); - if (payload_len<20 || (pn_offset + payload_len)>data_len) return false; - - aes_init_keygen_tables(); - - uint8_t sample_enc[16]; - aes_context ctx; - if (aes_setkey(&ctx, 1, aeshp, sizeof(aeshp)) || aes_cipher(&ctx, data + pn_offset + 4, sample_enc)) return false; - - uint8_t mask[5]; - memcpy(mask, sample_enc, sizeof(mask)); - - uint8_t packet0 = data[0] ^ (mask[0] & 0x0f); - uint8_t pkn_len = (packet0 & 0x03) + 1; - - uint8_t pkn_bytes[4]; - memcpy(pkn_bytes, data + pn_offset, pkn_len); - uint32_t pkn = 0; - for (uint8_t i = 0; i < pkn_len; i++) pkn |= (uint32_t)(pkn_bytes[i] ^ mask[1 + i]) << (8 * (pkn_len - 1 - i)); - - phton64(aesiv + sizeof(aesiv) - 8, pntoh64(aesiv + sizeof(aesiv) - 8) ^ pkn); - - size_t cryptlen = payload_len - pkn_len - 16; - if (cryptlen > *clean_len) return false; - *clean_len = cryptlen; - const uint8_t *decrypt_begin = data + pn_offset + pkn_len; - - uint8_t atag[16],header[256]; - size_t header_len = pn_offset + pkn_len; - if (header_len > sizeof(header)) return false; // not likely header will be so large - memcpy(header, data, header_len); - header[0] = packet0; - for(uint8_t i = 0; i < pkn_len; i++) header[header_len - 1 - i] = (uint8_t)(pkn >> (8 * i)); - - if (aes_gcm_crypt(AES_DECRYPT, clean, decrypt_begin, cryptlen, aeskey, sizeof(aeskey), aesiv, sizeof(aesiv), header, header_len, atag, sizeof(atag))) - return false; - - // check if message was decrypted correctly : good keys , no data corruption - return !memcmp(data + pn_offset + pkn_len + cryptlen, atag, 16); -} - -struct range64 -{ - uint64_t offset,len; -}; -#define MAX_DEFRAG_PIECES 128 -static int cmp_range64(const void * a, const void * b) -{ - return (((struct range64*)a)->offset < ((struct range64*)b)->offset) ? -1 : (((struct range64*)a)->offset > ((struct range64*)b)->offset) ? 1 : 0; -} -bool QUICDefragCrypto(const uint8_t *clean,size_t clean_len, uint8_t *defrag,size_t *defrag_len, bool *bFull) -{ - // Crypto frame can be split into multiple chunks - // chromium randomly splits it and pads with zero/one bytes to force support the standard - // mozilla does not split - - if (*defrag_len<10) return false; - uint8_t *defrag_data = defrag+10; - size_t defrag_data_len = *defrag_len-10; - uint8_t ft; - uint64_t offset,sz,szmax=0,zeropos=0,pos=0; - bool found=false; - struct range64 ranges[MAX_DEFRAG_PIECES]; - int i,range=0; - - while(pos1) // 00 - padding, 01 - ping - { - if (ft!=6) return false; // dont want to know all possible frame type formats - - if (pos>=clean_len) return false; - if (range>=MAX_DEFRAG_PIECES) return false; - - if ((pos+tvb_get_size(clean[pos])>=clean_len)) return false; - pos += tvb_get_varint(clean+pos, &offset); - - if ((pos+tvb_get_size(clean[pos])>clean_len)) return false; - pos += tvb_get_varint(clean+pos, &sz); - if ((pos+sz)>clean_len) return false; - - if ((offset+sz)>defrag_data_len) return false; // defrag buf overflow - if (zeropos < offset) - // make sure no uninitialized gaps exist in case of not full fragment coverage - memset(defrag_data+zeropos,0,offset-zeropos); - if ((offset+sz) > zeropos) - zeropos=offset+sz; - memcpy(defrag_data+offset,clean+pos,sz); - if ((offset+sz) > szmax) szmax = offset+sz; - - found=true; - pos+=sz; - - ranges[range].offset = offset; - ranges[range].len = sz; - range++; - } - } - if (found) - { - defrag[0] = 6; - defrag[1] = 0; // offset - // 2..9 - length 64 bit - // +10 - data start - phton64(defrag+2,szmax); - defrag[2] |= 0xC0; // 64 bit value - *defrag_len = (size_t)(szmax+10); - - qsort(ranges, range, sizeof(*ranges), cmp_range64); - - //for(i=0 ; i QUIC_MAX_CID_LENGTH) return false; - offset += 1 + data[offset]; - - // SCID - if (data[offset] > QUIC_MAX_CID_LENGTH) return false; - offset += 1 + data[offset]; - - // token length - offset += tvb_get_varint(data + offset, &sz); - offset += sz; - if (offset >= len) return false; - - // payload length - if ((offset + tvb_get_size(data[offset])) > len) return false; - tvb_get_varint(data + offset, &sz); - offset += sz; - if (offset > len) return false; - - // client hello cannot be too small. likely ACK - return sz>=96; -} - - - -bool IsWireguardHandshakeInitiation(const uint8_t *data, size_t len) -{ - return len==148 && data[0]==1; -} -bool IsDhtD1(const uint8_t *data, size_t len) -{ - return len>=7 && data[0]=='d' && data[1]=='1' && data[len-1]=='e'; -} -bool IsDiscordIpDiscoveryRequest(const uint8_t *data, size_t len) -{ - return len==74 && - data[0]==0 && data[1]==1 && - data[2]==0 && data[3]==70 && - !memcmp(data+8,"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",64); - // address is not set in request -} -bool IsStunMessage(const uint8_t *data, size_t len) -{ - return len>=20 && // header size - (data[0]&0xC0)==0 && // 2 most significant bits must be zeroes - (data[3]&0b11)==0 && // length must be a multiple of 4 - ntohl(*(uint32_t*)(&data[4]))==0x2112A442 && // magic cookie - ntohs(*(uint16_t*)(&data[2]))==len-20; -} diff --git a/nfq/protocol.h b/nfq/protocol.h deleted file mode 100644 index df7dc244..00000000 --- a/nfq/protocol.h +++ /dev/null @@ -1,97 +0,0 @@ -#pragma once - -#include -#include -#include -#include "crypto/sha.h" -#include "crypto/aes-gcm.h" -#include "helpers.h" - -typedef enum {UNKNOWN=0, HTTP, TLS, QUIC, WIREGUARD, DHT, DISCORD, STUN} t_l7proto; -#define L7_PROTO_HTTP 0x00000001 -#define L7_PROTO_TLS 0x00000002 -#define L7_PROTO_QUIC 0x00000004 -#define L7_PROTO_WIREGUARD 0x00000008 -#define L7_PROTO_DHT 0x00000010 -#define L7_PROTO_DISCORD 0x00000020 -#define L7_PROTO_STUN 0x00000040 -#define L7_PROTO_UNKNOWN 0x80000000 -const char *l7proto_str(t_l7proto l7); -bool l7_proto_match(t_l7proto l7proto, uint32_t filter_l7); - -// pos markers -#define PM_ABS 0 -#define PM_HOST 1 -#define PM_HOST_END 2 -#define PM_HOST_SLD 3 -#define PM_HOST_MIDSLD 4 -#define PM_HOST_ENDSLD 5 -#define PM_HTTP_METHOD 6 -#define PM_SNI_EXT 7 -struct proto_pos -{ - int16_t pos; - uint8_t marker; -}; -#define PROTO_POS_EMPTY(sp) ((sp)->marker==PM_ABS && (sp)->pos==0) -bool IsHostMarker(uint8_t posmarker); -const char *posmarker_name(uint8_t posmarker); -size_t AnyProtoPos(uint8_t posmarker, int16_t pos, const uint8_t *data, size_t sz); -size_t HttpPos(uint8_t posmarker, int16_t pos, const uint8_t *data, size_t sz); -size_t TLSPos(uint8_t posmarker, int16_t pos, const uint8_t *data, size_t sz); -size_t ResolvePos(const uint8_t *data, size_t sz, t_l7proto l7proto, const struct proto_pos *sp); -void ResolveMultiPos(const uint8_t *data, size_t sz, t_l7proto l7proto, const struct proto_pos *splits, int split_count, size_t *pos, int *pos_count); - -extern const char *http_methods[9]; -const char *HttpMethod(const uint8_t *data, size_t len); -bool IsHttp(const uint8_t *data, size_t len); -bool HttpFindHost(uint8_t **pHost,uint8_t *buf,size_t bs); -bool HttpFindHostConst(const uint8_t **pHost,const uint8_t *buf,size_t bs); -// header must be passed like this : "\nHost:" -bool HttpExtractHeader(const uint8_t *data, size_t len, const char *header, char *buf, size_t len_buf); -bool HttpExtractHost(const uint8_t *data, size_t len, char *host, size_t len_host); -bool IsHttpReply(const uint8_t *data, size_t len); -const char *HttpFind2ndLevelDomain(const char *host); -// must be pre-checked by IsHttpReply -int HttpReplyCode(const uint8_t *data, size_t len); -// must be pre-checked by IsHttpReply -bool HttpReplyLooksLikeDPIRedirect(const uint8_t *data, size_t len, const char *host); - -const char *TLSVersionStr(uint16_t tlsver); -uint16_t TLSRecordDataLen(const uint8_t *data); -size_t TLSRecordLen(const uint8_t *data); -bool IsTLSRecordFull(const uint8_t *data, size_t len); -bool IsTLSClientHello(const uint8_t *data, size_t len, bool bPartialIsOK); -size_t TLSHandshakeLen(const uint8_t *data); -bool IsTLSHandshakeClientHello(const uint8_t *data, size_t len); -bool IsTLSHandshakeFull(const uint8_t *data, size_t len); -bool TLSAdvanceToHostInSNI(const uint8_t **ext, size_t *elen, size_t *slen); -bool TLSFindExtLen(const uint8_t *data, size_t len, size_t *off); -bool TLSFindExtLenOffsetInHandshake(const uint8_t *data, size_t len, size_t *off); -bool TLSFindExt(const uint8_t *data, size_t len, uint16_t type, const uint8_t **ext, size_t *len_ext, bool bPartialIsOK); -bool TLSFindExtInHandshake(const uint8_t *data, size_t len, uint16_t type, const uint8_t **ext, size_t *len_ext, bool bPartialIsOK); -bool TLSHelloExtractHost(const uint8_t *data, size_t len, char *host, size_t len_host, bool bPartialIsOK); -bool TLSHelloExtractHostFromHandshake(const uint8_t *data, size_t len, char *host, size_t len_host, bool bPartialIsOK); - -bool IsWireguardHandshakeInitiation(const uint8_t *data, size_t len); -bool IsDhtD1(const uint8_t *data, size_t len); -bool IsDiscordIpDiscoveryRequest(const uint8_t *data, size_t len); -bool IsStunMessage(const uint8_t *data, size_t len); - -#define QUIC_MAX_CID_LENGTH 20 -typedef struct quic_cid { - uint8_t len; - uint8_t cid[QUIC_MAX_CID_LENGTH]; -} quic_cid_t; - -bool IsQUICInitial(const uint8_t *data, size_t len); -bool IsQUICCryptoHello(const uint8_t *data, size_t len, size_t *hello_offset, size_t *hello_len); -bool QUICIsLongHeader(const uint8_t *data, size_t len); -uint32_t QUICExtractVersion(const uint8_t *data, size_t len); -uint8_t QUICDraftVersion(uint32_t version); -bool QUICExtractDCID(const uint8_t *data, size_t len, quic_cid_t *cid); - -bool QUICDecryptInitial(const uint8_t *data, size_t data_len, uint8_t *clean, size_t *clean_len); -// returns true if crypto frames were found . bFull = true if crypto frame fragments have full coverage -bool QUICDefragCrypto(const uint8_t *clean,size_t clean_len, uint8_t *defrag,size_t *defrag_len, bool *bFull); -//bool QUICExtractHostFromInitial(const uint8_t *data, size_t data_len, char *host, size_t len_host, bool *bDecryptOK, bool *bIsCryptoHello); diff --git a/nfq/sec.c b/nfq/sec.c deleted file mode 100644 index a219a049..00000000 --- a/nfq/sec.c +++ /dev/null @@ -1,409 +0,0 @@ -#define _GNU_SOURCE - -#include -#include -#include "sec.h" -#include -#include -#include - -#include "params.h" - -#ifdef __linux__ - -#include -#include -#include -#include -// __X32_SYSCALL_BIT defined in linux/unistd.h -#include -#include -#include - -/************ SECCOMP ************/ - -// block most of the undesired syscalls to harden against code execution -static long blocked_syscalls[] = { -#ifdef SYS_execv -SYS_execv, -#endif -SYS_execve, -#ifdef SYS_execveat -SYS_execveat, -#endif -#ifdef SYS_exec_with_loader -SYS_exec_with_loader, -#endif -#ifdef SYS_clone -SYS_clone, -#endif -#ifdef SYS_clone2 -SYS_clone2, -#endif -#ifdef SYS_clone3 -SYS_clone3, -#endif -#ifdef SYS_osf_execve -SYS_osf_execve, -#endif -#ifdef SYS_fork -SYS_fork, -#endif -#ifdef SYS_vfork -SYS_vfork, -#endif -#ifdef SYS_uselib -SYS_uselib, -#endif -#ifdef SYS_unlink -SYS_unlink, -#endif -SYS_unlinkat, -#ifdef SYS_chmod -SYS_chmod, -#endif -SYS_fchmod,SYS_fchmodat, -#ifdef SYS_chown -SYS_chown, -#endif -#ifdef SYS_chown32 -SYS_chown32, -#endif -SYS_fchown, -#ifdef SYS_fchown32 -SYS_fchown32, -#endif -#ifdef SYS_lchown -SYS_lchown, -#endif -#ifdef SYS_lchown32 -SYS_lchown32, -#endif -SYS_fchownat, -#ifdef SYS_symlink -SYS_symlink, -#endif -SYS_symlinkat, -#ifdef SYS_link -SYS_link, -#endif -SYS_linkat, -SYS_truncate, -#ifdef SYS_truncate64 -SYS_truncate64, -#endif -SYS_ftruncate, -#ifdef SYS_ftruncate64 -SYS_ftruncate64, -#endif -#ifdef SYS_mknod -SYS_mknod, -#endif -SYS_mknodat, -#ifdef SYS_mkdir -SYS_mkdir, -#endif -SYS_mkdirat, -#ifdef SYS_rmdir -SYS_rmdir, -#endif -#ifdef SYS_rename -SYS_rename, -#endif -#ifdef SYS_renameat2 -SYS_renameat2, -#endif -#ifdef SYS_renameat -SYS_renameat, -#endif -#ifdef SYS_readdir -SYS_readdir, -#endif -#ifdef SYS_getdents -SYS_getdents, -#endif -#ifdef SYS_getdents64 -SYS_getdents64, -#endif -#ifdef SYS_process_vm_readv -SYS_process_vm_readv, -#endif -#ifdef SYS_process_vm_writev -SYS_process_vm_writev, -#endif -#ifdef SYS_process_madvise -SYS_process_madvise, -#endif -#ifdef SYS_tkill -SYS_tkill, -#endif -#ifdef SYS_tgkill -SYS_tgkill, -#endif -SYS_kill, SYS_ptrace -}; -#define BLOCKED_SYSCALL_COUNT (sizeof(blocked_syscalls)/sizeof(*blocked_syscalls)) - -static void set_filter(struct sock_filter *filter, __u16 code, __u8 jt, __u8 jf, __u32 k) -{ - filter->code = code; - filter->jt = jt; - filter->jf = jf; - filter->k = k; -} -// deny all blocked syscalls -static bool set_seccomp(void) -{ -#ifdef __X32_SYSCALL_BIT - #define SECCOMP_PROG_SIZE (6 + BLOCKED_SYSCALL_COUNT) -#else - #define SECCOMP_PROG_SIZE (5 + BLOCKED_SYSCALL_COUNT) -#endif - struct sock_filter sockf[SECCOMP_PROG_SIZE]; - struct sock_fprog prog = { .len = SECCOMP_PROG_SIZE, .filter = sockf }; - int i,idx=0; - - set_filter(&prog.filter[idx++], BPF_LD + BPF_W + BPF_ABS, 0, 0, arch_nr); -#ifdef __X32_SYSCALL_BIT - // x86 only - set_filter(&prog.filter[idx++], BPF_JMP + BPF_JEQ + BPF_K, 0, 3 + BLOCKED_SYSCALL_COUNT, ARCH_NR); // fail - set_filter(&prog.filter[idx++], BPF_LD + BPF_W + BPF_ABS, 0, 0, syscall_nr); - set_filter(&prog.filter[idx++], BPF_JMP + BPF_JGT + BPF_K, 1 + BLOCKED_SYSCALL_COUNT, 0, __X32_SYSCALL_BIT - 1); // fail -#else - set_filter(&prog.filter[idx++], BPF_JMP + BPF_JEQ + BPF_K, 0, 2 + BLOCKED_SYSCALL_COUNT, ARCH_NR); // fail - set_filter(&prog.filter[idx++], BPF_LD + BPF_W + BPF_ABS, 0, 0, syscall_nr); -#endif - -/* - // ! THIS IS NOT WORKING BECAUSE perror() in glibc dups() stderr - set_filter(&prog.filter[idx++], BPF_JMP + BPF_JEQ + BPF_K, 0, 3, SYS_write); // special check for write call - set_filter(&prog.filter[idx++], BPF_LD + BPF_W + BPF_ABS, 0, 0, syscall_arg(0)); // fd - set_filter(&prog.filter[idx++], BPF_JMP + BPF_JGT + BPF_K, 2 + BLOCKED_SYSCALL_COUNT, 0, 2); // 1 - stdout, 2 - stderr. greater are bad - set_filter(&prog.filter[idx++], BPF_LD + BPF_W + BPF_ABS, 0, 0, syscall_nr); // reload syscall_nr -*/ - for(i=0 ; i= 0; -} - -bool sec_harden(void) -{ - bool bRes = true; - if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) - { - DLOG_PERROR("PR_SET_NO_NEW_PRIVS(prctl)"); - bRes = false; - } -#if ARCH_NR!=0 - if (!set_seccomp()) - { - DLOG_PERROR("seccomp"); - if (errno==EINVAL) DLOG_ERR("seccomp: this can be safely ignored if kernel does not support seccomp\n"); - bRes = false; - } -#endif - return bRes; -} - - - - -bool checkpcap(uint64_t caps) -{ - if (!caps) return true; // no special caps reqd - - struct __user_cap_header_struct ch = {_LINUX_CAPABILITY_VERSION_3, getpid()}; - struct __user_cap_data_struct cd[2]; - uint32_t c0 = (uint32_t)caps; - uint32_t c1 = (uint32_t)(caps>>32); - - return !capget(&ch,cd) && (cd[0].effective & c0)==c0 && (cd[1].effective & c1)==c1; -} -bool setpcap(uint64_t caps) -{ - struct __user_cap_header_struct ch = {_LINUX_CAPABILITY_VERSION_3, getpid()}; - struct __user_cap_data_struct cd[2]; - - cd[0].effective = cd[0].permitted = (uint32_t)caps; - cd[0].inheritable = 0; - cd[1].effective = cd[1].permitted = (uint32_t)(caps>>32); - cd[1].inheritable = 0; - - return !capset(&ch,cd); -} -int getmaxcap(void) -{ - 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(void) -{ - uint64_t caps = (1< -#include - -#ifdef __linux__ - -#include -#include -#include - -bool checkpcap(uint64_t caps); -bool setpcap(uint64_t caps); -int getmaxcap(void); -bool dropcaps(void); - -#define syscall_nr (offsetof(struct seccomp_data, nr)) -#define arch_nr (offsetof(struct seccomp_data, arch)) -#define syscall_arg(x) (offsetof(struct seccomp_data, args[x])) - -#if defined(__aarch64__) - -# define ARCH_NR AUDIT_ARCH_AARCH64 - -#elif defined(__amd64__) - -# define ARCH_NR AUDIT_ARCH_X86_64 - -#elif defined(__arm__) && (defined(__ARM_EABI__) || defined(__thumb__)) - -# if __BYTE_ORDER == __LITTLE_ENDIAN -# define ARCH_NR AUDIT_ARCH_ARM -# else -# define ARCH_NR AUDIT_ARCH_ARMEB -# endif - -#elif defined(__i386__) - -# define ARCH_NR AUDIT_ARCH_I386 - -#elif defined(__mips__) - -#if _MIPS_SIM == _MIPS_SIM_ABI32 -# if __BYTE_ORDER == __LITTLE_ENDIAN -# define ARCH_NR AUDIT_ARCH_MIPSEL -# else -# define ARCH_NR AUDIT_ARCH_MIPS -# endif -#elif _MIPS_SIM == _MIPS_SIM_ABI64 -# if __BYTE_ORDER == __LITTLE_ENDIAN -# define ARCH_NR AUDIT_ARCH_MIPSEL64 -# else -# define ARCH_NR AUDIT_ARCH_MIPS64 -# endif -#else -# error "Unsupported mips abi" -#endif - -#elif defined(__PPC64__) - -# if __BYTE_ORDER == __LITTLE_ENDIAN -# define ARCH_NR AUDIT_ARCH_PPC64LE -# else -# define ARCH_NR AUDIT_ARCH_PPC64 -# endif - -#elif defined(__PPC__) - -# define ARCH_NR AUDIT_ARCH_PPC - -#elif __riscv && __riscv_xlen == 64 - -# define ARCH_NR AUDIT_ARCH_RISCV64 - -#else - -# error "Platform does not support seccomp filter yet" - -#endif - -#endif - - -#ifndef __CYGWIN__ -bool sec_harden(void); -bool can_drop_root(void); -bool droproot(uid_t uid, const char *user, const gid_t *gid, int gid_count); -void print_id(void); -#endif - -void daemonize(void); -bool writepid(const char *filename); diff --git a/nfq/uthash.h b/nfq/uthash.h deleted file mode 100644 index 9a396b61..00000000 --- a/nfq/uthash.h +++ /dev/null @@ -1,1136 +0,0 @@ -/* -Copyright (c) 2003-2021, Troy D. Hanson http://troydhanson.github.io/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.3.0 - -#include /* memcmp, memset, strlen */ -#include /* ptrdiff_t */ -#include /* exit */ - -#if defined(HASH_DEFINE_OWN_STDINT) && HASH_DEFINE_OWN_STDINT -/* This codepath is provided for backward compatibility, but I plan to remove it. */ -#warning "HASH_DEFINE_OWN_STDINT is deprecated; please use HASH_NO_STDINT instead" -typedef unsigned int uint32_t; -typedef unsigned char uint8_t; -#elif defined(HASH_NO_STDINT) && HASH_NO_STDINT -#else -#include /* uint8_t, uint32_t */ -#endif - -/* 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 - -#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_strlen -#define uthash_strlen(s) strlen(s) -#endif - -#ifndef HASH_FUNCTION -#define HASH_FUNCTION(keyptr,keylen,hashv) HASH_JEN(keyptr, keylen, hashv) -#endif - -#ifndef HASH_KEYCMP -#define HASH_KEYCMP(a,b,n) memcmp(a,b,n) -#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*)(void*)(((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_FUNCTION(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 { \ - (out) = NULL; \ - if (head) { \ - 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 = (const void*) (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 -#include /* fprintf, stderr */ -#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 - -/* 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]; /* FALLTHROUGH */ \ - default: ; \ - } \ - 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; \ - break; \ - default: ; \ - } \ - \ - /* 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) - -/* 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 (HASH_KEYCMP((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( \ - sizeof(struct UT_hash_bucket) * (tbl)->num_buckets * 2U); \ - if (!_he_new_buckets) { \ - HASH_RECORD_OOM(oomed); \ - } else { \ - uthash_bzero(_he_new_buckets, \ - sizeof(struct UT_hash_bucket) * (tbl)->num_buckets * 2U); \ - (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++; \ - if (_he_newbkt->count > _he_newbkt->expand_mult * (tbl)->ideal_chain_maxlen) { \ - _he_newbkt->expand_mult++; \ - } \ - } \ - _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*)(void*)(((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 */ - const 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/nfq/win.c b/nfq/win.c deleted file mode 100644 index e135324f..00000000 --- a/nfq/win.c +++ /dev/null @@ -1,80 +0,0 @@ -#ifdef __CYGWIN__ - -#include - -#include "win.h" -#include "nfqws.h" - -#define SERVICE_NAME "winws" - -static SERVICE_STATUS ServiceStatus; -static SERVICE_STATUS_HANDLE hStatus = NULL; -static int service_argc = 0; -static char **service_argv = NULL; - -void service_main(int argc __attribute__((unused)), char *argv[] __attribute__((unused))); - -bool service_run(int argc, char *argv[]) -{ - SERVICE_TABLE_ENTRY ServiceTable[] = { - {SERVICE_NAME, (LPSERVICE_MAIN_FUNCTION)service_main}, - {NULL, NULL} - }; - - service_argc = argc; - service_argv = argv; - - return StartServiceCtrlDispatcherA(ServiceTable); -} - -static void service_set_status(DWORD state) -{ - ServiceStatus.dwCurrentState = state; - SetServiceStatus(hStatus, &ServiceStatus); -} - -// Control handler function -void service_controlhandler(DWORD request) -{ - switch (request) - { - case SERVICE_CONTROL_STOP: - case SERVICE_CONTROL_SHUTDOWN: - bQuit = true; - ServiceStatus.dwCurrentState = SERVICE_STOP_PENDING; - break; - } - SetServiceStatus(hStatus, &ServiceStatus); -} - -void service_main(int argc __attribute__((unused)), char *argv[] __attribute__((unused))) -{ - ServiceStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS; - ServiceStatus.dwCurrentState = SERVICE_RUNNING; - ServiceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN; - ServiceStatus.dwWin32ExitCode = 0; - ServiceStatus.dwServiceSpecificExitCode = 0; - ServiceStatus.dwCheckPoint = 1; - ServiceStatus.dwWaitHint = 0; - - hStatus = RegisterServiceCtrlHandlerA( - SERVICE_NAME, - (LPHANDLER_FUNCTION)service_controlhandler); - if (hStatus == (SERVICE_STATUS_HANDLE)0) - { - // Registering Control Handler failed - return; - } - - SetServiceStatus(hStatus, &ServiceStatus); - - // Calling main with saved argc & argv - ServiceStatus.dwWin32ExitCode = (DWORD)main(service_argc, service_argv); - - ServiceStatus.dwCurrentState = SERVICE_STOPPED; - SetServiceStatus(hStatus, &ServiceStatus); - return; -} - - -#endif diff --git a/nfq/win.h b/nfq/win.h deleted file mode 100644 index 13a09801..00000000 --- a/nfq/win.h +++ /dev/null @@ -1,10 +0,0 @@ -#pragma once - -#ifdef __CYGWIN__ - -#include - -bool service_run(); - -#endif - diff --git a/nfq/windows/res/32/winicon.o b/nfq/windows/res/32/winicon.o deleted file mode 100644 index 8b8eaf6b..00000000 Binary files a/nfq/windows/res/32/winicon.o and /dev/null differ diff --git a/nfq/windows/res/32/winmanifest.o b/nfq/windows/res/32/winmanifest.o deleted file mode 100644 index 0db470db..00000000 Binary files a/nfq/windows/res/32/winmanifest.o and /dev/null differ diff --git a/nfq/windows/res/64/winicon.o b/nfq/windows/res/64/winicon.o deleted file mode 100644 index 36180cb4..00000000 Binary files a/nfq/windows/res/64/winicon.o and /dev/null differ diff --git a/nfq/windows/res/64/winmanifest.o b/nfq/windows/res/64/winmanifest.o deleted file mode 100644 index 0ca3b2f7..00000000 Binary files a/nfq/windows/res/64/winmanifest.o and /dev/null differ diff --git a/nfq/windows/windivert/libwindivert32.a b/nfq/windows/windivert/libwindivert32.a deleted file mode 100644 index 7d15ae65..00000000 Binary files a/nfq/windows/windivert/libwindivert32.a and /dev/null differ diff --git a/nfq/windows/windivert/libwindivert64.a b/nfq/windows/windivert/libwindivert64.a deleted file mode 100644 index 99f3d351..00000000 Binary files a/nfq/windows/windivert/libwindivert64.a and /dev/null differ diff --git a/nfq/windows/windivert/windivert.h b/nfq/windows/windivert/windivert.h deleted file mode 100644 index fc63adf2..00000000 --- a/nfq/windows/windivert/windivert.h +++ /dev/null @@ -1,630 +0,0 @@ -/* - * windivert.h - * (C) 2019, all rights reserved, - * - * This file is part of WinDivert. - * - * WinDivert 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 3 of the License, or (at your - * option) any later version. - * - * This program 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 this program. If not, see . - * - * WinDivert is free software; you can redistribute it and/or modify it under - * the terms of the GNU General Public License as published by the Free - * Software Foundation; either version 2 of the License, or (at your option) - * any later version. - * - * This program 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 General Public License - * for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., 51 - * Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -#ifndef __WINDIVERT_H -#define __WINDIVERT_H - -#ifndef WINDIVERT_KERNEL -#include -#endif /* WINDIVERT_KERNEL */ - -#ifndef WINDIVERTEXPORT -#define WINDIVERTEXPORT extern __declspec(dllimport) -#endif /* WINDIVERTEXPORT */ - -#ifdef __MINGW32__ -#define __in -#define __in_opt -#define __out -#define __out_opt -#define __inout -#define __inout_opt -#include -#define INT8 int8_t -#define UINT8 uint8_t -#define INT16 int16_t -#define UINT16 uint16_t -#define INT32 int32_t -#define UINT32 uint32_t -#define INT64 int64_t -#define UINT64 uint64_t -#endif /* __MINGW32__ */ - -#ifdef __cplusplus -extern "C" { -#endif - -/****************************************************************************/ -/* WINDIVERT API */ -/****************************************************************************/ - -/* - * WinDivert layers. - */ -typedef enum -{ - WINDIVERT_LAYER_NETWORK = 0, /* Network layer. */ - WINDIVERT_LAYER_NETWORK_FORWARD = 1,/* Network layer (forwarded packets) */ - WINDIVERT_LAYER_FLOW = 2, /* Flow layer. */ - WINDIVERT_LAYER_SOCKET = 3, /* Socket layer. */ - WINDIVERT_LAYER_REFLECT = 4, /* Reflect layer. */ -} WINDIVERT_LAYER, *PWINDIVERT_LAYER; - -/* - * WinDivert NETWORK and NETWORK_FORWARD layer data. - */ -typedef struct -{ - UINT32 IfIdx; /* Packet's interface index. */ - UINT32 SubIfIdx; /* Packet's sub-interface index. */ -} WINDIVERT_DATA_NETWORK, *PWINDIVERT_DATA_NETWORK; - -/* - * WinDivert FLOW layer data. - */ -typedef struct -{ - UINT64 EndpointId; /* Endpoint ID. */ - UINT64 ParentEndpointId; /* Parent endpoint ID. */ - UINT32 ProcessId; /* Process ID. */ - UINT32 LocalAddr[4]; /* Local address. */ - UINT32 RemoteAddr[4]; /* Remote address. */ - UINT16 LocalPort; /* Local port. */ - UINT16 RemotePort; /* Remote port. */ - UINT8 Protocol; /* Protocol. */ -} WINDIVERT_DATA_FLOW, *PWINDIVERT_DATA_FLOW; - -/* - * WinDivert SOCKET layer data. - */ -typedef struct -{ - UINT64 EndpointId; /* Endpoint ID. */ - UINT64 ParentEndpointId; /* Parent Endpoint ID. */ - UINT32 ProcessId; /* Process ID. */ - UINT32 LocalAddr[4]; /* Local address. */ - UINT32 RemoteAddr[4]; /* Remote address. */ - UINT16 LocalPort; /* Local port. */ - UINT16 RemotePort; /* Remote port. */ - UINT8 Protocol; /* Protocol. */ -} WINDIVERT_DATA_SOCKET, *PWINDIVERT_DATA_SOCKET; - -/* - * WinDivert REFLECTION layer data. - */ -typedef struct -{ - INT64 Timestamp; /* Handle open time. */ - UINT32 ProcessId; /* Handle process ID. */ - WINDIVERT_LAYER Layer; /* Handle layer. */ - UINT64 Flags; /* Handle flags. */ - INT16 Priority; /* Handle priority. */ -} WINDIVERT_DATA_REFLECT, *PWINDIVERT_DATA_REFLECT; - -/* - * WinDivert address. - */ -#ifdef _MSC_VER -#pragma warning(push) -#pragma warning(disable: 4201) -#endif -typedef struct -{ - INT64 Timestamp; /* Packet's timestamp. */ - UINT32 Layer:8; /* Packet's layer. */ - UINT32 Event:8; /* Packet event. */ - UINT32 Sniffed:1; /* Packet was sniffed? */ - UINT32 Outbound:1; /* Packet is outound? */ - UINT32 Loopback:1; /* Packet is loopback? */ - UINT32 Impostor:1; /* Packet is impostor? */ - UINT32 IPv6:1; /* Packet is IPv6? */ - UINT32 IPChecksum:1; /* Packet has valid IPv4 checksum? */ - UINT32 TCPChecksum:1; /* Packet has valid TCP checksum? */ - UINT32 UDPChecksum:1; /* Packet has valid UDP checksum? */ - UINT32 Reserved1:8; - UINT32 Reserved2; - union - { - WINDIVERT_DATA_NETWORK Network; /* Network layer data. */ - WINDIVERT_DATA_FLOW Flow; /* Flow layer data. */ - WINDIVERT_DATA_SOCKET Socket; /* Socket layer data. */ - WINDIVERT_DATA_REFLECT Reflect; /* Reflect layer data. */ - UINT8 Reserved3[64]; - }; -} WINDIVERT_ADDRESS, *PWINDIVERT_ADDRESS; -#ifdef _MSC_VER -#pragma warning(pop) -#endif - -/* - * WinDivert events. - */ -typedef enum -{ - WINDIVERT_EVENT_NETWORK_PACKET = 0, /* Network packet. */ - WINDIVERT_EVENT_FLOW_ESTABLISHED = 1, - /* Flow established. */ - WINDIVERT_EVENT_FLOW_DELETED = 2, /* Flow deleted. */ - WINDIVERT_EVENT_SOCKET_BIND = 3, /* Socket bind. */ - WINDIVERT_EVENT_SOCKET_CONNECT = 4, /* Socket connect. */ - WINDIVERT_EVENT_SOCKET_LISTEN = 5, /* Socket listen. */ - WINDIVERT_EVENT_SOCKET_ACCEPT = 6, /* Socket accept. */ - WINDIVERT_EVENT_SOCKET_CLOSE = 7, /* Socket close. */ - WINDIVERT_EVENT_REFLECT_OPEN = 8, /* WinDivert handle opened. */ - WINDIVERT_EVENT_REFLECT_CLOSE = 9, /* WinDivert handle closed. */ -} WINDIVERT_EVENT, *PWINDIVERT_EVENT; - -/* - * WinDivert flags. - */ -#define WINDIVERT_FLAG_SNIFF 0x0001 -#define WINDIVERT_FLAG_DROP 0x0002 -#define WINDIVERT_FLAG_RECV_ONLY 0x0004 -#define WINDIVERT_FLAG_READ_ONLY WINDIVERT_FLAG_RECV_ONLY -#define WINDIVERT_FLAG_SEND_ONLY 0x0008 -#define WINDIVERT_FLAG_WRITE_ONLY WINDIVERT_FLAG_SEND_ONLY -#define WINDIVERT_FLAG_NO_INSTALL 0x0010 -#define WINDIVERT_FLAG_FRAGMENTS 0x0020 - -/* - * WinDivert parameters. - */ -typedef enum -{ - WINDIVERT_PARAM_QUEUE_LENGTH = 0, /* Packet queue length. */ - WINDIVERT_PARAM_QUEUE_TIME = 1, /* Packet queue time. */ - WINDIVERT_PARAM_QUEUE_SIZE = 2, /* Packet queue size. */ - WINDIVERT_PARAM_VERSION_MAJOR = 3, /* Driver version (major). */ - WINDIVERT_PARAM_VERSION_MINOR = 4, /* Driver version (minor). */ -} WINDIVERT_PARAM, *PWINDIVERT_PARAM; -#define WINDIVERT_PARAM_MAX WINDIVERT_PARAM_VERSION_MINOR - -/* - * WinDivert shutdown parameter. - */ -typedef enum -{ - WINDIVERT_SHUTDOWN_RECV = 0x1, /* Shutdown recv. */ - WINDIVERT_SHUTDOWN_SEND = 0x2, /* Shutdown send. */ - WINDIVERT_SHUTDOWN_BOTH = 0x3, /* Shutdown recv and send. */ -} WINDIVERT_SHUTDOWN, *PWINDIVERT_SHUTDOWN; -#define WINDIVERT_SHUTDOWN_MAX WINDIVERT_SHUTDOWN_BOTH - -#ifndef WINDIVERT_KERNEL - -/* - * Open a WinDivert handle. - */ -WINDIVERTEXPORT HANDLE WinDivertOpen( - __in const char *filter, - __in WINDIVERT_LAYER layer, - __in INT16 priority, - __in UINT64 flags); - -/* - * Receive (read) a packet from a WinDivert handle. - */ -WINDIVERTEXPORT BOOL WinDivertRecv( - __in HANDLE handle, - __out_opt VOID *pPacket, - __in UINT packetLen, - __out_opt UINT *pRecvLen, - __out_opt WINDIVERT_ADDRESS *pAddr); - -/* - * Receive (read) a packet from a WinDivert handle. - */ -WINDIVERTEXPORT BOOL WinDivertRecvEx( - __in HANDLE handle, - __out_opt VOID *pPacket, - __in UINT packetLen, - __out_opt UINT *pRecvLen, - __in UINT64 flags, - __out WINDIVERT_ADDRESS *pAddr, - __inout_opt UINT *pAddrLen, - __inout_opt LPOVERLAPPED lpOverlapped); - -/* - * Send (write/inject) a packet to a WinDivert handle. - */ -WINDIVERTEXPORT BOOL WinDivertSend( - __in HANDLE handle, - __in const VOID *pPacket, - __in UINT packetLen, - __out_opt UINT *pSendLen, - __in const WINDIVERT_ADDRESS *pAddr); - -/* - * Send (write/inject) a packet to a WinDivert handle. - */ -WINDIVERTEXPORT BOOL WinDivertSendEx( - __in HANDLE handle, - __in const VOID *pPacket, - __in UINT packetLen, - __out_opt UINT *pSendLen, - __in UINT64 flags, - __in const WINDIVERT_ADDRESS *pAddr, - __in UINT addrLen, - __inout_opt LPOVERLAPPED lpOverlapped); - -/* - * Shutdown a WinDivert handle. - */ -WINDIVERTEXPORT BOOL WinDivertShutdown( - __in HANDLE handle, - __in WINDIVERT_SHUTDOWN how); - -/* - * Close a WinDivert handle. - */ -WINDIVERTEXPORT BOOL WinDivertClose( - __in HANDLE handle); - -/* - * Set a WinDivert handle parameter. - */ -WINDIVERTEXPORT BOOL WinDivertSetParam( - __in HANDLE handle, - __in WINDIVERT_PARAM param, - __in UINT64 value); - -/* - * Get a WinDivert handle parameter. - */ -WINDIVERTEXPORT BOOL WinDivertGetParam( - __in HANDLE handle, - __in WINDIVERT_PARAM param, - __out UINT64 *pValue); - -#endif /* WINDIVERT_KERNEL */ - -/* - * WinDivert constants. - */ -#define WINDIVERT_PRIORITY_HIGHEST 30000 -#define WINDIVERT_PRIORITY_LOWEST (-WINDIVERT_PRIORITY_HIGHEST) -#define WINDIVERT_PARAM_QUEUE_LENGTH_DEFAULT 4096 -#define WINDIVERT_PARAM_QUEUE_LENGTH_MIN 32 -#define WINDIVERT_PARAM_QUEUE_LENGTH_MAX 16384 -#define WINDIVERT_PARAM_QUEUE_TIME_DEFAULT 2000 /* 2s */ -#define WINDIVERT_PARAM_QUEUE_TIME_MIN 100 /* 100ms */ -#define WINDIVERT_PARAM_QUEUE_TIME_MAX 16000 /* 16s */ -#define WINDIVERT_PARAM_QUEUE_SIZE_DEFAULT 4194304 /* 4MB */ -#define WINDIVERT_PARAM_QUEUE_SIZE_MIN 65535 /* 64KB */ -#define WINDIVERT_PARAM_QUEUE_SIZE_MAX 33554432 /* 32MB */ -#define WINDIVERT_BATCH_MAX 0xFF /* 255 */ -#define WINDIVERT_MTU_MAX (40 + 0xFFFF) - -/****************************************************************************/ -/* WINDIVERT HELPER API */ -/****************************************************************************/ - -#ifdef _MSC_VER -#pragma warning(push) -#pragma warning(disable: 4214) -#endif - -/* - * IPv4/IPv6/ICMP/ICMPv6/TCP/UDP header definitions. - */ -typedef struct -{ - UINT8 HdrLength:4; - UINT8 Version:4; - UINT8 TOS; - UINT16 Length; - UINT16 Id; - UINT16 FragOff0; - UINT8 TTL; - UINT8 Protocol; - UINT16 Checksum; - UINT32 SrcAddr; - UINT32 DstAddr; -} WINDIVERT_IPHDR, *PWINDIVERT_IPHDR; - -#define WINDIVERT_IPHDR_GET_FRAGOFF(hdr) \ - (((hdr)->FragOff0) & 0xFF1F) -#define WINDIVERT_IPHDR_GET_MF(hdr) \ - ((((hdr)->FragOff0) & 0x0020) != 0) -#define WINDIVERT_IPHDR_GET_DF(hdr) \ - ((((hdr)->FragOff0) & 0x0040) != 0) -#define WINDIVERT_IPHDR_GET_RESERVED(hdr) \ - ((((hdr)->FragOff0) & 0x0080) != 0) - -#define WINDIVERT_IPHDR_SET_FRAGOFF(hdr, val) \ - do \ - { \ - (hdr)->FragOff0 = (((hdr)->FragOff0) & 0x00E0) | \ - ((val) & 0xFF1F); \ - } \ - while (FALSE) -#define WINDIVERT_IPHDR_SET_MF(hdr, val) \ - do \ - { \ - (hdr)->FragOff0 = (((hdr)->FragOff0) & 0xFFDF) | \ - (((val) & 0x0001) << 5); \ - } \ - while (FALSE) -#define WINDIVERT_IPHDR_SET_DF(hdr, val) \ - do \ - { \ - (hdr)->FragOff0 = (((hdr)->FragOff0) & 0xFFBF) | \ - (((val) & 0x0001) << 6); \ - } \ - while (FALSE) -#define WINDIVERT_IPHDR_SET_RESERVED(hdr, val) \ - do \ - { \ - (hdr)->FragOff0 = (((hdr)->FragOff0) & 0xFF7F) | \ - (((val) & 0x0001) << 7); \ - } \ - while (FALSE) - -typedef struct -{ - UINT8 TrafficClass0:4; - UINT8 Version:4; - UINT8 FlowLabel0:4; - UINT8 TrafficClass1:4; - UINT16 FlowLabel1; - UINT16 Length; - UINT8 NextHdr; - UINT8 HopLimit; - UINT32 SrcAddr[4]; - UINT32 DstAddr[4]; -} WINDIVERT_IPV6HDR, *PWINDIVERT_IPV6HDR; - -#define WINDIVERT_IPV6HDR_GET_TRAFFICCLASS(hdr) \ - ((((hdr)->TrafficClass0) << 4) | ((hdr)->TrafficClass1)) -#define WINDIVERT_IPV6HDR_GET_FLOWLABEL(hdr) \ - ((((UINT32)(hdr)->FlowLabel0) << 16) | ((UINT32)(hdr)->FlowLabel1)) - -#define WINDIVERT_IPV6HDR_SET_TRAFFICCLASS(hdr, val) \ - do \ - { \ - (hdr)->TrafficClass0 = ((UINT8)(val) >> 4); \ - (hdr)->TrafficClass1 = (UINT8)(val); \ - } \ - while (FALSE) -#define WINDIVERT_IPV6HDR_SET_FLOWLABEL(hdr, val) \ - do \ - { \ - (hdr)->FlowLabel0 = (UINT8)((val) >> 16); \ - (hdr)->FlowLabel1 = (UINT16)(val); \ - } \ - while (FALSE) - -typedef struct -{ - UINT8 Type; - UINT8 Code; - UINT16 Checksum; - UINT32 Body; -} WINDIVERT_ICMPHDR, *PWINDIVERT_ICMPHDR; - -typedef struct -{ - UINT8 Type; - UINT8 Code; - UINT16 Checksum; - UINT32 Body; -} WINDIVERT_ICMPV6HDR, *PWINDIVERT_ICMPV6HDR; - -typedef struct -{ - UINT16 SrcPort; - UINT16 DstPort; - UINT32 SeqNum; - UINT32 AckNum; - UINT16 Reserved1:4; - UINT16 HdrLength:4; - UINT16 Fin:1; - UINT16 Syn:1; - UINT16 Rst:1; - UINT16 Psh:1; - UINT16 Ack:1; - UINT16 Urg:1; - UINT16 Reserved2:2; - UINT16 Window; - UINT16 Checksum; - UINT16 UrgPtr; -} WINDIVERT_TCPHDR, *PWINDIVERT_TCPHDR; - -typedef struct -{ - UINT16 SrcPort; - UINT16 DstPort; - UINT16 Length; - UINT16 Checksum; -} WINDIVERT_UDPHDR, *PWINDIVERT_UDPHDR; - -#ifdef _MSC_VER -#pragma warning(pop) -#endif - -/* - * Flags for WinDivertHelperCalcChecksums() - */ -#define WINDIVERT_HELPER_NO_IP_CHECKSUM 1 -#define WINDIVERT_HELPER_NO_ICMP_CHECKSUM 2 -#define WINDIVERT_HELPER_NO_ICMPV6_CHECKSUM 4 -#define WINDIVERT_HELPER_NO_TCP_CHECKSUM 8 -#define WINDIVERT_HELPER_NO_UDP_CHECKSUM 16 - -#ifndef WINDIVERT_KERNEL - -/* - * Hash a packet. - */ -WINDIVERTEXPORT UINT64 WinDivertHelperHashPacket( - __in const VOID *pPacket, - __in UINT packetLen, - __in UINT64 seed -#ifdef __cplusplus - = 0 -#endif -); - -/* - * Parse IPv4/IPv6/ICMP/ICMPv6/TCP/UDP headers from a raw packet. - */ -WINDIVERTEXPORT BOOL WinDivertHelperParsePacket( - __in const VOID *pPacket, - __in UINT packetLen, - __out_opt PWINDIVERT_IPHDR *ppIpHdr, - __out_opt PWINDIVERT_IPV6HDR *ppIpv6Hdr, - __out_opt UINT8 *pProtocol, - __out_opt PWINDIVERT_ICMPHDR *ppIcmpHdr, - __out_opt PWINDIVERT_ICMPV6HDR *ppIcmpv6Hdr, - __out_opt PWINDIVERT_TCPHDR *ppTcpHdr, - __out_opt PWINDIVERT_UDPHDR *ppUdpHdr, - __out_opt PVOID *ppData, - __out_opt UINT *pDataLen, - __out_opt PVOID *ppNext, - __out_opt UINT *pNextLen); - -/* - * Parse an IPv4 address. - */ -WINDIVERTEXPORT BOOL WinDivertHelperParseIPv4Address( - __in const char *addrStr, - __out_opt UINT32 *pAddr); - -/* - * Parse an IPv6 address. - */ -WINDIVERTEXPORT BOOL WinDivertHelperParseIPv6Address( - __in const char *addrStr, - __out_opt UINT32 *pAddr); - -/* - * Format an IPv4 address. - */ -WINDIVERTEXPORT BOOL WinDivertHelperFormatIPv4Address( - __in UINT32 addr, - __out char *buffer, - __in UINT bufLen); - -/* - * Format an IPv6 address. - */ -WINDIVERTEXPORT BOOL WinDivertHelperFormatIPv6Address( - __in const UINT32 *pAddr, - __out char *buffer, - __in UINT bufLen); - -/* - * Calculate IPv4/IPv6/ICMP/ICMPv6/TCP/UDP checksums. - */ -WINDIVERTEXPORT BOOL WinDivertHelperCalcChecksums( - __inout VOID *pPacket, - __in UINT packetLen, - __out_opt WINDIVERT_ADDRESS *pAddr, - __in UINT64 flags); - -/* - * Decrement the TTL/HopLimit. - */ -WINDIVERTEXPORT BOOL WinDivertHelperDecrementTTL( - __inout VOID *pPacket, - __in UINT packetLen); - -/* - * Compile the given filter string. - */ -WINDIVERTEXPORT BOOL WinDivertHelperCompileFilter( - __in const char *filter, - __in WINDIVERT_LAYER layer, - __out_opt char *object, - __in UINT objLen, - __out_opt const char **errorStr, - __out_opt UINT *errorPos); - -/* - * Evaluate the given filter string. - */ -WINDIVERTEXPORT BOOL WinDivertHelperEvalFilter( - __in const char *filter, - __in const VOID *pPacket, - __in UINT packetLen, - __in const WINDIVERT_ADDRESS *pAddr); - -/* - * Format the given filter string. - */ -WINDIVERTEXPORT BOOL WinDivertHelperFormatFilter( - __in const char *filter, - __in WINDIVERT_LAYER layer, - __out char *buffer, - __in UINT bufLen); - -/* - * Byte ordering. - */ -WINDIVERTEXPORT UINT16 WinDivertHelperNtohs( - __in UINT16 x); -WINDIVERTEXPORT UINT16 WinDivertHelperHtons( - __in UINT16 x); -WINDIVERTEXPORT UINT32 WinDivertHelperNtohl( - __in UINT32 x); -WINDIVERTEXPORT UINT32 WinDivertHelperHtonl( - __in UINT32 x); -WINDIVERTEXPORT UINT64 WinDivertHelperNtohll( - __in UINT64 x); -WINDIVERTEXPORT UINT64 WinDivertHelperHtonll( - __in UINT64 x); -WINDIVERTEXPORT void WinDivertHelperNtohIPv6Address( - __in const UINT *inAddr, - __out UINT *outAddr); -WINDIVERTEXPORT void WinDivertHelperHtonIPv6Address( - __in const UINT *inAddr, - __out UINT *outAddr); - -/* - * Old names to be removed in the next version. - */ -WINDIVERTEXPORT void WinDivertHelperNtohIpv6Address( - __in const UINT *inAddr, - __out UINT *outAddr); -WINDIVERTEXPORT void WinDivertHelperHtonIpv6Address( - __in const UINT *inAddr, - __out UINT *outAddr); - -#endif /* WINDIVERT_KERNEL */ - -#ifdef __cplusplus -} -#endif - -#endif /* __WINDIVERT_H */ diff --git a/tpws/BSDmakefile b/tpws/BSDmakefile deleted file mode 100644 index f2264198..00000000 --- a/tpws/BSDmakefile +++ /dev/null @@ -1,13 +0,0 @@ -CC ?= cc -OPTIMIZE ?= -Os -CFLAGS += -std=gnu99 -s $(OPTIMIZE) -flto=auto -LIBS = -lz -lpthread -SRC_FILES = *.c - -all: tpws - -tpws: $(SRC_FILES) - $(CC) $(CFLAGS) -Iepoll-shim/include -o tpws $(SRC_FILES) epoll-shim/src/*.c $(LIBS) $(LDFLAGS) - -clean: - rm -f tpws *.o diff --git a/tpws/Makefile b/tpws/Makefile deleted file mode 100644 index 35ebb45e..00000000 --- a/tpws/Makefile +++ /dev/null @@ -1,35 +0,0 @@ -CC ?= cc -OPTIMIZE ?= -Os -CFLAGS += -std=gnu99 $(OPTIMIZE) -flto=auto -CFLAGS_SYSTEMD = -DUSE_SYSTEMD -CFLAGS_BSD = -Wno-address-of-packed-member -LDFLAGS_ANDROID = -llog -LIBS = -lz -lpthread -LIBS_SYSTEMD = -lsystemd -LIBS_ANDROID = -lz -SRC_FILES = *.c -SRC_FILES_ANDROID = $(SRC_FILES) andr/*.c - -all: tpws - -tpws: $(SRC_FILES) - $(CC) -s $(CFLAGS) -o tpws $(SRC_FILES) $(LIBS) $(LDFLAGS) - -systemd: $(SRC_FILES) - $(CC) -s $(CFLAGS) $(CFLAGS_SYSTEMD) -o tpws $(SRC_FILES) $(LIBS) $(LIBS_SYSTEMD) $(LDFLAGS) - -android: $(SRC_FILES) - $(CC) -s $(CFLAGS) -o tpws $(SRC_FILES_ANDROID) $(LIBS_ANDROID) $(LDFLAGS) $(LDFLAGS_ANDROID) - -bsd: $(SRC_FILES) - $(CC) -s $(CFLAGS) $(CFLAGS_BSD) -Iepoll-shim/include -o tpws $(SRC_FILES) epoll-shim/src/*.c $(LIBS) $(LDFLAGS) - -mac: $(SRC_FILES) - $(CC) $(CFLAGS) $(CFLAGS_BSD) -Iepoll-shim/include -Imacos -o tpwsa -target arm64-apple-macos10.8 $(SRC_FILES) epoll-shim/src/*.c $(LIBS) $(LDFLAGS) - $(CC) $(CFLAGS) $(CFLAGS_BSD) -Iepoll-shim/include -Imacos -o tpwsx -target x86_64-apple-macos10.8 $(SRC_FILES) epoll-shim/src/*.c $(LIBS) $(LDFLAGS) - strip tpwsa tpwsx - lipo -create -output tpws tpwsx tpwsa - rm -f tpwsx tpwsa - -clean: - rm -f tpws *.o diff --git a/tpws/andr/_musl_license.txt b/tpws/andr/_musl_license.txt deleted file mode 100644 index 5ae13c60..00000000 --- a/tpws/andr/_musl_license.txt +++ /dev/null @@ -1,26 +0,0 @@ -Code in this dir is taken from musl libc to support old android versions <7.0 - -musl as a whole is licensed under the following standard MIT license: - ----------------------------------------------------------------------- -Copyright 2005-2020 Rich Felker, et al. - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ----------------------------------------------------------------------- diff --git a/tpws/andr/getifaddrs.c b/tpws/andr/getifaddrs.c deleted file mode 100644 index 74df4d6c..00000000 --- a/tpws/andr/getifaddrs.c +++ /dev/null @@ -1,216 +0,0 @@ -#define _GNU_SOURCE -#include -#include -#include -#include -#include -#include -#include -#include -#include "netlink.h" - -#define IFADDRS_HASH_SIZE 64 - -/* getifaddrs() reports hardware addresses with PF_PACKET that implies - * struct sockaddr_ll. But e.g. Infiniband socket address length is - * longer than sockaddr_ll.ssl_addr[8] can hold. Use this hack struct - * to extend ssl_addr - callers should be able to still use it. */ -struct sockaddr_ll_hack { - unsigned short sll_family, sll_protocol; - int sll_ifindex; - unsigned short sll_hatype; - unsigned char sll_pkttype, sll_halen; - unsigned char sll_addr[24]; -}; - -union sockany { - struct sockaddr sa; - struct sockaddr_ll_hack ll; - struct sockaddr_in v4; - struct sockaddr_in6 v6; -}; - -struct ifaddrs_storage { - struct ifaddrs ifa; - struct ifaddrs_storage *hash_next; - union sockany addr, netmask, ifu; - unsigned int index; - char name[IFNAMSIZ+1]; -}; - -struct ifaddrs_ctx { - struct ifaddrs *first; - struct ifaddrs *last; - struct ifaddrs_storage *hash[IFADDRS_HASH_SIZE]; -}; - -void freeifaddrs(struct ifaddrs *ifp) -{ - struct ifaddrs *n; - while (ifp) { - n = ifp->ifa_next; - free(ifp); - ifp = n; - } -} - -static void copy_addr(struct sockaddr **r, int af, union sockany *sa, void *addr, size_t addrlen, int ifindex) -{ - uint8_t *dst; - int len; - - switch (af) { - case AF_INET: - dst = (uint8_t*) &sa->v4.sin_addr; - len = 4; - break; - case AF_INET6: - dst = (uint8_t*) &sa->v6.sin6_addr; - len = 16; - if (IN6_IS_ADDR_LINKLOCAL(addr) || IN6_IS_ADDR_MC_LINKLOCAL(addr)) - sa->v6.sin6_scope_id = ifindex; - break; - default: - return; - } - if (addrlen < len) return; - sa->sa.sa_family = af; - memcpy(dst, addr, len); - *r = &sa->sa; -} - -static void gen_netmask(struct sockaddr **r, int af, union sockany *sa, int prefixlen) -{ - uint8_t addr[16] = {0}; - int i; - - if (prefixlen > 8*sizeof(addr)) prefixlen = 8*sizeof(addr); - i = prefixlen / 8; - memset(addr, 0xff, i); - if (i < sizeof(addr)) addr[i++] = 0xff << (8 - (prefixlen % 8)); - copy_addr(r, af, sa, addr, sizeof(addr), 0); -} - -static void copy_lladdr(struct sockaddr **r, union sockany *sa, void *addr, size_t addrlen, int ifindex, unsigned short hatype) -{ - if (addrlen > sizeof(sa->ll.sll_addr)) return; - sa->ll.sll_family = AF_PACKET; - sa->ll.sll_ifindex = ifindex; - sa->ll.sll_hatype = hatype; - sa->ll.sll_halen = addrlen; - memcpy(sa->ll.sll_addr, addr, addrlen); - *r = &sa->sa; -} - -static int netlink_msg_to_ifaddr(void *pctx, struct nlmsghdr *h) -{ - struct ifaddrs_ctx *ctx = pctx; - struct ifaddrs_storage *ifs, *ifs0; - struct ifinfomsg *ifi = NLMSG_DATA(h); - struct ifaddrmsg *ifa = NLMSG_DATA(h); - struct rtattr *rta; - int stats_len = 0; - - if (h->nlmsg_type == RTM_NEWLINK) { - for (rta = NLMSG_RTA(h, sizeof(*ifi)); NLMSG_RTAOK(rta, h); rta = RTA_NEXT(rta)) { - if (rta->rta_type != IFLA_STATS) continue; - stats_len = RTA_DATALEN(rta); - break; - } - } else { - for (ifs0 = ctx->hash[ifa->ifa_index % IFADDRS_HASH_SIZE]; ifs0; ifs0 = ifs0->hash_next) - if (ifs0->index == ifa->ifa_index) - break; - if (!ifs0) return 0; - } - - ifs = calloc(1, sizeof(struct ifaddrs_storage) + stats_len); - if (ifs == 0) return -1; - - if (h->nlmsg_type == RTM_NEWLINK) { - ifs->index = ifi->ifi_index; - ifs->ifa.ifa_flags = ifi->ifi_flags; - - for (rta = NLMSG_RTA(h, sizeof(*ifi)); NLMSG_RTAOK(rta, h); rta = RTA_NEXT(rta)) { - switch (rta->rta_type) { - case IFLA_IFNAME: - if (RTA_DATALEN(rta) < sizeof(ifs->name)) { - memcpy(ifs->name, RTA_DATA(rta), RTA_DATALEN(rta)); - ifs->ifa.ifa_name = ifs->name; - } - break; - case IFLA_ADDRESS: - copy_lladdr(&ifs->ifa.ifa_addr, &ifs->addr, RTA_DATA(rta), RTA_DATALEN(rta), ifi->ifi_index, ifi->ifi_type); - break; - case IFLA_BROADCAST: - copy_lladdr(&ifs->ifa.ifa_broadaddr, &ifs->ifu, RTA_DATA(rta), RTA_DATALEN(rta), ifi->ifi_index, ifi->ifi_type); - break; - case IFLA_STATS: - ifs->ifa.ifa_data = (void*)(ifs+1); - memcpy(ifs->ifa.ifa_data, RTA_DATA(rta), RTA_DATALEN(rta)); - break; - } - } - if (ifs->ifa.ifa_name) { - unsigned int bucket = ifs->index % IFADDRS_HASH_SIZE; - ifs->hash_next = ctx->hash[bucket]; - ctx->hash[bucket] = ifs; - } - } else { - ifs->ifa.ifa_name = ifs0->ifa.ifa_name; - ifs->ifa.ifa_flags = ifs0->ifa.ifa_flags; - for (rta = NLMSG_RTA(h, sizeof(*ifa)); NLMSG_RTAOK(rta, h); rta = RTA_NEXT(rta)) { - switch (rta->rta_type) { - case IFA_ADDRESS: - /* If ifa_addr is already set we, received an IFA_LOCAL before - * so treat this as destination address */ - if (ifs->ifa.ifa_addr) - copy_addr(&ifs->ifa.ifa_dstaddr, ifa->ifa_family, &ifs->ifu, RTA_DATA(rta), RTA_DATALEN(rta), ifa->ifa_index); - else - copy_addr(&ifs->ifa.ifa_addr, ifa->ifa_family, &ifs->addr, RTA_DATA(rta), RTA_DATALEN(rta), ifa->ifa_index); - break; - case IFA_BROADCAST: - copy_addr(&ifs->ifa.ifa_broadaddr, ifa->ifa_family, &ifs->ifu, RTA_DATA(rta), RTA_DATALEN(rta), ifa->ifa_index); - break; - case IFA_LOCAL: - /* If ifa_addr is set and we get IFA_LOCAL, assume we have - * a point-to-point network. Move address to correct field. */ - if (ifs->ifa.ifa_addr) { - ifs->ifu = ifs->addr; - ifs->ifa.ifa_dstaddr = &ifs->ifu.sa; - memset(&ifs->addr, 0, sizeof(ifs->addr)); - } - copy_addr(&ifs->ifa.ifa_addr, ifa->ifa_family, &ifs->addr, RTA_DATA(rta), RTA_DATALEN(rta), ifa->ifa_index); - break; - case IFA_LABEL: - if (RTA_DATALEN(rta) < sizeof(ifs->name)) { - memcpy(ifs->name, RTA_DATA(rta), RTA_DATALEN(rta)); - ifs->ifa.ifa_name = ifs->name; - } - break; - } - } - if (ifs->ifa.ifa_addr) - gen_netmask(&ifs->ifa.ifa_netmask, ifa->ifa_family, &ifs->netmask, ifa->ifa_prefixlen); - } - - if (ifs->ifa.ifa_name) { - if (!ctx->first) ctx->first = &ifs->ifa; - if (ctx->last) ctx->last->ifa_next = &ifs->ifa; - ctx->last = &ifs->ifa; - } else { - free(ifs); - } - return 0; -} - -int getifaddrs(struct ifaddrs **ifap) -{ - struct ifaddrs_ctx _ctx, *ctx = &_ctx; - int r; - memset(ctx, 0, sizeof *ctx); - r = __rtnetlink_enumerate(AF_UNSPEC, AF_UNSPEC, netlink_msg_to_ifaddr, ctx); - if (r == 0) *ifap = ctx->first; - else freeifaddrs(ctx->first); - return r; -} diff --git a/tpws/andr/ifaddrs.h b/tpws/andr/ifaddrs.h deleted file mode 100644 index a7298091..00000000 --- a/tpws/andr/ifaddrs.h +++ /dev/null @@ -1,8 +0,0 @@ -#pragma once - -#include - -#if __ANDROID_API__ < 24 -void freeifaddrs(struct ifaddrs *); -int getifaddrs(struct ifaddrs **); -#endif diff --git a/tpws/andr/netlink.c b/tpws/andr/netlink.c deleted file mode 100644 index ad61a2dc..00000000 --- a/tpws/andr/netlink.c +++ /dev/null @@ -1,54 +0,0 @@ -#include -#include -#include -#include -#include - -#include "netlink.h" - -static int __netlink_enumerate(int fd, unsigned int seq, int type, int af, - int (*cb)(void *ctx, struct nlmsghdr *h), void *ctx) -{ - struct nlmsghdr *h; - union { - uint8_t buf[8192]; - struct { - struct nlmsghdr nlh; - struct rtgenmsg g; - } req; - struct nlmsghdr reply; - } u; - int r, ret; - - memset(&u.req, 0, sizeof(u.req)); - u.req.nlh.nlmsg_len = sizeof(u.req); - u.req.nlh.nlmsg_type = type; - u.req.nlh.nlmsg_flags = NLM_F_DUMP | NLM_F_REQUEST; - u.req.nlh.nlmsg_seq = seq; - u.req.g.rtgen_family = af; - r = send(fd, &u.req, sizeof(u.req), 0); - if (r < 0) return r; - - while (1) { - r = recv(fd, u.buf, sizeof(u.buf), MSG_DONTWAIT); - if (r <= 0) return -1; - for (h = &u.reply; NLMSG_OK(h, (void*)&u.buf[r]); h = NLMSG_NEXT(h)) { - if (h->nlmsg_type == NLMSG_DONE) return 0; - if (h->nlmsg_type == NLMSG_ERROR) return -1; - ret = cb(ctx, h); - if (ret) return ret; - } - } -} - -int __rtnetlink_enumerate(int link_af, int addr_af, int (*cb)(void *ctx, struct nlmsghdr *h), void *ctx) -{ - int fd, r; - - fd = socket(PF_NETLINK, SOCK_RAW|SOCK_CLOEXEC, NETLINK_ROUTE); - if (fd < 0) return -1; - r = __netlink_enumerate(fd, 1, RTM_GETLINK, link_af, cb, ctx); - if (!r) r = __netlink_enumerate(fd, 2, RTM_GETADDR, addr_af, cb, ctx); - close(fd); - return r; -} diff --git a/tpws/andr/netlink.h b/tpws/andr/netlink.h deleted file mode 100644 index 939c56c3..00000000 --- a/tpws/andr/netlink.h +++ /dev/null @@ -1,94 +0,0 @@ -#include - -/* linux/netlink.h */ - -#define NETLINK_ROUTE 0 - -struct nlmsghdr { - uint32_t nlmsg_len; - uint16_t nlmsg_type; - uint16_t nlmsg_flags; - uint32_t nlmsg_seq; - uint32_t nlmsg_pid; -}; - -#define NLM_F_REQUEST 1 -#define NLM_F_MULTI 2 -#define NLM_F_ACK 4 - -#define NLM_F_ROOT 0x100 -#define NLM_F_MATCH 0x200 -#define NLM_F_ATOMIC 0x400 -#define NLM_F_DUMP (NLM_F_ROOT|NLM_F_MATCH) - -#define NLMSG_NOOP 0x1 -#define NLMSG_ERROR 0x2 -#define NLMSG_DONE 0x3 -#define NLMSG_OVERRUN 0x4 - -/* linux/rtnetlink.h */ - -#define RTM_NEWLINK 16 -#define RTM_GETLINK 18 -#define RTM_NEWADDR 20 -#define RTM_GETADDR 22 - -struct rtattr { - unsigned short rta_len; - unsigned short rta_type; -}; - -struct rtgenmsg { - unsigned char rtgen_family; -}; - -struct ifinfomsg { - unsigned char ifi_family; - unsigned char __ifi_pad; - unsigned short ifi_type; - int ifi_index; - unsigned ifi_flags; - unsigned ifi_change; -}; - -/* linux/if_link.h */ - -#define IFLA_ADDRESS 1 -#define IFLA_BROADCAST 2 -#define IFLA_IFNAME 3 -#define IFLA_STATS 7 - -/* linux/if_addr.h */ - -struct ifaddrmsg { - uint8_t ifa_family; - uint8_t ifa_prefixlen; - uint8_t ifa_flags; - uint8_t ifa_scope; - uint32_t ifa_index; -}; - -#define IFA_ADDRESS 1 -#define IFA_LOCAL 2 -#define IFA_LABEL 3 -#define IFA_BROADCAST 4 - -/* musl */ - -#define NETLINK_ALIGN(len) (((len)+3) & ~3) -#define NLMSG_DATA(nlh) ((void*)((char*)(nlh)+sizeof(struct nlmsghdr))) -#define NLMSG_DATALEN(nlh) ((nlh)->nlmsg_len-sizeof(struct nlmsghdr)) -#define NLMSG_DATAEND(nlh) ((char*)(nlh)+(nlh)->nlmsg_len) -#define NLMSG_NEXT(nlh) (struct nlmsghdr*)((char*)(nlh)+NETLINK_ALIGN((nlh)->nlmsg_len)) -#define NLMSG_OK(nlh,end) ((char*)(end)-(char*)(nlh) >= sizeof(struct nlmsghdr)) - -#define RTA_DATA(rta) ((void*)((char*)(rta)+sizeof(struct rtattr))) -#define RTA_DATALEN(rta) ((rta)->rta_len-sizeof(struct rtattr)) -#define RTA_DATAEND(rta) ((char*)(rta)+(rta)->rta_len) -#define RTA_NEXT(rta) (struct rtattr*)((char*)(rta)+NETLINK_ALIGN((rta)->rta_len)) -#define RTA_OK(rta,end) ((char*)(end)-(char*)(rta) >= sizeof(struct rtattr)) - -#define NLMSG_RTA(nlh,len) ((void*)((char*)(nlh)+sizeof(struct nlmsghdr)+NETLINK_ALIGN(len))) -#define NLMSG_RTAOK(rta,nlh) RTA_OK(rta,NLMSG_DATAEND(nlh)) - -int __rtnetlink_enumerate(int link_af, int addr_af, int (*cb)(void *ctx, struct nlmsghdr *h), void *ctx); diff --git a/tpws/epoll-shim/include/sys/epoll.h b/tpws/epoll-shim/include/sys/epoll.h deleted file mode 100644 index f96c0f10..00000000 --- a/tpws/epoll-shim/include/sys/epoll.h +++ /dev/null @@ -1,80 +0,0 @@ -#ifndef SHIM_SYS_EPOLL_H -#define SHIM_SYS_EPOLL_H - -#ifdef __cplusplus -extern "C" { -#endif - -#include -#include -#include - -#if defined(__NetBSD__) -#include -#elif defined(__OpenBSD__) || defined(__DragonFly__) || defined(__FreeBSD__) || defined(__APPLE__) -#include -#endif - -#define EPOLL_CLOEXEC O_CLOEXEC -#define EPOLL_NONBLOCK O_NONBLOCK - -enum EPOLL_EVENTS { __EPOLL_DUMMY }; -#define EPOLLIN 0x001 -#define EPOLLPRI 0x002 -#define EPOLLOUT 0x004 -#define EPOLLRDNORM 0x040 -#define EPOLLNVAL 0x020 -#define EPOLLRDBAND 0x080 -#define EPOLLWRNORM 0x100 -#define EPOLLWRBAND 0x200 -#define EPOLLMSG 0x400 -#define EPOLLERR 0x008 -#define EPOLLHUP 0x010 -#define EPOLLRDHUP 0x2000 -#define EPOLLEXCLUSIVE (1U<<28) -#define EPOLLWAKEUP (1U<<29) -#define EPOLLONESHOT (1U<<30) -#define EPOLLET (1U<<31) - -#define EPOLL_CTL_ADD 1 -#define EPOLL_CTL_DEL 2 -#define EPOLL_CTL_MOD 3 - -typedef union epoll_data { - void *ptr; - int fd; - uint32_t u32; - uint64_t u64; -} epoll_data_t; - -struct epoll_event { - uint32_t events; - epoll_data_t data; -} -#ifdef __x86_64__ -__attribute__ ((__packed__)) -#endif -; - - -int epoll_create(int); -int epoll_create1(int); -int epoll_ctl(int, int, int, struct epoll_event *); -int epoll_wait(int, struct epoll_event *, int, int); -int epoll_pwait(int, struct epoll_event *, int, int, const sigset_t *); - - -#ifndef SHIM_SYS_SHIM_HELPERS -#define SHIM_SYS_SHIM_HELPERS -#include /* IWYU pragma: keep */ - -extern int epoll_shim_close(int); -#define close epoll_shim_close -#endif - - -#ifdef __cplusplus -} -#endif - -#endif /* sys/epoll.h */ diff --git a/tpws/epoll-shim/src/epoll.c b/tpws/epoll-shim/src/epoll.c deleted file mode 100644 index 7b11653f..00000000 --- a/tpws/epoll-shim/src/epoll.c +++ /dev/null @@ -1,305 +0,0 @@ -#include - -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -#include "epoll_shim_ctx.h" - -#ifdef __NetBSD__ -#define ppoll pollts -#endif - -// TODO(jan): Remove this once the definition is exposed in in -// all supported FreeBSD versions. -#ifndef timespecsub -#define timespecsub(tsp, usp, vsp) \ - do { \ - (vsp)->tv_sec = (tsp)->tv_sec - (usp)->tv_sec; \ - (vsp)->tv_nsec = (tsp)->tv_nsec - (usp)->tv_nsec; \ - if ((vsp)->tv_nsec < 0) { \ - (vsp)->tv_sec--; \ - (vsp)->tv_nsec += 1000000000L; \ - } \ - } while (0) -#endif - -static errno_t -epollfd_close(FDContextMapNode *node) -{ - return epollfd_ctx_terminate(&node->ctx.epollfd); -} - -static FDContextVTable const epollfd_vtable = { - .read_fun = fd_context_default_read, - .write_fun = fd_context_default_write, - .close_fun = epollfd_close, -}; - -static FDContextMapNode * -epoll_create_impl(errno_t *ec) -{ - FDContextMapNode *node; - - node = epoll_shim_ctx_create_node(&epoll_shim_ctx, ec); - if (!node) { - return NULL; - } - - node->flags = 0; - - if ((*ec = epollfd_ctx_init(&node->ctx.epollfd, /**/ - node->fd)) != 0) { - goto fail; - } - - node->vtable = &epollfd_vtable; - return node; - -fail: - epoll_shim_ctx_remove_node_explicit(&epoll_shim_ctx, node); - (void)fd_context_map_node_destroy(node); - return NULL; -} - -static int -epoll_create_common(void) -{ - FDContextMapNode *node; - errno_t ec; - - node = epoll_create_impl(&ec); - if (!node) { - errno = ec; - return -1; - } - - return node->fd; -} - -int -epoll_create(int size) -{ - if (size <= 0) { - errno = EINVAL; - return -1; - } - - return epoll_create_common(); -} - -int -epoll_create1(int flags) -{ - if (flags & ~EPOLL_CLOEXEC) { - errno = EINVAL; - return -1; - } - - return epoll_create_common(); -} - -static errno_t -epoll_ctl_impl(int fd, int op, int fd2, struct epoll_event *ev) -{ - if (!ev && op != EPOLL_CTL_DEL) { - return EFAULT; - } - - FDContextMapNode *node = epoll_shim_ctx_find_node(&epoll_shim_ctx, fd); - if (!node || node->vtable != &epollfd_vtable) { - struct stat sb; - return (fd < 0 || fstat(fd, &sb) < 0) ? EBADF : EINVAL; - } - - return epollfd_ctx_ctl(&node->ctx.epollfd, op, fd2, ev); -} - -int -epoll_ctl(int fd, int op, int fd2, struct epoll_event *ev) -{ - errno_t ec = epoll_ctl_impl(fd, op, fd2, ev); - if (ec != 0) { - errno = ec; - return -1; - } - - return 0; -} - -static bool -is_no_wait_deadline(struct timespec const *deadline) -{ - return (deadline && deadline->tv_sec == 0 && deadline->tv_nsec == 0); -} - -static errno_t -epollfd_ctx_wait_or_block(EpollFDCtx *epollfd, struct epoll_event *ev, int cnt, - int *actual_cnt, struct timespec const *deadline, sigset_t const *sigs) -{ - errno_t ec; - - for (;;) { - if ((ec = epollfd_ctx_wait(epollfd, /**/ - ev, cnt, actual_cnt)) != 0) { - return ec; - } - - if (*actual_cnt || is_no_wait_deadline(deadline)) { - return 0; - } - - struct timespec timeout; - - if (deadline) { - struct timespec current_time; - - if (clock_gettime(CLOCK_MONOTONIC, /**/ - ¤t_time) < 0) { - return errno; - } - - timespecsub(deadline, ¤t_time, &timeout); - if (timeout.tv_sec < 0 || - is_no_wait_deadline(&timeout)) { - return 0; - } - } - - (void)pthread_mutex_lock(&epollfd->mutex); - - nfds_t nfds = (nfds_t)(1 + epollfd->poll_fds_size); - - size_t size; - if (__builtin_mul_overflow(nfds, sizeof(struct pollfd), - &size)) { - ec = ENOMEM; - (void)pthread_mutex_unlock(&epollfd->mutex); - return ec; - } - - struct pollfd *pfds = malloc(size); - if (!pfds) { - ec = errno; - (void)pthread_mutex_unlock(&epollfd->mutex); - return ec; - } - - epollfd_ctx_fill_pollfds(epollfd, pfds); - - (void)pthread_mutex_lock(&epollfd->nr_polling_threads_mutex); - ++epollfd->nr_polling_threads; - (void)pthread_mutex_unlock(&epollfd->nr_polling_threads_mutex); - - (void)pthread_mutex_unlock(&epollfd->mutex); - - /* - * This surfaced a race condition when - * registering/unregistering poll-only fds. The tests should - * still succeed if this is enabled. - */ -#if 0 - usleep(500000); -#endif - - int n = ppoll(pfds, nfds, deadline ? &timeout : NULL, sigs); - if (n < 0) { - ec = errno; - } - - free(pfds); - - (void)pthread_mutex_lock(&epollfd->nr_polling_threads_mutex); - --epollfd->nr_polling_threads; - if (epollfd->nr_polling_threads == 0) { - (void)pthread_cond_signal( - &epollfd->nr_polling_threads_cond); - } - (void)pthread_mutex_unlock(&epollfd->nr_polling_threads_mutex); - - if (n < 0) { - return ec; - } - } -} - -static errno_t -timeout_to_deadline(struct timespec *deadline, int to) -{ - assert(to >= 0); - - if (to == 0) { - *deadline = (struct timespec){0, 0}; - } else if (to > 0) { - if (clock_gettime(CLOCK_MONOTONIC, deadline) < 0) { - return errno; - } - - if (__builtin_add_overflow(deadline->tv_sec, to / 1000 + 1, - &deadline->tv_sec)) { - return EINVAL; - } - deadline->tv_sec -= 1; - - deadline->tv_nsec += (to % 1000) * 1000000L; - if (deadline->tv_nsec >= 1000000000) { - deadline->tv_nsec -= 1000000000; - deadline->tv_sec += 1; - } - } - - return 0; -} - -static errno_t -epoll_pwait_impl(int fd, struct epoll_event *ev, int cnt, int to, - sigset_t const *sigs, int *actual_cnt) -{ - if (cnt < 1 || cnt > (int)(INT_MAX / sizeof(struct epoll_event))) { - return EINVAL; - } - - FDContextMapNode *node = epoll_shim_ctx_find_node(&epoll_shim_ctx, fd); - if (!node || node->vtable != &epollfd_vtable) { - struct stat sb; - return (fd < 0 || fstat(fd, &sb) < 0) ? EBADF : EINVAL; - } - - struct timespec deadline; - errno_t ec; - if (to >= 0 && (ec = timeout_to_deadline(&deadline, to)) != 0) { - return ec; - } - - return epollfd_ctx_wait_or_block(&node->ctx.epollfd, ev, cnt, - actual_cnt, (to >= 0) ? &deadline : NULL, sigs); -} - -int -epoll_pwait(int fd, struct epoll_event *ev, int cnt, int to, - sigset_t const *sigs) -{ - int actual_cnt; - - errno_t ec = epoll_pwait_impl(fd, ev, cnt, to, sigs, &actual_cnt); - if (ec != 0) { - errno = ec; - return -1; - } - - return actual_cnt; -} - -int -epoll_wait(int fd, struct epoll_event *ev, int cnt, int to) -{ - return epoll_pwait(fd, ev, cnt, to, NULL); -} diff --git a/tpws/epoll-shim/src/epoll_shim_ctx.c b/tpws/epoll-shim/src/epoll_shim_ctx.c deleted file mode 100644 index ac89f5fc..00000000 --- a/tpws/epoll-shim/src/epoll_shim_ctx.c +++ /dev/null @@ -1,281 +0,0 @@ -#include "epoll_shim_ctx.h" - -#include - -#include -#include -#include -#include - -static void -fd_context_map_node_init(FDContextMapNode *node, int kq) -{ - node->fd = kq; - node->vtable = NULL; -} - -static FDContextMapNode * -fd_context_map_node_create(int kq, errno_t *ec) -{ - FDContextMapNode *node; - - node = malloc(sizeof(FDContextMapNode)); - if (!node) { - *ec = errno; - return NULL; - } - - fd_context_map_node_init(node, kq); - return node; -} - -static errno_t -fd_context_map_node_terminate(FDContextMapNode *node, bool close_fd) -{ - errno_t ec = node->vtable ? node->vtable->close_fun(node) : 0; - - if (close_fd && close(node->fd) < 0) { - ec = ec ? ec : errno; - } - - return ec; -} - -errno_t -fd_context_map_node_destroy(FDContextMapNode *node) -{ - errno_t ec = fd_context_map_node_terminate(node, true); - free(node); - return ec; -} - -/**/ - -errno_t -fd_context_default_read(FDContextMapNode *node, /**/ - void *buf, size_t nbytes, size_t *bytes_transferred) -{ - (void)node; - (void)buf; - (void)nbytes; - (void)bytes_transferred; - - return EINVAL; -} - -errno_t -fd_context_default_write(FDContextMapNode *node, /**/ - void const *buf, size_t nbytes, size_t *bytes_transferred) -{ - (void)node; - (void)buf; - (void)nbytes; - (void)bytes_transferred; - - return EINVAL; -} - -/**/ - -static int -fd_context_map_node_cmp(FDContextMapNode *e1, FDContextMapNode *e2) -{ - return (e1->fd < e2->fd) ? -1 : (e1->fd > e2->fd); -} - -RB_PROTOTYPE_STATIC(fd_context_map_, fd_context_map_node_, entry, - fd_context_map_node_cmp); -RB_GENERATE_STATIC(fd_context_map_, fd_context_map_node_, entry, - fd_context_map_node_cmp); - -EpollShimCtx epoll_shim_ctx = { - .fd_context_map = RB_INITIALIZER(&fd_context_map), - .mutex = PTHREAD_MUTEX_INITIALIZER, -}; - -static FDContextMapNode * -epoll_shim_ctx_create_node_impl(EpollShimCtx *epoll_shim_ctx, int kq, - errno_t *ec) -{ - FDContextMapNode *node; - - { - FDContextMapNode find; - find.fd = kq; - - node = RB_FIND(fd_context_map_, /**/ - &epoll_shim_ctx->fd_context_map, &find); - } - - if (node) { - /* - * If we get here, someone must have already closed the old fd - * with a normal 'close()' call, i.e. not with our - * 'epoll_shim_close()' wrapper. The fd inside the node - * refers now to the new kq we are currently creating. We - * must not close it, but we must clean up the old context - * object! - */ - (void)fd_context_map_node_terminate(node, false); - fd_context_map_node_init(node, kq); - } else { - node = fd_context_map_node_create(kq, ec); - if (!node) { - return NULL; - } - - void *colliding_node = RB_INSERT(fd_context_map_, - &epoll_shim_ctx->fd_context_map, node); - (void)colliding_node; - assert(colliding_node == NULL); - } - - return node; -} - -FDContextMapNode * -epoll_shim_ctx_create_node(EpollShimCtx *epoll_shim_ctx, errno_t *ec) -{ - FDContextMapNode *node; - - int kq = kqueue(); - if (kq < 0) { - *ec = errno; - return NULL; - } - - (void)pthread_mutex_lock(&epoll_shim_ctx->mutex); - node = epoll_shim_ctx_create_node_impl(epoll_shim_ctx, kq, ec); - (void)pthread_mutex_unlock(&epoll_shim_ctx->mutex); - - if (!node) { - close(kq); - } - - return node; -} - -static FDContextMapNode * -epoll_shim_ctx_find_node_impl(EpollShimCtx *epoll_shim_ctx, int fd) -{ - FDContextMapNode *node; - - FDContextMapNode find; - find.fd = fd; - - node = RB_FIND(fd_context_map_, /**/ - &epoll_shim_ctx->fd_context_map, &find); - - return node; -} - -FDContextMapNode * -epoll_shim_ctx_find_node(EpollShimCtx *epoll_shim_ctx, int fd) -{ - FDContextMapNode *node; - - (void)pthread_mutex_lock(&epoll_shim_ctx->mutex); - node = epoll_shim_ctx_find_node_impl(epoll_shim_ctx, fd); - (void)pthread_mutex_unlock(&epoll_shim_ctx->mutex); - - return node; -} - -FDContextMapNode * -epoll_shim_ctx_remove_node(EpollShimCtx *epoll_shim_ctx, int fd) -{ - FDContextMapNode *node; - - (void)pthread_mutex_lock(&epoll_shim_ctx->mutex); - node = epoll_shim_ctx_find_node_impl(epoll_shim_ctx, fd); - if (node) { - RB_REMOVE(fd_context_map_, /**/ - &epoll_shim_ctx->fd_context_map, node); - } - (void)pthread_mutex_unlock(&epoll_shim_ctx->mutex); - - return node; -} - -void -epoll_shim_ctx_remove_node_explicit(EpollShimCtx *epoll_shim_ctx, - FDContextMapNode *node) -{ - (void)pthread_mutex_lock(&epoll_shim_ctx->mutex); - RB_REMOVE(fd_context_map_, /**/ - &epoll_shim_ctx->fd_context_map, node); - (void)pthread_mutex_unlock(&epoll_shim_ctx->mutex); -} - -/**/ - -int -epoll_shim_close(int fd) -{ - FDContextMapNode *node; - - node = epoll_shim_ctx_remove_node(&epoll_shim_ctx, fd); - if (!node) { - return close(fd); - } - - errno_t ec = fd_context_map_node_destroy(node); - if (ec != 0) { - errno = ec; - return -1; - } - - return 0; -} - -ssize_t -epoll_shim_read(int fd, void *buf, size_t nbytes) -{ - FDContextMapNode *node; - - node = epoll_shim_ctx_find_node(&epoll_shim_ctx, fd); - if (!node) { - return read(fd, buf, nbytes); - } - - if (nbytes > SSIZE_MAX) { - errno = EINVAL; - return -1; - } - - size_t bytes_transferred; - errno_t ec = node->vtable->read_fun(node, /**/ - buf, nbytes, &bytes_transferred); - if (ec != 0) { - errno = ec; - return -1; - } - - return (ssize_t)bytes_transferred; -} - -ssize_t -epoll_shim_write(int fd, void const *buf, size_t nbytes) -{ - FDContextMapNode *node; - - node = epoll_shim_ctx_find_node(&epoll_shim_ctx, fd); - if (!node) { - return write(fd, buf, nbytes); - } - - if (nbytes > SSIZE_MAX) { - errno = EINVAL; - return -1; - } - - size_t bytes_transferred; - errno_t ec = node->vtable->write_fun(node, /**/ - buf, nbytes, &bytes_transferred); - if (ec != 0) { - errno = ec; - return -1; - } - - return (ssize_t)bytes_transferred; -} diff --git a/tpws/epoll-shim/src/epoll_shim_ctx.h b/tpws/epoll-shim/src/epoll_shim_ctx.h deleted file mode 100644 index 01ae19a7..00000000 --- a/tpws/epoll-shim/src/epoll_shim_ctx.h +++ /dev/null @@ -1,76 +0,0 @@ -#ifndef EPOLL_SHIM_CTX_H_ -#define EPOLL_SHIM_CTX_H_ - -#include "fix.h" - -#include - -#include - -#include "epollfd_ctx.h" -#include "eventfd_ctx.h" -#include "signalfd_ctx.h" -#include "timerfd_ctx.h" - -struct fd_context_map_node_; -typedef struct fd_context_map_node_ FDContextMapNode; - -typedef errno_t (*fd_context_read_fun)(FDContextMapNode *node, /**/ - void *buf, size_t nbytes, size_t *bytes_transferred); -typedef errno_t (*fd_context_write_fun)(FDContextMapNode *node, /**/ - const void *buf, size_t nbytes, size_t *bytes_transferred); -typedef errno_t (*fd_context_close_fun)(FDContextMapNode *node); - -typedef struct { - fd_context_read_fun read_fun; - fd_context_write_fun write_fun; - fd_context_close_fun close_fun; -} FDContextVTable; - -errno_t fd_context_default_read(FDContextMapNode *node, /**/ - void *buf, size_t nbytes, size_t *bytes_transferred); -errno_t fd_context_default_write(FDContextMapNode *node, /**/ - void const *buf, size_t nbytes, size_t *bytes_transferred); - -struct fd_context_map_node_ { - RB_ENTRY(fd_context_map_node_) entry; - int fd; - int flags; - union { - EpollFDCtx epollfd; - EventFDCtx eventfd; - TimerFDCtx timerfd; - SignalFDCtx signalfd; - } ctx; - FDContextVTable const *vtable; -}; - -errno_t fd_context_map_node_destroy(FDContextMapNode *node); - -/**/ - -typedef RB_HEAD(fd_context_map_, fd_context_map_node_) FDContextMap; - -typedef struct { - FDContextMap fd_context_map; - pthread_mutex_t mutex; -} EpollShimCtx; - -extern EpollShimCtx epoll_shim_ctx; - -FDContextMapNode *epoll_shim_ctx_create_node(EpollShimCtx *epoll_shim_ctx, - errno_t *ec); -FDContextMapNode *epoll_shim_ctx_find_node(EpollShimCtx *epoll_shim_ctx, - int fd); -FDContextMapNode *epoll_shim_ctx_remove_node(EpollShimCtx *epoll_shim_ctx, - int fd); -void epoll_shim_ctx_remove_node_explicit(EpollShimCtx *epoll_shim_ctx, - FDContextMapNode *node); - -/**/ - -int epoll_shim_close(int fd); -ssize_t epoll_shim_read(int fd, void *buf, size_t nbytes); -ssize_t epoll_shim_write(int fd, void const *buf, size_t nbytes); - -#endif diff --git a/tpws/epoll-shim/src/epollfd_ctx.c b/tpws/epoll-shim/src/epollfd_ctx.c deleted file mode 100644 index baf3dc28..00000000 --- a/tpws/epoll-shim/src/epollfd_ctx.c +++ /dev/null @@ -1,1386 +0,0 @@ -#include "epollfd_ctx.h" - -#include - -#if defined(__FreeBSD__) -#include -#endif -#include -#include -#include -#include -#include - -#if defined(__DragonFly__) -/* For TAILQ_FOREACH_SAFE. */ -#include -#endif - -#include -#include -#include -#include - -#include -#include -#include - -static RegisteredFDsNode * -registered_fds_node_create(int fd) -{ - RegisteredFDsNode *node; - - node = malloc(sizeof(*node)); - if (!node) { - return NULL; - } - - *node = (RegisteredFDsNode){.fd = fd, .self_pipe = {-1, -1}}; - - return node; -} - -static void -registered_fds_node_destroy(RegisteredFDsNode *node) -{ - if (node->self_pipe[0] >= 0 && node->self_pipe[1] >= 0) { - (void)close(node->self_pipe[0]); - (void)close(node->self_pipe[1]); - } - - free(node); -} - -typedef struct { - int evfilt_read; - int evfilt_write; - int evfilt_except; -} NeededFilters; - -static NeededFilters -get_needed_filters(RegisteredFDsNode *fd2_node) -{ - NeededFilters needed_filters; - - needed_filters.evfilt_except = 0; - - if (fd2_node->node_type == NODE_TYPE_FIFO) { - if (fd2_node->node_data.fifo.readable && - fd2_node->node_data.fifo.writable) { - needed_filters.evfilt_read = !!( - fd2_node->events & EPOLLIN); - needed_filters.evfilt_write = !!( - fd2_node->events & EPOLLOUT); - - if (fd2_node->events == 0) { - needed_filters.evfilt_read = - fd2_node->eof_state ? 1 : EV_CLEAR; - } - - } else if (fd2_node->node_data.fifo.readable) { - needed_filters.evfilt_read = !!( - fd2_node->events & EPOLLIN); - needed_filters.evfilt_write = 0; - - if (needed_filters.evfilt_read == 0) { - needed_filters.evfilt_read = - fd2_node->eof_state ? 1 : EV_CLEAR; - } - } else if (fd2_node->node_data.fifo.writable) { - needed_filters.evfilt_read = 0; - needed_filters.evfilt_write = !!( - fd2_node->events & EPOLLOUT); - - if (needed_filters.evfilt_write == 0) { - needed_filters.evfilt_write = - fd2_node->eof_state ? 1 : EV_CLEAR; - } - } else { - __builtin_unreachable(); - } - - goto out; - } - - if (fd2_node->node_type == NODE_TYPE_KQUEUE) { - needed_filters.evfilt_read = !!(fd2_node->events & EPOLLIN); - needed_filters.evfilt_write = 0; - - assert(fd2_node->eof_state == 0); - - if (needed_filters.evfilt_read == 0) { - needed_filters.evfilt_read = EV_CLEAR; - } - - goto out; - } - - if (fd2_node->node_type == NODE_TYPE_SOCKET) { - needed_filters.evfilt_read = !!(fd2_node->events & EPOLLIN); - - if (needed_filters.evfilt_read == 0 && - (fd2_node->events & EPOLLRDHUP)) { - needed_filters.evfilt_read = (fd2_node->eof_state & - EOF_STATE_READ_EOF) - ? 1 - : EV_CLEAR; - } - -#ifdef EVFILT_EXCEPT - needed_filters.evfilt_except = !!(fd2_node->events & EPOLLPRI); -#else - if (needed_filters.evfilt_read == 0 && - (fd2_node->events & EPOLLPRI)) { - needed_filters.evfilt_read = fd2_node->pollpri_active - ? 1 - : EV_CLEAR; - } -#endif - - needed_filters.evfilt_write = !!(fd2_node->events & EPOLLOUT); - - /* Let's use EVFILT_READ to drive the POLLHUP. */ - if (fd2_node->eof_state == - (EOF_STATE_READ_EOF | EOF_STATE_WRITE_EOF)) { - if (needed_filters.evfilt_read != 1 && - needed_filters.evfilt_write != 1) { - needed_filters.evfilt_read = 1; - } - - if (needed_filters.evfilt_read) { - needed_filters.evfilt_write = 0; - } else { - needed_filters.evfilt_read = 0; - } - } - - /* We need something to detect POLLHUP. */ - if (fd2_node->eof_state == 0 && - needed_filters.evfilt_read == 0 && - needed_filters.evfilt_write == 0) { - needed_filters.evfilt_read = EV_CLEAR; - } - - if (fd2_node->eof_state == EOF_STATE_READ_EOF) { - if (needed_filters.evfilt_write == 0) { - needed_filters.evfilt_write = EV_CLEAR; - } - } - - if (fd2_node->eof_state == EOF_STATE_WRITE_EOF) { - if (needed_filters.evfilt_read == 0) { - needed_filters.evfilt_read = EV_CLEAR; - } - } - - goto out; - } - - needed_filters.evfilt_read = !!(fd2_node->events & EPOLLIN); - needed_filters.evfilt_write = !!(fd2_node->events & EPOLLOUT); - - if (fd2_node->events == 0) { - needed_filters.evfilt_read = fd2_node->eof_state ? 1 - : EV_CLEAR; - } - -out: - if (fd2_node->is_edge_triggered) { - if (needed_filters.evfilt_read) { - needed_filters.evfilt_read = EV_CLEAR; - } - if (needed_filters.evfilt_write) { - needed_filters.evfilt_write = EV_CLEAR; - } - if (needed_filters.evfilt_except) { - needed_filters.evfilt_except = EV_CLEAR; - } - } - - assert(needed_filters.evfilt_read || needed_filters.evfilt_write); - assert(needed_filters.evfilt_read == 0 || - needed_filters.evfilt_read == 1 || - needed_filters.evfilt_read == EV_CLEAR); - assert(needed_filters.evfilt_write == 0 || - needed_filters.evfilt_write == 1 || - needed_filters.evfilt_write == EV_CLEAR); - assert(needed_filters.evfilt_except == 0 || - needed_filters.evfilt_except == 1 || - needed_filters.evfilt_except == EV_CLEAR); - - return needed_filters; -} - -static void -registered_fds_node_update_flags_from_epoll_event(RegisteredFDsNode *fd2_node, - struct epoll_event *ev) -{ - fd2_node->events = ev->events & - (EPOLLIN | EPOLLPRI | EPOLLRDHUP | EPOLLOUT); - fd2_node->data = ev->data; - fd2_node->is_edge_triggered = ev->events & EPOLLET; - fd2_node->is_oneshot = ev->events & EPOLLONESHOT; - - if (fd2_node->is_oneshot) { - fd2_node->is_edge_triggered = true; - } -} - -static errno_t -registered_fds_node_add_self_trigger(RegisteredFDsNode *fd2_node, - EpollFDCtx *epollfd) -{ - struct kevent kevs[1]; - -#ifdef EVFILT_USER - EV_SET(&kevs[0], (uintptr_t)fd2_node, EVFILT_USER, /**/ - EV_ADD | EV_CLEAR, 0, 0, fd2_node); -#else - if (fd2_node->self_pipe[0] < 0 && fd2_node->self_pipe[1] < 0) { - if (pipe2(fd2_node->self_pipe, O_NONBLOCK | O_CLOEXEC) < 0) { - errno_t ec = errno; - fd2_node->self_pipe[0] = fd2_node->self_pipe[1] = -1; - return ec; - } - - assert(fd2_node->self_pipe[0] >= 0); - assert(fd2_node->self_pipe[1] >= 0); - } - - EV_SET(&kevs[0], fd2_node->self_pipe[0], EVFILT_READ, /**/ - EV_ADD | EV_CLEAR, 0, 0, fd2_node); -#endif - - if (kevent(epollfd->kq, kevs, 1, NULL, 0, NULL) < 0) { - return errno; - } - - return 0; -} - -static void -registered_fds_node_trigger_self(RegisteredFDsNode *fd2_node, - EpollFDCtx *epollfd) -{ -#ifdef EVFILT_USER - struct kevent kevs[1]; - EV_SET(&kevs[0], (uintptr_t)fd2_node, EVFILT_USER, /**/ - 0, NOTE_TRIGGER, 0, fd2_node); - (void)kevent(epollfd->kq, kevs, 1, NULL, 0, NULL); -#else - (void)epollfd; - assert(fd2_node->self_pipe[1] >= 0); - - char c = 0; - (void)write(fd2_node->self_pipe[1], &c, 1); -#endif -} - -static void -registered_fds_node_feed_event(RegisteredFDsNode *fd2_node, - EpollFDCtx *epollfd, struct kevent const *kev) -{ - int revents = 0; - - if (fd2_node->node_type == NODE_TYPE_POLL) { - assert(fd2_node->revents == 0); - -#ifdef EVFILT_USER - assert(kev->filter == EVFILT_USER); -#else - char c[32]; - while (read(fd2_node->self_pipe[0], c, sizeof(c)) >= 0) { - } -#endif - - struct pollfd pfd = { - .fd = fd2_node->fd, - .events = (short)fd2_node->events, - }; - - revents = poll(&pfd, 1, 0) < 0 ? EPOLLERR : pfd.revents; - - fd2_node->revents = revents & POLLNVAL ? 0 : (uint32_t)revents; - assert(!(fd2_node->revents & - ~(uint32_t)(POLLIN | POLLOUT | POLLERR | POLLHUP))); - return; - } - - if (fd2_node->node_type == NODE_TYPE_FIFO && -#ifdef EVFILT_USER - kev->filter == EVFILT_USER -#else - (fd2_node->self_pipe[0] >= 0 && - kev->ident == (uintptr_t)fd2_node->self_pipe[0]) -#endif - ) { - assert(fd2_node->revents == 0); - - assert(!fd2_node->has_evfilt_read); - assert(!fd2_node->has_evfilt_write); - assert(!fd2_node->has_evfilt_except); - - NeededFilters needed_filters = get_needed_filters(fd2_node); - assert(needed_filters.evfilt_write); - - struct kevent nkev[1]; - EV_SET(&nkev[0], fd2_node->fd, EVFILT_WRITE, - EV_ADD | (needed_filters.evfilt_write & EV_CLEAR) | - EV_RECEIPT, - 0, 0, fd2_node); - - if (kevent(epollfd->kq, nkev, 1, nkev, 1, NULL) != 1 || - nkev[0].data != 0) { - revents = EPOLLERR | EPOLLOUT; - - if (!fd2_node->is_edge_triggered) { - registered_fds_node_trigger_self(fd2_node, - epollfd); - } - - goto out; - } else { - fd2_node->has_evfilt_write = true; - return; - } - } - -#ifdef EVFILT_EXCEPT - assert(kev->filter == EVFILT_READ || kev->filter == EVFILT_WRITE || - kev->filter == EVFILT_EXCEPT); -#else - assert(kev->filter == EVFILT_READ || kev->filter == EVFILT_WRITE); -#endif - assert((int)kev->ident == fd2_node->fd); - - if (kev->filter == EVFILT_READ) { - revents |= EPOLLIN; -#ifndef EVFILT_EXCEPT - if (fd2_node->events & EPOLLPRI) { - struct pollfd pfd = { - .fd = fd2_node->fd, - .events = POLLPRI, - }; - - if ((poll(&pfd, 1, 0) == 1) && - (pfd.revents & POLLPRI)) { - revents |= EPOLLPRI; - fd2_node->pollpri_active = true; - } else { - fd2_node->pollpri_active = false; - } - } -#endif - } else if (kev->filter == EVFILT_WRITE) { - revents |= EPOLLOUT; - } -#ifdef EVFILT_EXCEPT - else if (kev->filter == EVFILT_EXCEPT) { - assert((kev->fflags & NOTE_OOB) != 0); - - revents |= EPOLLPRI; - goto out; - } -#endif - - if (fd2_node->node_type == NODE_TYPE_SOCKET) { - if (kev->filter == EVFILT_READ) { - if (kev->flags & EV_EOF) { - fd2_node->eof_state |= EOF_STATE_READ_EOF; - } else { - fd2_node->eof_state &= ~EOF_STATE_READ_EOF; - } - } else if (kev->filter == EVFILT_WRITE) { - if (kev->flags & EV_EOF) { - fd2_node->eof_state |= EOF_STATE_WRITE_EOF; - } else { - fd2_node->eof_state &= ~EOF_STATE_WRITE_EOF; - } - } - } else { - if (kev->filter == EVFILT_READ) { - if (kev->flags & EV_EOF) { - fd2_node->eof_state = EOF_STATE_READ_EOF | - EOF_STATE_WRITE_EOF; - } else { - fd2_node->eof_state = 0; - } - } else if (kev->filter == EVFILT_WRITE) { - if (kev->flags & EV_EOF) { - fd2_node->eof_state = EOF_STATE_READ_EOF | - EOF_STATE_WRITE_EOF; - } else { - fd2_node->eof_state = 0; - } - } - } - - if (kev->flags & EV_ERROR) { - revents |= EPOLLERR; - } - - if (kev->flags & EV_EOF) { - if (kev->fflags) { - revents |= EPOLLERR; - } - } - - if (fd2_node->eof_state) { - int epoll_event; - - if (fd2_node->node_type == NODE_TYPE_FIFO) { - if (kev->filter == EVFILT_READ) { - epoll_event = EPOLLHUP; - if (kev->data == 0) { - revents &= ~EPOLLIN; - } - } else if (kev->filter == EVFILT_WRITE) { - if (fd2_node->has_evfilt_read) { - assert( - fd2_node->node_data.fifo.readable); - assert( - fd2_node->node_data.fifo.writable); - - /* - * Any non-zero revents must have come - * from the EVFILT_READ filter. It - * could either be "POLLIN", - * "POLLIN | POLLHUP" or "POLLHUP", so - * we know if there is data to read. - * But we also know that the FIFO is - * done, so set POLLHUP because it - * would be set anyway. - * - * If revents is zero, not setting it - * will simply ignore this EVFILT_WRITE - * and wait for the next EVFILT_READ - * (which will be EOF). - */ - - if (fd2_node->revents != 0) { - fd2_node->revents |= POLLHUP; - } - return; - } - - epoll_event = EPOLLERR; - if (kev->data < PIPE_BUF) { - revents &= ~EPOLLOUT; - } - } else { - __builtin_unreachable(); - } - } else if (fd2_node->node_type == NODE_TYPE_SOCKET) { - epoll_event = 0; - - if (fd2_node->eof_state & EOF_STATE_READ_EOF) { - epoll_event |= EPOLLIN | EPOLLRDHUP; - } - - if (fd2_node->eof_state & EOF_STATE_WRITE_EOF) { - epoll_event |= EPOLLOUT; - } - - if (fd2_node->eof_state == - (EOF_STATE_READ_EOF | EOF_STATE_WRITE_EOF)) { - epoll_event |= EPOLLHUP; - } - } else { - epoll_event = EPOLLHUP; - } - - revents |= epoll_event; - } - -out: - fd2_node->revents |= (uint32_t)revents; - fd2_node->revents &= (fd2_node->events | EPOLLHUP | EPOLLERR); - - if (fd2_node->revents && (uintptr_t)fd2_node->fd == kev->ident) { - if (kev->filter == EVFILT_READ) { - fd2_node->got_evfilt_read = true; - } else if (kev->filter == EVFILT_WRITE) { - fd2_node->got_evfilt_write = true; - } -#ifdef EVFILT_EXCEPT - else if (kev->filter == EVFILT_EXCEPT) { - fd2_node->got_evfilt_except = true; - } -#endif - } -} - -static void -registered_fds_node_register_for_completion(int *kq, - RegisteredFDsNode *fd2_node) -{ - struct kevent kev[3]; - int n = 0; - - if (fd2_node->has_evfilt_read && !fd2_node->got_evfilt_read) { - EV_SET(&kev[n++], fd2_node->fd, EVFILT_READ, - EV_ADD | EV_ONESHOT | EV_RECEIPT, 0, 0, fd2_node); - } - if (fd2_node->has_evfilt_write && !fd2_node->got_evfilt_write) { - EV_SET(&kev[n++], fd2_node->fd, EVFILT_WRITE, - EV_ADD | EV_ONESHOT | EV_RECEIPT, 0, 0, fd2_node); - } - if (fd2_node->has_evfilt_except && !fd2_node->got_evfilt_except) { -#ifdef EVFILT_EXCEPT - EV_SET(&kev[n++], fd2_node->fd, EVFILT_EXCEPT, - EV_ADD | EV_ONESHOT | EV_RECEIPT, NOTE_OOB, 0, fd2_node); -#else - assert(0); -#endif - } - - if (n == 0) { - return; - } - - if (*kq < 0) { - *kq = kqueue(); - } - - if (*kq >= 0) { - (void)kevent(*kq, kev, n, kev, n, NULL); - } -} - -static void -registered_fds_node_complete(int kq) -{ - if (kq < 0) { - return; - } - - struct kevent kevs[32]; - int n; - - while ((n = kevent(kq, /**/ - NULL, 0, kevs, 32, &(struct timespec){0, 0})) > 0) { - for (int i = 0; i < n; ++i) { - RegisteredFDsNode *fd2_node = - (RegisteredFDsNode *)kevs[i].udata; - - registered_fds_node_feed_event(fd2_node, NULL, - &kevs[i]); - } - } - - (void)close(kq); -} - -static int -fd_cmp(RegisteredFDsNode *e1, RegisteredFDsNode *e2) -{ - return (e1->fd < e2->fd) ? -1 : (e1->fd > e2->fd); -} - -RB_PROTOTYPE_STATIC(registered_fds_set_, registered_fds_node_, entry, fd_cmp); -RB_GENERATE_STATIC(registered_fds_set_, registered_fds_node_, entry, fd_cmp); - -errno_t -epollfd_ctx_init(EpollFDCtx *epollfd, int kq) -{ - errno_t ec; - - *epollfd = (EpollFDCtx){ - .kq = kq, - .registered_fds = RB_INITIALIZER(®istered_fds), - .self_pipe = {-1, -1}, - }; - - TAILQ_INIT(&epollfd->poll_fds); - - if ((ec = pthread_mutex_init(&epollfd->mutex, NULL)) != 0) { - return ec; - } - - if ((ec = pthread_mutex_init(&epollfd->nr_polling_threads_mutex, - NULL)) != 0) { - pthread_mutex_destroy(&epollfd->mutex); - return ec; - } - - if ((ec = pthread_cond_init(&epollfd->nr_polling_threads_cond, - NULL)) != 0) { - pthread_mutex_destroy(&epollfd->nr_polling_threads_mutex); - pthread_mutex_destroy(&epollfd->mutex); - return ec; - } - - return 0; -} - -errno_t -epollfd_ctx_terminate(EpollFDCtx *epollfd) -{ - errno_t ec = 0; - errno_t ec_local; - - ec_local = pthread_cond_destroy(&epollfd->nr_polling_threads_cond); - ec = ec ? ec : ec_local; - ec_local = pthread_mutex_destroy(&epollfd->nr_polling_threads_mutex); - ec = ec ? ec : ec_local; - ec_local = pthread_mutex_destroy(&epollfd->mutex); - ec = ec ? ec : ec_local; - - RegisteredFDsNode *np; - RegisteredFDsNode *np_temp; - RB_FOREACH_SAFE(np, registered_fds_set_, &epollfd->registered_fds, - np_temp) - { - RB_REMOVE(registered_fds_set_, &epollfd->registered_fds, np); - registered_fds_node_destroy(np); - } - - free(epollfd->kevs); - free(epollfd->pfds); - if (epollfd->self_pipe[0] >= 0 && epollfd->self_pipe[1] >= 0) { - (void)close(epollfd->self_pipe[0]); - (void)close(epollfd->self_pipe[1]); - } - - return ec; -} - -static errno_t -epollfd_ctx_make_kevs_space(EpollFDCtx *epollfd, size_t cnt) -{ - assert(cnt > 0); - - if (cnt <= epollfd->kevs_length) { - return 0; - } - - size_t size; - if (__builtin_mul_overflow(cnt, sizeof(struct kevent), &size)) { - return ENOMEM; - } - - struct kevent *new_kevs = realloc(epollfd->kevs, size); - if (!new_kevs) { - return errno; - } - - epollfd->kevs = new_kevs; - epollfd->kevs_length = cnt; - - return 0; -} - -static errno_t -epollfd_ctx_make_pfds_space(EpollFDCtx *epollfd) -{ - size_t cnt = 1 + epollfd->poll_fds_size; - - if (cnt <= epollfd->pfds_length) { - return 0; - } - - size_t size; - if (__builtin_mul_overflow(cnt, sizeof(struct pollfd), &size)) { - return ENOMEM; - } - - struct pollfd *new_pfds = realloc(epollfd->pfds, size); - if (!new_pfds) { - return errno; - } - - epollfd->pfds = new_pfds; - epollfd->pfds_length = cnt; - - return 0; -} - -static errno_t -epollfd_ctx__add_self_trigger(EpollFDCtx *epollfd) -{ - struct kevent kevs[1]; - -#ifdef EVFILT_USER - EV_SET(&kevs[0], 0, EVFILT_USER, EV_ADD | EV_CLEAR, 0, 0, 0); -#else - if (epollfd->self_pipe[0] < 0 && epollfd->self_pipe[1] < 0) { - if (pipe2(epollfd->self_pipe, O_NONBLOCK | O_CLOEXEC) < 0) { - errno_t ec = errno; - epollfd->self_pipe[0] = epollfd->self_pipe[1] = -1; - return ec; - } - - assert(epollfd->self_pipe[0] >= 0); - assert(epollfd->self_pipe[1] >= 0); - } - - EV_SET(&kevs[0], epollfd->self_pipe[0], EVFILT_READ, /**/ - EV_ADD | EV_CLEAR, 0, 0, 0); -#endif - - if (kevent(epollfd->kq, kevs, 1, NULL, 0, NULL) < 0) { - return errno; - } - - return 0; -} - -static void -epollfd_ctx__trigger_self(EpollFDCtx *epollfd) -{ -#ifdef EVFILT_USER - struct kevent kevs[1]; - EV_SET(&kevs[0], 0, EVFILT_USER, 0, NOTE_TRIGGER, 0, 0); - (void)kevent(epollfd->kq, kevs, 1, NULL, 0, NULL); -#else - assert(epollfd->self_pipe[0] >= 0); - assert(epollfd->self_pipe[1] >= 0); - - char c = 0; - (void)write(epollfd->self_pipe[1], &c, 1); -#endif -} - -static void -epollfd_ctx__trigger_repoll(EpollFDCtx *epollfd) -{ - (void)pthread_mutex_lock(&epollfd->nr_polling_threads_mutex); - unsigned long nr_polling_threads = epollfd->nr_polling_threads; - (void)pthread_mutex_unlock(&epollfd->nr_polling_threads_mutex); - - if (nr_polling_threads == 0) { - return; - } - - epollfd_ctx__trigger_self(epollfd); - - (void)pthread_mutex_lock(&epollfd->nr_polling_threads_mutex); - while (epollfd->nr_polling_threads != 0) { - pthread_cond_wait(&epollfd->nr_polling_threads_cond, - &epollfd->nr_polling_threads_mutex); - } - (void)pthread_mutex_unlock(&epollfd->nr_polling_threads_mutex); - -#ifndef EVFILT_USER - char c[32]; - while (read(epollfd->self_pipe[0], c, sizeof(c)) >= 0) { - } -#endif -} - -static void -epollfd_ctx__remove_node_from_kq(EpollFDCtx *epollfd, - RegisteredFDsNode *fd2_node) -{ - if (fd2_node->is_on_pollfd_list) { - TAILQ_REMOVE(&epollfd->poll_fds, fd2_node, pollfd_list_entry); - fd2_node->is_on_pollfd_list = false; - assert(epollfd->poll_fds_size != 0); - --epollfd->poll_fds_size; - - epollfd_ctx__trigger_repoll(epollfd); - } - - if (fd2_node->self_pipe[0] >= 0) { - struct kevent kevs[1]; - EV_SET(&kevs[0], fd2_node->self_pipe[0], EVFILT_READ, /**/ - EV_DELETE, 0, 0, 0); - (void)kevent(epollfd->kq, kevs, 1, NULL, 0, NULL); - - char c[32]; - while (read(fd2_node->self_pipe[0], c, sizeof(c)) >= 0) { - } - } - - if (fd2_node->node_type == NODE_TYPE_POLL) { -#ifdef EVFILT_USER - struct kevent kevs[1]; - EV_SET(&kevs[0], (uintptr_t)fd2_node, EVFILT_USER, /**/ - EV_DELETE, 0, 0, 0); - (void)kevent(epollfd->kq, kevs, 1, NULL, 0, NULL); -#endif - } else { - struct kevent kevs[3]; - int fd2 = fd2_node->fd; - - EV_SET(&kevs[0], fd2, EVFILT_READ, /**/ - EV_DELETE | EV_RECEIPT, 0, 0, 0); - EV_SET(&kevs[1], fd2, EVFILT_WRITE, /**/ - EV_DELETE | EV_RECEIPT, 0, 0, 0); -#ifdef EVFILT_USER - EV_SET(&kevs[2], (uintptr_t)fd2_node, EVFILT_USER, /**/ - EV_DELETE | EV_RECEIPT, 0, 0, 0); -#endif - (void)kevent(epollfd->kq, kevs, 3, kevs, 3, NULL); - - fd2_node->has_evfilt_read = false; - fd2_node->has_evfilt_write = false; - fd2_node->has_evfilt_except = false; - } -} - -static errno_t -epollfd_ctx__register_events(EpollFDCtx *epollfd, RegisteredFDsNode *fd2_node) -{ - errno_t ec = 0; - - /* Only sockets support EPOLLRDHUP and EPOLLPRI. */ - if (fd2_node->node_type != NODE_TYPE_SOCKET) { - fd2_node->events &= ~(uint32_t)EPOLLRDHUP; - fd2_node->events &= ~(uint32_t)EPOLLPRI; - } - - int const fd2 = fd2_node->fd; - struct kevent kev[4] = { - {.data = 0}, - {.data = 0}, - {.data = 0}, - {.data = 0}, - }; - - assert(fd2 >= 0); - - int evfilt_read_index = -1; - int evfilt_write_index = -1; - - if (fd2_node->node_type != NODE_TYPE_POLL) { - if (fd2_node->is_registered) { - epollfd_ctx__remove_node_from_kq(epollfd, fd2_node); - } - - int n = 0; - - assert(!fd2_node->has_evfilt_read); - assert(!fd2_node->has_evfilt_write); - assert(!fd2_node->has_evfilt_except); - - NeededFilters needed_filters = get_needed_filters(fd2_node); - - if (needed_filters.evfilt_read) { - fd2_node->has_evfilt_read = true; - evfilt_read_index = n; - EV_SET(&kev[n++], fd2, EVFILT_READ, - EV_ADD | (needed_filters.evfilt_read & EV_CLEAR), - 0, 0, fd2_node); - } - if (needed_filters.evfilt_write) { - fd2_node->has_evfilt_write = true; - evfilt_write_index = n; - EV_SET(&kev[n++], fd2, EVFILT_WRITE, - EV_ADD | (needed_filters.evfilt_write & EV_CLEAR), - 0, 0, fd2_node); - } - - assert(n != 0); - - if (needed_filters.evfilt_except) { -#ifdef EVFILT_EXCEPT - fd2_node->has_evfilt_except = true; - EV_SET(&kev[n++], fd2, EVFILT_EXCEPT, - EV_ADD | (needed_filters.evfilt_except & EV_CLEAR), - NOTE_OOB, 0, fd2_node); -#else - assert(0); -#endif - } - - for (int i = 0; i < n; ++i) { - kev[i].flags |= EV_RECEIPT; - } - - int ret = kevent(epollfd->kq, kev, n, kev, n, NULL); - if (ret < 0) { - ec = errno; - goto out; - } - - assert(ret == n); - - for (int i = 0; i < n; ++i) { - assert((kev[i].flags & EV_ERROR) != 0); - } - } - - /* Check for fds that only support poll. */ - if (((fd2_node->node_type == NODE_TYPE_OTHER && - kev[0].data == ENODEV) || - fd2_node->node_type == NODE_TYPE_POLL)) { - - assert((fd2_node->events & /**/ - ~(uint32_t)(EPOLLIN | EPOLLOUT)) == 0); - assert(fd2_node->is_registered || - fd2_node->node_type == NODE_TYPE_OTHER); - - fd2_node->has_evfilt_read = false; - fd2_node->has_evfilt_write = false; - fd2_node->has_evfilt_except = false; - - fd2_node->node_type = NODE_TYPE_POLL; - - if ((ec = registered_fds_node_add_self_trigger(fd2_node, - epollfd)) != 0) { - goto out; - } - - if (!fd2_node->is_on_pollfd_list) { - if ((ec = /**/ - epollfd_ctx__add_self_trigger(epollfd)) != 0) { - goto out; - } - - TAILQ_INSERT_TAIL(&epollfd->poll_fds, fd2_node, - pollfd_list_entry); - fd2_node->is_on_pollfd_list = true; - ++epollfd->poll_fds_size; - } - - /* This is outside the above if because poll ".events" might - * have changed which needs a retriggering. */ - epollfd_ctx__trigger_repoll(epollfd); - - goto out; - } - - for (int i = 0; i < 4; ++i) { - if (kev[i].data != 0) { - if ((kev[i].data == EPIPE -#ifdef __NetBSD__ - || kev[i].data == EBADF -#endif - ) && - i == evfilt_write_index && - fd2_node->node_type == NODE_TYPE_FIFO) { - - fd2_node->eof_state = EOF_STATE_READ_EOF | - EOF_STATE_WRITE_EOF; - fd2_node->has_evfilt_write = false; - - if (evfilt_read_index < 0) { - if ((ec = registered_fds_node_add_self_trigger( - fd2_node, epollfd)) != 0) { - goto out; - } - - registered_fds_node_trigger_self( - fd2_node, epollfd); - } - } else { - ec = (int)kev[i].data; - goto out; - } - } - } - - ec = 0; - -out: - return ec; -} - -static void -epollfd_ctx_remove_node(EpollFDCtx *epollfd, RegisteredFDsNode *fd2_node) -{ - epollfd_ctx__remove_node_from_kq(epollfd, fd2_node); - - RB_REMOVE(registered_fds_set_, &epollfd->registered_fds, fd2_node); - assert(epollfd->registered_fds_size > 0); - --epollfd->registered_fds_size; - - registered_fds_node_destroy(fd2_node); -} - -#if defined(__FreeBSD__) -static void -modify_fifo_rights_from_capabilities(RegisteredFDsNode *fd2_node) -{ - assert(fd2_node->node_data.fifo.readable); - assert(fd2_node->node_data.fifo.writable); - - cap_rights_t rights; - memset(&rights, 0, sizeof(rights)); - - if (cap_rights_get(fd2_node->fd, &rights) == 0) { - cap_rights_t test_rights; - - cap_rights_init(&test_rights, CAP_READ); - bool has_read_rights = cap_rights_contains(&rights, - &test_rights); - - cap_rights_init(&test_rights, CAP_WRITE); - bool has_write_rights = cap_rights_contains(&rights, - &test_rights); - - if (has_read_rights != has_write_rights) { - fd2_node->node_data.fifo.readable = has_read_rights; - fd2_node->node_data.fifo.writable = has_write_rights; - } - } -} -#endif - -static errno_t -epollfd_ctx_add_node(EpollFDCtx *epollfd, int fd2, struct epoll_event *ev, - struct stat const *statbuf) -{ - RegisteredFDsNode *fd2_node = registered_fds_node_create(fd2); - if (!fd2_node) { - return ENOMEM; - } - - if (S_ISFIFO(statbuf->st_mode)) { - int tmp; - - if (ioctl(fd2_node->fd, FIONREAD, &tmp) < 0 && - errno == ENOTTY) { -#ifdef __FreeBSD__ - /* - * On FreeBSD we need to distinguish between kqueues - * and native eventfds. - */ - if (ioctl(fd2_node->fd, FIONBIO, &tmp) < 0 && - errno == ENOTTY) { - fd2_node->node_type = NODE_TYPE_KQUEUE; - } else { - fd2_node->node_type = NODE_TYPE_OTHER; - } -#else - fd2_node->node_type = NODE_TYPE_KQUEUE; -#endif - } else { - fd2_node->node_type = NODE_TYPE_FIFO; - - int fl = fcntl(fd2, F_GETFL, 0); - if (fl < 0) { - errno_t ec = errno; - registered_fds_node_destroy(fd2_node); - return ec; - } - - fl &= O_ACCMODE; - - if (fl == O_RDWR) { - fd2_node->node_data.fifo.readable = true; - fd2_node->node_data.fifo.writable = true; -#if defined(__FreeBSD__) - modify_fifo_rights_from_capabilities(fd2_node); -#endif - } else if (fl == O_WRONLY) { - fd2_node->node_data.fifo.writable = true; - } else if (fl == O_RDONLY) { - fd2_node->node_data.fifo.readable = true; - } else { - registered_fds_node_destroy(fd2_node); - return EINVAL; - } - } - } else if (S_ISSOCK(statbuf->st_mode)) { - fd2_node->node_type = NODE_TYPE_SOCKET; - } else { - /* May also be NODE_TYPE_POLL, - will be checked when registering. */ - fd2_node->node_type = NODE_TYPE_OTHER; - } - - registered_fds_node_update_flags_from_epoll_event(fd2_node, ev); - - void *colliding_node = RB_INSERT(registered_fds_set_, - &epollfd->registered_fds, fd2_node); - (void)colliding_node; - assert(colliding_node == NULL); - ++epollfd->registered_fds_size; - - errno_t ec = epollfd_ctx__register_events(epollfd, fd2_node); - if (ec != 0) { - epollfd_ctx_remove_node(epollfd, fd2_node); - return ec; - } - - fd2_node->is_registered = true; - - return 0; -} - -static errno_t -epollfd_ctx_modify_node(EpollFDCtx *epollfd, RegisteredFDsNode *fd2_node, - struct epoll_event *ev) -{ - registered_fds_node_update_flags_from_epoll_event(fd2_node, ev); - - assert(fd2_node->is_registered); - - errno_t ec = epollfd_ctx__register_events(epollfd, fd2_node); - if (ec != 0) { - epollfd_ctx_remove_node(epollfd, fd2_node); - return ec; - } - - return 0; -} - -static errno_t -epollfd_ctx_ctl_impl(EpollFDCtx *epollfd, int op, int fd2, - struct epoll_event *ev) -{ - assert(op == EPOLL_CTL_DEL || ev != NULL); - - if (epollfd->kq == fd2) { - return EINVAL; - } - - if (op != EPOLL_CTL_DEL && - ((ev->events & - ~(uint32_t)(EPOLLIN | EPOLLOUT | EPOLLRDHUP | /**/ - EPOLLPRI | /* unsupported by FreeBSD's kqueue! */ - EPOLLHUP | EPOLLERR | /**/ - EPOLLET | EPOLLONESHOT)))) { - return EINVAL; - } - - RegisteredFDsNode *fd2_node; - { - RegisteredFDsNode find; - find.fd = fd2; - - fd2_node = RB_FIND(registered_fds_set_, /**/ - &epollfd->registered_fds, &find); - } - - struct stat statbuf; - if (fstat(fd2, &statbuf) < 0) { - errno_t ec = errno; - - /* If the fstat fails for any reason we must clear - * internal state to avoid EEXIST errors in future - * calls to epoll_ctl. */ - if (fd2_node) { - epollfd_ctx_remove_node(epollfd, fd2_node); - } - - return ec; - } - - errno_t ec; - - if (op == EPOLL_CTL_ADD) { - ec = fd2_node - ? EEXIST - : epollfd_ctx_add_node(epollfd, fd2, ev, &statbuf); - } else if (op == EPOLL_CTL_DEL) { - ec = !fd2_node - ? ENOENT - : (epollfd_ctx_remove_node(epollfd, fd2_node), 0); - } else if (op == EPOLL_CTL_MOD) { - ec = !fd2_node - ? ENOENT - : epollfd_ctx_modify_node(epollfd, fd2_node, ev); - } else { - ec = EINVAL; - } - - return ec; -} - -void -epollfd_ctx_fill_pollfds(EpollFDCtx *epollfd, struct pollfd *pfds) -{ - pfds[0] = (struct pollfd){.fd = epollfd->kq, .events = POLLIN}; - - RegisteredFDsNode *poll_node; - size_t i = 1; - TAILQ_FOREACH(poll_node, &epollfd->poll_fds, pollfd_list_entry) - { - pfds[i++] = (struct pollfd){ - .fd = poll_node->fd, - .events = poll_node->node_type == NODE_TYPE_POLL - ? (short)poll_node->events - : POLLPRI, - }; - } -} - -errno_t -epollfd_ctx_ctl(EpollFDCtx *epollfd, int op, int fd2, struct epoll_event *ev) -{ - errno_t ec; - - (void)pthread_mutex_lock(&epollfd->mutex); - ec = epollfd_ctx_ctl_impl(epollfd, op, fd2, ev); - (void)pthread_mutex_unlock(&epollfd->mutex); - - return ec; -} - -static errno_t -epollfd_ctx_wait_impl(EpollFDCtx *epollfd, struct epoll_event *ev, int cnt, - int *actual_cnt) -{ - errno_t ec; - - assert(cnt >= 1); - - ec = epollfd_ctx_make_pfds_space(epollfd); - if (ec != 0) { - return ec; - } - - epollfd_ctx_fill_pollfds(epollfd, epollfd->pfds); - - int n = poll(epollfd->pfds, (nfds_t)(1 + epollfd->poll_fds_size), 0); - if (n < 0) { - return errno; - } - if (n == 0) { - *actual_cnt = 0; - return 0; - } - - { - RegisteredFDsNode *poll_node, *tmp_poll_node; - size_t i = 1; - TAILQ_FOREACH_SAFE(poll_node, &epollfd->poll_fds, - pollfd_list_entry, tmp_poll_node) - { - struct pollfd *pfd = &epollfd->pfds[i++]; - - if (pfd->revents & POLLNVAL) { - epollfd_ctx_remove_node(epollfd, poll_node); - } else if (pfd->revents) { - registered_fds_node_trigger_self(poll_node, - epollfd); - } - } - } - -again:; - - /* - * Each registered fd can produce a maximum of 3 kevents. If - * the provided space in 'ev' is large enough to hold results - * for all registered fds, provide enough space for the kevent - * call as well. Add some wiggle room for the 'poll only fd' - * notification mechanism. - */ - if ((size_t)cnt >= epollfd->registered_fds_size) { - if (__builtin_add_overflow(cnt, 1, &cnt)) { - return ENOMEM; - } - if (__builtin_mul_overflow(cnt, 3, &cnt)) { - return ENOMEM; - } - } - - ec = epollfd_ctx_make_kevs_space(epollfd, (size_t)cnt); - if (ec != 0) { - return ec; - } - - struct kevent *kevs = epollfd->kevs; - assert(kevs != NULL); - - n = kevent(epollfd->kq, NULL, 0, kevs, cnt, &(struct timespec){0, 0}); - if (n < 0) { - return errno; - } - - int j = 0; - - for (int i = 0; i < n; ++i) { - RegisteredFDsNode *fd2_node = - (RegisteredFDsNode *)kevs[i].udata; - - if (!fd2_node) { -#ifdef EVFILT_USER - assert(kevs[i].filter == EVFILT_USER); -#else - assert(kevs[i].filter == EVFILT_READ); -#endif - assert(kevs[i].udata == 0); - continue; - } - - uint32_t old_revents = fd2_node->revents; - NeededFilters old_needed_filters = get_needed_filters( - fd2_node); - - registered_fds_node_feed_event(fd2_node, epollfd, &kevs[i]); - - if (fd2_node->node_type != NODE_TYPE_POLL && - !(fd2_node->is_edge_triggered && - fd2_node->eof_state == - (EOF_STATE_READ_EOF | EOF_STATE_WRITE_EOF) && - fd2_node->node_type != NODE_TYPE_FIFO)) { - - NeededFilters needed_filters = get_needed_filters( - fd2_node); - - if (old_needed_filters.evfilt_read != - needed_filters.evfilt_read || - old_needed_filters.evfilt_write != - needed_filters.evfilt_write) { - - if (epollfd_ctx__register_events(epollfd, - fd2_node) != 0) { - epollfd_ctx__remove_node_from_kq( - epollfd, fd2_node); - } - } - } - - if (fd2_node->revents && !old_revents) { - ev[j++].data.ptr = fd2_node; - } - } - - { - int completion_kq = -1; - - for (int i = 0; i < j; ++i) { - RegisteredFDsNode *fd2_node = - (RegisteredFDsNode *)ev[i].data.ptr; - - if (n == cnt || fd2_node->is_edge_triggered) { - registered_fds_node_register_for_completion( - &completion_kq, fd2_node); - } - } - - registered_fds_node_complete(completion_kq); - } - - for (int i = 0; i < j; ++i) { - RegisteredFDsNode *fd2_node = - (RegisteredFDsNode *)ev[i].data.ptr; - - ev[i].events = fd2_node->revents; - ev[i].data = fd2_node->data; - - fd2_node->revents = 0; - fd2_node->got_evfilt_read = false; - fd2_node->got_evfilt_write = false; - fd2_node->got_evfilt_except = false; - - if (fd2_node->is_oneshot) { - epollfd_ctx__remove_node_from_kq(epollfd, fd2_node); - } - } - - if (n && j == 0) { - goto again; - } - - *actual_cnt = j; - return 0; -} - -errno_t -epollfd_ctx_wait(EpollFDCtx *epollfd, struct epoll_event *ev, int cnt, - int *actual_cnt) -{ - errno_t ec; - - (void)pthread_mutex_lock(&epollfd->mutex); - ec = epollfd_ctx_wait_impl(epollfd, ev, cnt, actual_cnt); - (void)pthread_mutex_unlock(&epollfd->mutex); - - return ec; -} diff --git a/tpws/epoll-shim/src/epollfd_ctx.h b/tpws/epoll-shim/src/epollfd_ctx.h deleted file mode 100644 index 1af7195a..00000000 --- a/tpws/epoll-shim/src/epollfd_ctx.h +++ /dev/null @@ -1,108 +0,0 @@ -#ifndef EPOLLFD_CTX_H_ -#define EPOLLFD_CTX_H_ - -#include "fix.h" - -#define SHIM_SYS_SHIM_HELPERS -#include - -#include -#include - -#include -#include -#include - -#include -#include - -struct registered_fds_node_; -typedef struct registered_fds_node_ RegisteredFDsNode; - -typedef enum { - EOF_STATE_READ_EOF = 0x01, - EOF_STATE_WRITE_EOF = 0x02, -} EOFState; - -typedef enum { - NODE_TYPE_FIFO = 1, - NODE_TYPE_SOCKET = 2, - NODE_TYPE_KQUEUE = 3, - NODE_TYPE_OTHER = 4, - NODE_TYPE_POLL = 5, -} NodeType; - -struct registered_fds_node_ { - RB_ENTRY(registered_fds_node_) entry; - TAILQ_ENTRY(registered_fds_node_) pollfd_list_entry; - - int fd; - epoll_data_t data; - - bool is_registered; - - bool has_evfilt_read; - bool has_evfilt_write; - bool has_evfilt_except; - - bool got_evfilt_read; - bool got_evfilt_write; - bool got_evfilt_except; - - NodeType node_type; - union { - struct { - bool readable; - bool writable; - } fifo; - } node_data; - int eof_state; - bool pollpri_active; - - uint16_t events; - uint32_t revents; - - bool is_edge_triggered; - bool is_oneshot; - - bool is_on_pollfd_list; - int self_pipe[2]; -}; - -typedef TAILQ_HEAD(pollfds_list_, registered_fds_node_) PollFDList; -typedef RB_HEAD(registered_fds_set_, registered_fds_node_) RegisteredFDsSet; - -typedef struct { - int kq; // non owning - pthread_mutex_t mutex; - - PollFDList poll_fds; - size_t poll_fds_size; - - RegisteredFDsSet registered_fds; - size_t registered_fds_size; - - struct kevent *kevs; - size_t kevs_length; - - struct pollfd *pfds; - size_t pfds_length; - - pthread_mutex_t nr_polling_threads_mutex; - pthread_cond_t nr_polling_threads_cond; - unsigned long nr_polling_threads; - - int self_pipe[2]; -} EpollFDCtx; - -errno_t epollfd_ctx_init(EpollFDCtx *epollfd, int kq); -errno_t epollfd_ctx_terminate(EpollFDCtx *epollfd); - -void epollfd_ctx_fill_pollfds(EpollFDCtx *epollfd, struct pollfd *pfds); - -errno_t epollfd_ctx_ctl(EpollFDCtx *epollfd, int op, int fd2, - struct epoll_event *ev); -errno_t epollfd_ctx_wait(EpollFDCtx *epollfd, struct epoll_event *ev, int cnt, - int *actual_cnt); - -#endif diff --git a/tpws/epoll-shim/src/eventfd_ctx.h b/tpws/epoll-shim/src/eventfd_ctx.h deleted file mode 100644 index 3e5bb557..00000000 --- a/tpws/epoll-shim/src/eventfd_ctx.h +++ /dev/null @@ -1,31 +0,0 @@ -#ifndef EVENTFD_CTX_H_ -#define EVENTFD_CTX_H_ - -#include "fix.h" - -#include -#include -#include - -#include - -#define EVENTFD_CTX_FLAG_SEMAPHORE (1 << 0) - -typedef struct { - int kq_; // non owning - int flags_; - pthread_mutex_t mutex_; - - bool is_signalled_; - int self_pipe_[2]; // only used if EVFILT_USER is not available - uint_least64_t counter_; -} EventFDCtx; - -errno_t eventfd_ctx_init(EventFDCtx *eventfd, int kq, unsigned int counter, - int flags); -errno_t eventfd_ctx_terminate(EventFDCtx *eventfd); - -errno_t eventfd_ctx_write(EventFDCtx *eventfd, uint64_t value); -errno_t eventfd_ctx_read(EventFDCtx *eventfd, uint64_t *value); - -#endif diff --git a/tpws/epoll-shim/src/fix.c b/tpws/epoll-shim/src/fix.c deleted file mode 100644 index 6fbd3f59..00000000 --- a/tpws/epoll-shim/src/fix.c +++ /dev/null @@ -1,19 +0,0 @@ -#include "fix.h" - -#ifdef __APPLE__ - -#include - -int ppoll(struct pollfd *fds, nfds_t nfds,const struct timespec *tmo_p, const sigset_t *sigmask) -{ - // macos does not implement ppoll - // this is a hacky ppoll shim. only for tpws which does not require sigmask - if (sigmask) - { - errno = EINVAL; - return -1; - } - return poll(fds,nfds,tmo_p ? tmo_p->tv_sec*1000 + tmo_p->tv_nsec/1000000 : -1); -} - -#endif diff --git a/tpws/epoll-shim/src/fix.h b/tpws/epoll-shim/src/fix.h deleted file mode 100644 index ebefc14b..00000000 --- a/tpws/epoll-shim/src/fix.h +++ /dev/null @@ -1,20 +0,0 @@ -#pragma once - -#ifndef _ERRNO_T_DEFINED -#define _ERRNO_T_DEFINED -typedef int errno_t; -#endif - -#ifdef __APPLE__ - -#include -#include -#include - -struct itimerspec { - struct timespec it_interval; - struct timespec it_value; -}; -int ppoll(struct pollfd *fds, nfds_t nfds,const struct timespec *tmo_p, const sigset_t *sigmask); - -#endif diff --git a/tpws/epoll-shim/src/signalfd_ctx.h b/tpws/epoll-shim/src/signalfd_ctx.h deleted file mode 100644 index 8623f630..00000000 --- a/tpws/epoll-shim/src/signalfd_ctx.h +++ /dev/null @@ -1,19 +0,0 @@ -#ifndef SIGNALFD_CTX_H_ -#define SIGNALFD_CTX_H_ - -#include "fix.h" - -#include -#include -#include - -typedef struct { - int kq; // non owning -} SignalFDCtx; - -errno_t signalfd_ctx_init(SignalFDCtx *signalfd, int kq, const sigset_t *sigs); -errno_t signalfd_ctx_terminate(SignalFDCtx *signalfd); - -errno_t signalfd_ctx_read(SignalFDCtx *signalfd, uint32_t *ident); - -#endif diff --git a/tpws/epoll-shim/src/timerfd_ctx.h b/tpws/epoll-shim/src/timerfd_ctx.h deleted file mode 100644 index 8b415075..00000000 --- a/tpws/epoll-shim/src/timerfd_ctx.h +++ /dev/null @@ -1,38 +0,0 @@ -#ifndef TIMERFD_CTX_H_ -#define TIMERFD_CTX_H_ - -#include "fix.h" - -#include -#include -#include -#include - -#include -#include - -typedef struct { - int kq; // non owning - int flags; - pthread_mutex_t mutex; - - int clockid; - /* - * Next expiration time, absolute (clock given by clockid). - * If it_interval is != 0, it is a periodic timer. - * If it_value is == 0, the timer is disarmed. - */ - struct itimerspec current_itimerspec; - uint64_t nr_expirations; -} TimerFDCtx; - -errno_t timerfd_ctx_init(TimerFDCtx *timerfd, int kq, int clockid); -errno_t timerfd_ctx_terminate(TimerFDCtx *timerfd); - -errno_t timerfd_ctx_settime(TimerFDCtx *timerfd, int flags, - struct itimerspec const *new, struct itimerspec *old); -errno_t timerfd_ctx_gettime(TimerFDCtx *timerfd, struct itimerspec *cur); - -errno_t timerfd_ctx_read(TimerFDCtx *timerfd, uint64_t *value); - -#endif diff --git a/tpws/gzip.c b/tpws/gzip.c deleted file mode 100644 index a3e4eb76..00000000 --- a/tpws/gzip.c +++ /dev/null @@ -1,79 +0,0 @@ -#include "gzip.h" -#include -#include -#include - -#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) < BUFMIN) - { - bufsize += BUFCHUNK; - newbuf = *buf ? realloc(*buf, bufsize) : malloc(bufsize); - if (!newbuf) - { - r = Z_MEM_ERROR; - goto zerr; - } - *buf = newbuf; - } - zs.avail_out = bufsize - *size; - zs.next_out = (unsigned char*)(*buf + *size); - r = inflate(&zs, Z_NO_FLUSH); - if (r != Z_OK && r != Z_STREAM_END) goto zerr; - *size = bufsize - zs.avail_out; - } while (r == Z_OK && zs.avail_in); - } while (r == Z_OK); - - if (*size < bufsize) - { - // free extra space - if ((newbuf = realloc(*buf, *size))) *buf = newbuf; - } - - inflateEnd(&zs); - return Z_OK; - -zerr: - inflateEnd(&zs); - free(*buf); - *buf = NULL; - return r; -} - -bool is_gzip(FILE* F) -{ - unsigned char magic[2]; - bool b = !fseek(F, 0, SEEK_SET) && fread(magic, 1, 2, F) == 2 && magic[0] == 0x1F && magic[1] == 0x8B; - fseek(F, 0, SEEK_SET); - return b; -} diff --git a/tpws/gzip.h b/tpws/gzip.h deleted file mode 100644 index 15e30d2e..00000000 --- a/tpws/gzip.h +++ /dev/null @@ -1,8 +0,0 @@ -#pragma once - -#include -#include -#include - -int z_readfile(FILE *F,char **buf,size_t *size); -bool is_gzip(FILE* F); diff --git a/tpws/helpers.c b/tpws/helpers.c deleted file mode 100644 index 64461ac1..00000000 --- a/tpws/helpers.c +++ /dev/null @@ -1,629 +0,0 @@ -#define _GNU_SOURCE - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#ifdef __ANDROID__ -#include "andr/ifaddrs.h" -#else -#include -#endif - -#include "helpers.h" -#ifdef __linux__ -#include -#endif -#include "linux_compat.h" - -int unique_size_t(size_t *pu, int ct) -{ - size_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 cmp_size_t(const void * a, const void * b) -{ - return *(size_t*)a < *(size_t*)b ? -1 : *(size_t*)a > *(size_t*)b; -} -void qsort_size_t(size_t *array, size_t ct) -{ - qsort(array, ct, sizeof(*array), cmp_size_t); -} - - -void rtrim(char *s) -{ - if (s) - for (char *p = s + strlen(s) - 1; p >= s && (*p == '\n' || *p == '\r'); p--) *p = '\0'; -} - -void replace_char(char *s, char from, char to) -{ - for (; *s; s++) if (*s == from) *s = to; -} - -char *strncasestr(const char *s, const char *find, size_t slen) -{ - char c, sc; - size_t len; - - if ((c = *find++) != '\0') - { - len = strlen(find); - do - { - do - { - if (slen-- < 1 || (sc = *s++) == '\0') return NULL; - } while (toupper(c) != toupper(sc)); - if (len > slen) return NULL; - } while (strncasecmp(s, find, len) != 0); - s--; - } - return (char *)s; -} - -bool str_ends_with(const char *s, const char *suffix) -{ - size_t slen = strlen(s); - size_t suffix_len = strlen(suffix); - return suffix_len <= slen && !strcmp(s + slen - suffix_len, suffix); -} - -bool load_file(const char *filename, void *buffer, size_t *buffer_size) -{ - FILE *F; - - F = fopen(filename, "rb"); - if (!F) return false; - - *buffer_size = fread(buffer, 1, *buffer_size, F); - if (ferror(F)) - { - fclose(F); - return false; - } - - fclose(F); - return true; -} - -bool append_to_list_file(const char *filename, const char *s) -{ - FILE *F = fopen(filename, "at"); - if (!F) return false; - bool bOK = fprintf(F, "%s\n", s) > 0; - fclose(F); - return bOK; -} - -void expand_bits(void *target, const void *source, unsigned int source_bitlen, unsigned int target_bytelen) -{ - unsigned int target_bitlen = target_bytelen<<3; - unsigned int bitlen = target_bitlen>3; - - if ((target_bytelen-bytelen)>=1) memset(target+bytelen,0,target_bytelen-bytelen); - memcpy(target,source,bytelen); - if ((bitlen &= 7)) ((uint8_t*)target)[bytelen] = ((uint8_t*)source)[bytelen] & (~((1 << (8-bitlen)) - 1)); -} - -// " [fd00::1]" => "fd00::1" -// "[fd00::1]:8000" => "fd00::1" -// "127.0.0.1" => "127.0.0.1" -// " 127.0.0.1:8000" => "127.0.0.1" -// " vk.com:8000" => "vk.com" -// return value: true - host is ip addr -bool strip_host_to_ip(char *host) -{ - size_t l; - char *h,*p; - uint8_t addr[16]; - - for (h = host ; *h==' ' || *h=='\t' ; h++); - l = strlen(h); - if (l>=2) - { - if (*h=='[') - { - // ipv6 ? - for (p=++h ; *p && *p!=']' ; p++); - if (*p==']') - { - l = p-h; - memmove(host,h,l); - host[l]=0; - return inet_pton(AF_INET6, host, addr)>0; - } - } - else - { - if (inet_pton(AF_INET6, h, addr)>0) - { - // ipv6 ? - if (host!=h) - { - l = strlen(h); - memmove(host,h,l); - host[l]=0; - } - return true; - } - else - { - // ipv4 ? - for (p=h ; *p && *p!=':' ; p++); - l = p-h; - if (host!=h) memmove(host,h,l); - host[l]=0; - return inet_pton(AF_INET, host, addr)>0; - } - } - } - return false; -} - -void ntop46(const struct sockaddr *sa, char *str, size_t len) -{ - if (!len) return; - *str = 0; - switch (sa->sa_family) - { - case AF_INET: - inet_ntop(sa->sa_family, &((struct sockaddr_in*)sa)->sin_addr, str, len); - break; - case AF_INET6: - inet_ntop(sa->sa_family, &((struct sockaddr_in6*)sa)->sin6_addr, str, len); - break; - default: - snprintf(str, len, "UNKNOWN_FAMILY_%d", sa->sa_family); - } -} -void ntop46_port(const struct sockaddr *sa, char *str, size_t len) -{ - char ip[40]; - ntop46(sa, ip, sizeof(ip)); - switch (sa->sa_family) - { - case AF_INET: - snprintf(str, len, "%s:%u", ip, ntohs(((struct sockaddr_in*)sa)->sin_port)); - break; - case AF_INET6: - snprintf(str, len, "[%s]:%u", ip, ntohs(((struct sockaddr_in6*)sa)->sin6_port)); - break; - default: - snprintf(str, len, "%s", ip); - } -} -void print_sockaddr(const struct sockaddr *sa) -{ - char ip_port[48]; - - ntop46_port(sa, ip_port, sizeof(ip_port)); - printf("%s", ip_port); -} - -// -1 = error, 0 = not local, 1 = local -bool check_local_ip(const struct sockaddr *saddr) -{ - struct ifaddrs *addrs, *a; - - if (is_localnet(saddr)) - return true; - - 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; -} -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; - } -} - - - -bool saismapped(const struct sockaddr_in6 *sa) -{ - // ::ffff:1.2.3.4 - return !memcmp(sa->sin6_addr.s6_addr, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff", 12); -} -bool samappedcmp(const struct sockaddr_in *sa1, const struct sockaddr_in6 *sa2) -{ - return saismapped(sa2) && !memcmp(sa2->sin6_addr.s6_addr + 12, &sa1->sin_addr.s_addr, 4); -} -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 && samappedcmp((struct sockaddr_in*)sa1, (struct sockaddr_in6*)sa2)) || - (sa1->sa_family == AF_INET6 && sa2->sa_family == AF_INET && samappedcmp((struct sockaddr_in*)sa2, (struct sockaddr_in6*)sa1)); -} -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); -} -bool saconvmapped(struct sockaddr_storage *a) -{ - if ((a->ss_family == AF_INET6) && saismapped((struct sockaddr_in6*)a)) - { - uint32_t ip4 = IN6_EXTRACT_MAP4(((struct sockaddr_in6*)a)->sin6_addr.s6_addr); - uint16_t port = ((struct sockaddr_in6*)a)->sin6_port; - a->ss_family = AF_INET; - ((struct sockaddr_in*)a)->sin_addr.s_addr = ip4; - ((struct sockaddr_in*)a)->sin_port = port; - return true; - } - return false; -} - -void sacopy(struct sockaddr_storage *sa_dest, const struct sockaddr *sa) -{ - switch (sa->sa_family) - { - case AF_INET: - memcpy(sa_dest, sa, sizeof(struct sockaddr_in)); - break; - case AF_INET6: - memcpy(sa_dest, sa, sizeof(struct sockaddr_in6)); - break; - default: - sa_dest->ss_family = 0; - } -} -void sa46copy(sockaddr_in46 *sa_dest, const struct sockaddr *sa) -{ - sacopy((struct sockaddr_storage*)sa_dest, sa); -} - -bool is_localnet(const struct sockaddr *a) -{ - // match 127.0.0.0/8, 0.0.0.0, ::1, ::0, :ffff:127.0.0.0/104, :ffff:0.0.0.0 - return (a->sa_family == AF_INET && (IN_LOOPBACK(ntohl(((struct sockaddr_in *)a)->sin_addr.s_addr)) || - INADDR_ANY == ntohl((((struct sockaddr_in *)a)->sin_addr.s_addr)))) || - (a->sa_family == AF_INET6 && (IN6_IS_ADDR_LOOPBACK(&((struct sockaddr_in6 *)a)->sin6_addr) || - IN6_IS_ADDR_UNSPECIFIED(&((struct sockaddr_in6 *)a)->sin6_addr) || - (IN6_IS_ADDR_V4MAPPED(&((struct sockaddr_in6 *)a)->sin6_addr) && (IN_LOOPBACK(ntohl(IN6_EXTRACT_MAP4(((struct sockaddr_in6*)a)->sin6_addr.s6_addr))) || - INADDR_ANY == ntohl(IN6_EXTRACT_MAP4(((struct sockaddr_in6*)a)->sin6_addr.s6_addr)))))); -} -bool is_linklocal(const struct sockaddr_in6 *a) -{ - // fe80::/10 - return a->sin6_addr.s6_addr[0] == 0xFE && (a->sin6_addr.s6_addr[1] & 0xC0) == 0x80; -} -bool is_private6(const struct sockaddr_in6* a) -{ - // fc00::/7 - return (a->sin6_addr.s6_addr[0] & 0xFE) == 0xFC; -} - - - -bool set_keepalive(int fd) -{ - int yes = 1; - return setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &yes, sizeof(int)) != -1; -} -bool set_ttl(int fd, int ttl) -{ - return setsockopt(fd, IPPROTO_IP, IP_TTL, &ttl, sizeof(ttl)) != -1; -} -bool set_hl(int fd, int hl) -{ - return setsockopt(fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &hl, sizeof(hl)) != -1; -} -bool set_ttl_hl(int fd, int ttl) -{ - bool b1, b2; - // try to set both but one may fail if family is wrong - b1 = set_ttl(fd, ttl); - b2 = set_hl(fd, ttl); - return b1 || b2; -} -int get_so_error(int fd) -{ - // getsockopt(SO_ERROR) clears error - int errn; - socklen_t optlen = sizeof(errn); - if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &errn, &optlen) == -1) - errn = errno; - return errn; -} - -int fprint_localtime(FILE *F) -{ - struct tm t; - time_t now; - - time(&now); - localtime_r(&now, &t); - return fprintf(F, "%02d.%02d.%04d %02d:%02d:%02d", t.tm_mday, t.tm_mon + 1, t.tm_year + 1900, t.tm_hour, t.tm_min, t.tm_sec); -} - -time_t file_mod_time(const char *filename) -{ - struct stat st; - return stat(filename, &st) == -1 ? 0 : st.st_mtime; -} -bool file_mod_signature(const char *filename, file_mod_sig *ms) -{ - struct stat st; - if (stat(filename,&st)==-1) - { - FILE_MOD_RESET(ms); - return false; - } - ms->mod_time=st.st_mtime; - ms->size=st.st_size; - return true; -} - -bool file_open_test(const char *filename, int flags) -{ - int fd = open(filename,flags); - if (fd>=0) - { - close(fd); - return true; - } - return false; -} - -bool pf_in_range(uint16_t port, const port_filter *pf) -{ - return port && (((!pf->from && !pf->to) || (port >= pf->from && port <= pf->to)) ^ pf->neg); -} -bool pf_parse(const char *s, port_filter *pf) -{ - unsigned int v1, v2; - char c; - - if (!s) return false; - if (*s == '*' && s[1] == 0) - { - pf->from = 1; pf->to = 0xFFFF; - return true; - } - if (*s == '~') - { - pf->neg = true; - s++; - } - else - pf->neg = false; - if (sscanf(s, "%u-%u%c", &v1, &v2, &c) == 2) - { - if (v1 > 65535 || v2 > 65535 || v1 > v2) return false; - pf->from = (uint16_t)v1; - pf->to = (uint16_t)v2; - } - else if (sscanf(s, "%u%c", &v1, &c) == 1) - { - if (v1 > 65535) return false; - pf->to = pf->from = (uint16_t)v1; - } - else - return false; - // deny all case - if (!pf->from && !pf->to) pf->neg = true; - return true; -} -bool pf_is_empty(const port_filter *pf) -{ - return !pf->neg && !pf->from && !pf->to; -} - -void set_console_io_buffering(void) -{ - setvbuf(stdout, NULL, _IOLBF, 0); - setvbuf(stderr, NULL, _IOLBF, 0); -} - -bool set_env_exedir(const char *argv0) -{ - char *s, *d; - bool bOK = false; - if ((s = strdup(argv0))) - { - if ((d = dirname(s))) - setenv("EXEDIR", s, 1); - free(s); - } - return bOK; -} - - -void str_cidr4(char *s, size_t s_len, const struct cidr4 *cidr) -{ - char s_ip[16]; - *s_ip = 0; - inet_ntop(AF_INET, &cidr->addr, s_ip, sizeof(s_ip)); - snprintf(s, s_len, cidr->preflen < 32 ? "%s/%u" : "%s", s_ip, cidr->preflen); -} -void print_cidr4(const struct cidr4 *cidr) -{ - char s[19]; - str_cidr4(s, sizeof(s), cidr); - printf("%s", s); -} -void str_cidr6(char *s, size_t s_len, const struct cidr6 *cidr) -{ - char s_ip[40]; - *s_ip = 0; - inet_ntop(AF_INET6, &cidr->addr, s_ip, sizeof(s_ip)); - snprintf(s, s_len, cidr->preflen < 128 ? "%s/%u" : "%s", s_ip, cidr->preflen); -} -void print_cidr6(const struct cidr6 *cidr) -{ - char s[44]; - str_cidr6(s, sizeof(s), cidr); - printf("%s", s); -} -bool parse_cidr4(char *s, struct cidr4 *cidr) -{ - char *p, d; - bool b; - unsigned int plen; - - if ((p = strchr(s, '/'))) - { - if (sscanf(p + 1, "%u", &plen) != 1 || plen > 32) - return false; - cidr->preflen = (uint8_t)plen; - d = *p; *p = 0; // backup char - } - else - cidr->preflen = 32; - b = (inet_pton(AF_INET, s, &cidr->addr) == 1); - if (p) *p = d; // restore char - return b; -} -bool parse_cidr6(char *s, struct cidr6 *cidr) -{ - char *p, d; - bool b; - unsigned int plen; - - if ((p = strchr(s, '/'))) - { - if (sscanf(p + 1, "%u", &plen) != 1 || plen > 128) - return false; - cidr->preflen = (uint8_t)plen; - d = *p; *p = 0; // backup char - } - else - cidr->preflen = 128; - b = (inet_pton(AF_INET6, s, &cidr->addr) == 1); - if (p) *p = d; // restore char - return b; -} - - -void msleep(unsigned int ms) -{ - struct timespec time = { - .tv_nsec = (ms % 1000) * 1000000, - .tv_sec = ms / 1000 - }; - nanosleep(&time, 0); -} - -#ifdef __linux__ -bool socket_supports_notsent() -{ - int sfd; - struct tcp_info_new tcpi; - - sfd = socket(AF_INET,SOCK_STREAM,0); - if (sfd<0) return false; - - socklen_t ts = sizeof(tcpi); - if (getsockopt(sfd, IPPROTO_TCP, TCP_INFO, (char *)&tcpi, &ts) < 0) - { - close(sfd); - return false; - } - close(sfd); - - return ts>=((char *)&tcpi.tcpi_notsent_bytes - (char *)&tcpi + sizeof(tcpi.tcpi_notsent_bytes)); -} -bool socket_has_notsent(int sfd) -{ - struct tcp_info_new tcpi; - socklen_t ts = sizeof(tcpi); - - if (getsockopt(sfd, IPPROTO_TCP, TCP_INFO, (char *)&tcpi, &ts) < 0) - return false; - if (tcpi.tcpi_state != 1) // TCP_ESTABLISHED - return false; - size_t s = (char *)&tcpi.tcpi_notsent_bytes - (char *)&tcpi + sizeof(tcpi.tcpi_notsent_bytes); - if (ts < s) - // old structure version - return false; - return !!tcpi.tcpi_notsent_bytes; -} -bool socket_wait_notsent(int sfd, unsigned int delay_ms, unsigned int *wasted_ms) -{ - struct timespec tres; - unsigned int mtick; - - if (wasted_ms) *wasted_ms=0; - if (!socket_has_notsent(sfd)) return true; - - if (clock_getres(CLOCK_MONOTONIC,&tres)) - { - tres.tv_nsec = 10000000; - tres.tv_sec = 0; - } - mtick = (unsigned int)(tres.tv_sec*1000) + (unsigned int)(tres.tv_nsec/1000000); - if (mtick<1) mtick=1; - for(;;) - { - msleep(mtick); - if (wasted_ms) *wasted_ms+=mtick; - if (!socket_has_notsent(sfd)) return true; - if (delay_ms<=mtick) break; - delay_ms-=mtick; - } - return false; -} - -int is_wsl(void) -{ - struct utsname buf; - if (uname(&buf) != 0) - return -1; - - if (strcmp(buf.sysname, "Linux") != 0) - return 0; - if (str_ends_with(buf.release, "microsoft-standard-WSL2")) - return 2; - if (str_ends_with(buf.release, "-Microsoft")) - return 1; - - return 0; -} -#endif diff --git a/tpws/helpers.h b/tpws/helpers.h deleted file mode 100644 index 271c3aea..00000000 --- a/tpws/helpers.h +++ /dev/null @@ -1,134 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include - -// this saves memory. sockaddr_storage is larger than required. it can be 128 bytes. sockaddr_in6 is 28 bytes. -typedef union -{ - sa_family_t sa_family; - struct sockaddr_in sa4; // size 16 - struct sockaddr_in6 sa6; // size 28 -} sockaddr_in46; - -int unique_size_t(size_t *pu, int ct); -void qsort_size_t(size_t *array,size_t ct); - -void rtrim(char *s); -void replace_char(char *s, char from, char to); -char *strncasestr(const char *s,const char *find, size_t slen); - -bool str_ends_with(const char *s, const char *suffix); - -bool load_file(const char *filename,void *buffer,size_t *buffer_size); -bool append_to_list_file(const char *filename, const char *s); - -void expand_bits(void *target, const void *source, unsigned int source_bitlen, unsigned int target_bytelen); - -bool strip_host_to_ip(char *host); - -void ntop46(const struct sockaddr *sa, char *str, size_t len); -void ntop46_port(const struct sockaddr *sa, char *str, size_t len); -void print_sockaddr(const struct sockaddr *sa); -void print_addrinfo(const struct addrinfo *ai); -bool check_local_ip(const struct sockaddr *saddr); - -bool saismapped(const struct sockaddr_in6 *sa); -bool samappedcmp(const struct sockaddr_in *sa1,const struct sockaddr_in6 *sa2); -bool sacmp(const struct sockaddr *sa1,const struct sockaddr *sa2); -uint16_t saport(const struct sockaddr *sa); -// true = was converted -bool saconvmapped(struct sockaddr_storage *a); - -void sacopy(struct sockaddr_storage *sa_dest, const struct sockaddr *sa); -void sa46copy(sockaddr_in46 *sa_dest, const struct sockaddr *sa); - -bool is_localnet(const struct sockaddr *a); -bool is_linklocal(const struct sockaddr_in6* a); -bool is_private6(const struct sockaddr_in6* a); - -bool set_keepalive(int fd); -bool set_ttl(int fd, int ttl); -bool set_hl(int fd, int hl); -bool set_ttl_hl(int fd, int ttl); -int get_so_error(int fd); - -// alignment-safe functions -static inline uint16_t pntoh16(const uint8_t *p) { - return ((uint16_t)p[0] << 8) | (uint16_t)p[1]; -} -static inline void phton16(uint8_t *p, uint16_t v) { - p[0] = (uint8_t)(v>>8); - p[1] = (uint8_t)v; -} - -int fprint_localtime(FILE *F); - -typedef struct -{ - time_t mod_time; - off_t size; -} file_mod_sig; -#define FILE_MOD_COMPARE(ms1,ms2) (((ms1)->mod_time==(ms2)->mod_time) && ((ms1)->size==(ms2)->size)) -#define FILE_MOD_RESET(ms) memset(ms,0,sizeof(file_mod_sig)) -bool file_mod_signature(const char *filename, file_mod_sig *ms); -time_t file_mod_time(const char *filename); -bool file_open_test(const char *filename, int flags); - -typedef struct -{ - uint16_t from,to; - bool neg; -} port_filter; -bool pf_in_range(uint16_t port, const port_filter *pf); -bool pf_parse(const char *s, port_filter *pf); -bool pf_is_empty(const port_filter *pf); - -void set_console_io_buffering(void); -bool set_env_exedir(const char *argv0); - -#ifndef IN_LOOPBACK -#define IN_LOOPBACK(a) ((((uint32_t) (a)) & 0xff000000) == 0x7f000000) -#endif - -#ifdef __GNUC__ -#define IN6_EXTRACT_MAP4(a) \ - (__extension__ \ - ({ const struct in6_addr *__a = (const struct in6_addr *) (a); \ - (((const uint32_t *) (__a))[3]); })) -#else -#define IN6_EXTRACT_MAP4(a) (((const uint32_t *) (a))[3]) -#endif - - -struct cidr4 -{ - struct in_addr addr; - uint8_t preflen; -}; -struct cidr6 -{ - struct in6_addr addr; - uint8_t preflen; -}; -void str_cidr4(char *s, size_t s_len, const struct cidr4 *cidr); -void print_cidr4(const struct cidr4 *cidr); -void str_cidr6(char *s, size_t s_len, const struct cidr6 *cidr); -void print_cidr6(const struct cidr6 *cidr); -bool parse_cidr4(char *s, struct cidr4 *cidr); -bool parse_cidr6(char *s, struct cidr6 *cidr); - -void msleep(unsigned int ms); -#ifdef __linux__ -bool socket_supports_notsent(); -bool socket_has_notsent(int sfd); -bool socket_wait_notsent(int sfd, unsigned int delay_ms, unsigned int *wasted_ms); - -int is_wsl(); -#endif diff --git a/tpws/hostlist.c b/tpws/hostlist.c deleted file mode 100644 index 631f41f3..00000000 --- a/tpws/hostlist.c +++ /dev/null @@ -1,340 +0,0 @@ -#include -#include "hostlist.h" -#include "gzip.h" -#include "helpers.h" - -// inplace tolower() and add to pool -static bool addpool(hostlist_pool **hostlist, char **s, const char *end, int *ct) -{ - char *p=*s; - - // comment line - if ( *p == '#' || *p == ';' || *p == '/' || *p == '\r' || *p == '\n') - { - // advance until eol - for (; pfilename) - { - file_mod_sig fsig; - if (!file_mod_signature(hfile->filename, &fsig)) - { - // stat() error - DLOG_PERROR("file_mod_signature"); - DLOG_ERR("cannot access hostlist file '%s'. in-memory content remains unchanged.\n",hfile->filename); - return true; - } - if (FILE_MOD_COMPARE(&hfile->mod_sig,&fsig)) return true; // up to date - HostlistPoolDestroy(&hfile->hostlist); - if (!AppendHostList(&hfile->hostlist, hfile->filename)) - { - HostlistPoolDestroy(&hfile->hostlist); - return false; - } - hfile->mod_sig=fsig; - } - return true; -} -static bool LoadHostLists(struct hostlist_files_head *list) -{ - bool bres=true; - struct hostlist_file *hfile; - - LIST_FOREACH(hfile, list, next) - { - if (!LoadHostList(hfile)) - // at least one failed - bres=false; - } - return bres; -} - -bool NonEmptyHostlist(hostlist_pool **hostlist) -{ - // add impossible hostname if the list is empty - return *hostlist ? true : HostlistPoolAddStrLen(hostlist, "@&()", 4, 0); -} - -static void MakeAutolistsNonEmpty() -{ - struct desync_profile_list *dpl; - LIST_FOREACH(dpl, ¶ms.desync_profiles, next) - { - if (dpl->dp.hostlist_auto) - NonEmptyHostlist(&dpl->dp.hostlist_auto->hostlist); - } -} - -bool LoadAllHostLists() -{ - if (!LoadHostLists(¶ms.hostlists)) - return false; - MakeAutolistsNonEmpty(); - return true; -} - - - -static bool SearchHostList(hostlist_pool *hostlist, const char *host, bool no_match_subdomains) -{ - if (hostlist) - { - const char *p = host; - const struct hostlist_pool *hp; - bool bHostFull=true; - while (p) - { - VPRINT("hostlist check for %s : ", p); - hp = HostlistPoolGetStr(hostlist, p); - if (hp) - { - if ((hp->flags & HOSTLIST_POOL_FLAG_STRICT_MATCH) && !bHostFull) - { - VPRINT("negative : strict_mismatch : %s != %s\n", p, host); - } - else - { - VPRINT("positive\n"); - return true; - } - } - else - VPRINT("negative\n"); - if (no_match_subdomains) break; - p = strchr(p, '.'); - if (p) p++; - bHostFull = false; - } - } - return false; -} - - -static bool HostlistsReloadCheck(const struct hostlist_collection_head *hostlists) -{ - struct hostlist_item *item; - LIST_FOREACH(item, hostlists, next) - { - if (!LoadHostList(item->hfile)) - return false; - } - MakeAutolistsNonEmpty(); - return true; -} -bool HostlistsReloadCheckForProfile(const struct desync_profile *dp) -{ - return HostlistsReloadCheck(&dp->hl_collection) && HostlistsReloadCheck(&dp->hl_collection_exclude); -} -// return : true = apply fooling, false = do not apply -static bool HostlistCheck_(const struct hostlist_collection_head *hostlists, const struct hostlist_collection_head *hostlists_exclude, const char *host, bool no_match_subdomains, bool *excluded, bool bSkipReloadCheck) -{ - struct hostlist_item *item; - - if (excluded) *excluded = false; - - if (!bSkipReloadCheck) - if (!HostlistsReloadCheck(hostlists) || !HostlistsReloadCheck(hostlists_exclude)) - return false; - - LIST_FOREACH(item, hostlists_exclude, next) - { - VPRINT("[%s] exclude ", item->hfile->filename ? item->hfile->filename : "fixed"); - if (SearchHostList(item->hfile->hostlist, host, no_match_subdomains)) - { - if (excluded) *excluded = true; - return false; - } - } - // old behavior compat: all include lists are empty means check passes - if (!hostlist_collection_is_empty(hostlists)) - { - LIST_FOREACH(item, hostlists, next) - { - VPRINT("[%s] include ", item->hfile->filename ? item->hfile->filename : "fixed"); - if (SearchHostList(item->hfile->hostlist, host, no_match_subdomains)) - return true; - } - return false; - } - return true; -} - - -// return : true = apply fooling, false = do not apply -bool HostlistCheck(const struct desync_profile *dp, const char *host, bool no_match_subdomains, bool *excluded, bool bSkipReloadCheck) -{ - VPRINT("* hostlist check for profile %d\n",dp->n); - return HostlistCheck_(&dp->hl_collection, &dp->hl_collection_exclude, host, no_match_subdomains, excluded, bSkipReloadCheck); -} - - -static struct hostlist_file *RegisterHostlist_(struct hostlist_files_head *hostlists, struct hostlist_collection_head *hl_collection, const char *filename) -{ - struct hostlist_file *hfile; - - if (filename) - { - if (!(hfile=hostlist_files_search(hostlists, filename))) - if (!(hfile=hostlist_files_add(hostlists, filename))) - return NULL; - if (!hostlist_collection_search(hl_collection, filename)) - if (!hostlist_collection_add(hl_collection, hfile)) - return NULL; - } - else - { - if (!(hfile=hostlist_files_add(hostlists, NULL))) - return NULL; - if (!hostlist_collection_add(hl_collection, hfile)) - return NULL; - } - - return hfile; -} -struct hostlist_file *RegisterHostlist(struct desync_profile *dp, bool bExclude, const char *filename) -{ -/* - if (filename && !file_mod_time(filename)) - { - DLOG_ERR("cannot access hostlist file '%s'\n",filename); - return NULL; - } -*/ - return RegisterHostlist_( - ¶ms.hostlists, - bExclude ? &dp->hl_collection_exclude : &dp->hl_collection, - filename); -} - -void HostlistsDebug() -{ - if (!params.debug) return; - - struct hostlist_file *hfile; - struct desync_profile_list *dpl; - struct hostlist_item *hl_item; - - LIST_FOREACH(hfile, ¶ms.hostlists, next) - { - if (hfile->filename) - VPRINT("hostlist file %s%s\n",hfile->filename,hfile->hostlist ? "" : " (empty)"); - else - VPRINT("hostlist fixed%s\n",hfile->hostlist ? "" : " (empty)"); - } - - LIST_FOREACH(dpl, ¶ms.desync_profiles, next) - { - LIST_FOREACH(hl_item, &dpl->dp.hl_collection, next) - if (hl_item->hfile!=dpl->dp.hostlist_auto) - { - if (hl_item->hfile->filename) - VPRINT("profile %d include hostlist %s%s\n",dpl->dp.n, hl_item->hfile->filename,hl_item->hfile->hostlist ? "" : " (empty)"); - else - VPRINT("profile %d include fixed hostlist%s\n",dpl->dp.n, hl_item->hfile->hostlist ? "" : " (empty)"); - } - LIST_FOREACH(hl_item, &dpl->dp.hl_collection_exclude, next) - { - if (hl_item->hfile->filename) - VPRINT("profile %d exclude hostlist %s%s\n",dpl->dp.n,hl_item->hfile->filename,hl_item->hfile->hostlist ? "" : " (empty)"); - else - VPRINT("profile %d exclude fixed hostlist%s\n",dpl->dp.n,hl_item->hfile->hostlist ? "" : " (empty)"); - } - if (dpl->dp.hostlist_auto) - VPRINT("profile %d auto hostlist %s%s\n",dpl->dp.n,dpl->dp.hostlist_auto->filename,dpl->dp.hostlist_auto->hostlist ? "" : " (empty)"); - } -} diff --git a/tpws/hostlist.h b/tpws/hostlist.h deleted file mode 100644 index 954d0cb9..00000000 --- a/tpws/hostlist.h +++ /dev/null @@ -1,17 +0,0 @@ -#pragma once - -#include -#include "pools.h" -#include "params.h" - -bool AppendHostlistItem(hostlist_pool **hostlist, char *s); -bool AppendHostList(hostlist_pool **hostlist, const char *filename); -bool LoadAllHostLists(); -bool NonEmptyHostlist(hostlist_pool **hostlist); -// return : true = apply fooling, false = do not apply -bool HostlistCheck(const struct desync_profile *dp,const char *host, bool no_match_subdomains, bool *excluded, bool bSkipReloadCheck); -struct hostlist_file *RegisterHostlist(struct desync_profile *dp, bool bExclude, const char *filename); -bool HostlistsReloadCheckForProfile(const struct desync_profile *dp); -void HostlistsDebug(); - -#define ResetAllHostlistsModTime() hostlist_files_reset_modtime(¶ms.hostlists) diff --git a/tpws/ipset.c b/tpws/ipset.c deleted file mode 100644 index 4be5aede..00000000 --- a/tpws/ipset.c +++ /dev/null @@ -1,319 +0,0 @@ -#include -#include "ipset.h" -#include "gzip.h" -#include "helpers.h" - - -// inplace tolower() and add to pool -static bool addpool(ipset *ips, char **s, const char *end, int *ct) -{ - char *p, cidr[128]; - size_t l; - struct cidr4 c4; - struct cidr6 c6; - - // advance until eol - for (p=*s; p=sizeof(cidr)) l=sizeof(cidr)-1; - memcpy(cidr,*s,l); - cidr[l]=0; - rtrim(cidr); - - if (parse_cidr4(cidr,&c4)) - { - if (!ipset4AddCidr(&ips->ips4, &c4)) - { - ipsetDestroy(ips); - return false; - } - if (ct) (*ct)++; - } - else if (parse_cidr6(cidr,&c6)) - { - if (!ipset6AddCidr(&ips->ips6, &c6)) - { - ipsetDestroy(ips); - return false; - } - if (ct) (*ct)++; - } - else - DLOG_ERR("bad ip or subnet : %s\n",cidr); - } - - // advance to the next line - for (; pfilename) - { - file_mod_sig fsig; - if (!file_mod_signature(hfile->filename, &fsig)) - { - // stat() error - DLOG_PERROR("file_mod_signature"); - DLOG_ERR("cannot access ipset file '%s'. in-memory content remains unchanged.\n",hfile->filename); - return true; - } - if (FILE_MOD_COMPARE(&hfile->mod_sig,&fsig)) return true; // up to date - ipsetDestroy(&hfile->ipset); - if (!AppendIpset(&hfile->ipset, hfile->filename)) - { - ipsetDestroy(&hfile->ipset); - return false; - } - hfile->mod_sig=fsig; - } - return true; -} -static bool LoadIpsets(struct ipset_files_head *list) -{ - bool bres=true; - struct ipset_file *hfile; - - LIST_FOREACH(hfile, list, next) - { - if (!LoadIpset(hfile)) - // at least one failed - bres=false; - } - return bres; -} - -bool LoadAllIpsets() -{ - return LoadIpsets(¶ms.ipsets); -} - -static bool SearchIpset(const ipset *ips, const struct in_addr *ipv4, const struct in6_addr *ipv6) -{ - char s_ip[40]; - bool bInSet=false; - - if (!!ipv4 != !!ipv6) - { - *s_ip=0; - if (ipv4) - { - if (params.debug) inet_ntop(AF_INET, ipv4, s_ip, sizeof(s_ip)); - if (ips->ips4) bInSet = ipset4Check(ips->ips4, ipv4, 32); - } - if (ipv6) - { - if (params.debug) inet_ntop(AF_INET6, ipv6, s_ip, sizeof(s_ip)); - if (ips->ips6) bInSet = ipset6Check(ips->ips6, ipv6, 128); - } - VPRINT("ipset check for %s : %s\n", s_ip, bInSet ? "positive" : "negative"); - } - else - // ipv4 and ipv6 are both empty or non-empty - VPRINT("ipset check error !!!!!!!! ipv4=%p ipv6=%p\n",ipv4,ipv6); - return bInSet; -} - -static bool IpsetsReloadCheck(const struct ipset_collection_head *ipsets) -{ - struct ipset_item *item; - LIST_FOREACH(item, ipsets, next) - { - if (!LoadIpset(item->hfile)) - return false; - } - return true; -} -bool IpsetsReloadCheckForProfile(const struct desync_profile *dp) -{ - return IpsetsReloadCheck(&dp->ips_collection) && IpsetsReloadCheck(&dp->ips_collection_exclude); -} - -static bool IpsetCheck_(const struct ipset_collection_head *ips, const struct ipset_collection_head *ips_exclude, const struct in_addr *ipv4, const struct in6_addr *ipv6) -{ - struct ipset_item *item; - - if (!IpsetsReloadCheck(ips) || !IpsetsReloadCheck(ips_exclude)) - return false; - - LIST_FOREACH(item, ips_exclude, next) - { - VPRINT("[%s] exclude ",item->hfile->filename ? item->hfile->filename : "fixed"); - if (SearchIpset(&item->hfile->ipset, ipv4, ipv6)) - return false; - } - // old behavior compat: all include lists are empty means check passes - if (!ipset_collection_is_empty(ips)) - { - LIST_FOREACH(item, ips, next) - { - VPRINT("[%s] include ",item->hfile->filename ? item->hfile->filename : "fixed"); - if (SearchIpset(&item->hfile->ipset, ipv4, ipv6)) - return true; - } - return false; - } - return true; -} - -bool IpsetCheck(const struct desync_profile *dp, const struct in_addr *ipv4, const struct in6_addr *ipv6) -{ - if (PROFILE_IPSETS_ABSENT(dp)) return true; - VPRINT("* ipset check for profile %d\n",dp->n); - return IpsetCheck_(&dp->ips_collection,&dp->ips_collection_exclude,ipv4,ipv6); -} - - -static struct ipset_file *RegisterIpset_(struct ipset_files_head *ipsets, struct ipset_collection_head *ips_collection, const char *filename) -{ - struct ipset_file *hfile; - if (filename) - { - if (!(hfile=ipset_files_search(ipsets, filename))) - if (!(hfile=ipset_files_add(ipsets, filename))) - return NULL; - if (!ipset_collection_search(ips_collection, filename)) - if (!ipset_collection_add(ips_collection, hfile)) - return NULL; - } - else - { - if (!(hfile=ipset_files_add(ipsets, NULL))) - return NULL; - if (!ipset_collection_add(ips_collection, hfile)) - return NULL; - } - return hfile; -} -struct ipset_file *RegisterIpset(struct desync_profile *dp, bool bExclude, const char *filename) -{ - if (filename && !file_mod_time(filename)) - { - DLOG_ERR("cannot access ipset file '%s'\n",filename); - return NULL; - } - return RegisterIpset_( - ¶ms.ipsets, - bExclude ? &dp->ips_collection_exclude : &dp->ips_collection, - filename); -} - -static const char *dbg_ipset_fill(const ipset *ips) -{ - if (ips->ips4) - if (ips->ips6) - return "ipv4+ipv6"; - else - return "ipv4"; - else - if (ips->ips6) - return "ipv6"; - else - return "empty"; -} -void IpsetsDebug() -{ - if (!params.debug) return; - - struct ipset_file *hfile; - struct desync_profile_list *dpl; - struct ipset_item *ips_item; - - LIST_FOREACH(hfile, ¶ms.ipsets, next) - { - if (hfile->filename) - VPRINT("ipset file %s (%s)\n",hfile->filename,dbg_ipset_fill(&hfile->ipset)); - else - VPRINT("ipset fixed (%s)\n",dbg_ipset_fill(&hfile->ipset)); - } - - LIST_FOREACH(dpl, ¶ms.desync_profiles, next) - { - LIST_FOREACH(ips_item, &dpl->dp.ips_collection, next) - if (ips_item->hfile->filename) - VPRINT("profile %d include ipset %s (%s)\n",dpl->dp.n,ips_item->hfile->filename,dbg_ipset_fill(&ips_item->hfile->ipset)); - else - VPRINT("profile %d include fixed ipset (%s)\n",dpl->dp.n,dbg_ipset_fill(&ips_item->hfile->ipset)); - LIST_FOREACH(ips_item, &dpl->dp.ips_collection_exclude, next) - if (ips_item->hfile->filename) - VPRINT("profile %d exclude ipset %s (%s)\n",dpl->dp.n,ips_item->hfile->filename,dbg_ipset_fill(&ips_item->hfile->ipset)); - else - VPRINT("profile %d exclude fixed ipset (%s)\n",dpl->dp.n,dbg_ipset_fill(&ips_item->hfile->ipset)); - } -} diff --git a/tpws/ipset.h b/tpws/ipset.h deleted file mode 100644 index b711ac50..00000000 --- a/tpws/ipset.h +++ /dev/null @@ -1,14 +0,0 @@ -#pragma once - -#include -#include -#include "params.h" -#include "pools.h" - -bool LoadAllIpsets(); -bool IpsetCheck(const struct desync_profile *dp, const struct in_addr *ipv4, const struct in6_addr *ipv6); -struct ipset_file *RegisterIpset(struct desync_profile *dp, bool bExclude, const char *filename); -void IpsetsDebug(); -bool AppendIpsetItem(ipset *ips, char *ip); - -#define ResetAllIpsetModTime() ipset_files_reset_modtime(¶ms.ipsets) diff --git a/tpws/kavl.h b/tpws/kavl.h deleted file mode 100644 index f3520fd7..00000000 --- a/tpws/kavl.h +++ /dev/null @@ -1,400 +0,0 @@ -/* The MIT License - - Copyright (c) 2018 by Attractive Chaos - - Permission is hereby granted, free of charge, to any person obtaining - a copy of this software and associated documentation files (the - "Software"), to deal in the Software without restriction, including - without limitation the rights to use, copy, modify, merge, publish, - distribute, sublicense, and/or sell copies of the Software, and to - permit persons to whom the Software is furnished to do so, subject to - the following conditions: - - The above copyright notice and this permission notice shall be - included in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - SOFTWARE. -*/ - -/* An example: - -#include -#include -#include -#include "kavl.h" - -struct my_node { - char key; - KAVL_HEAD(struct my_node) head; -}; -#define my_cmp(p, q) (((q)->key < (p)->key) - ((p)->key < (q)->key)) -KAVL_INIT(my, struct my_node, head, my_cmp) - -int main(void) { - const char *str = "MNOLKQOPHIA"; // from wiki, except a duplicate - struct my_node *root = 0; - int i, l = strlen(str); - for (i = 0; i < l; ++i) { // insert in the input order - struct my_node *q, *p = malloc(sizeof(*p)); - p->key = str[i]; - q = kavl_insert(my, &root, p, 0); - if (p != q) free(p); // if already present, free - } - kavl_itr_t(my) itr; - kavl_itr_first(my, root, &itr); // place at first - do { // traverse - const struct my_node *p = kavl_at(&itr); - putchar(p->key); - free((void*)p); // free node - } while (kavl_itr_next(my, &itr)); - putchar('\n'); - return 0; -} -*/ - -#ifndef KAVL_H -#define KAVL_H - -#ifdef __STRICT_ANSI__ -#define inline __inline__ -#endif - -#define KAVL_MAX_DEPTH 64 - -#define kavl_size(head, p) ((p)? (p)->head.size : 0) -#define kavl_size_child(head, q, i) ((q)->head.p[(i)]? (q)->head.p[(i)]->head.size : 0) - -#define KAVL_HEAD(__type) \ - struct { \ - __type *p[2]; \ - signed char balance; /* balance factor */ \ - unsigned size; /* #elements in subtree */ \ - } - -#define __KAVL_FIND(suf, __scope, __type, __head, __cmp) \ - __scope __type *kavl_find_##suf(const __type *root, const __type *x, unsigned *cnt_) { \ - const __type *p = root; \ - unsigned cnt = 0; \ - while (p != 0) { \ - int cmp; \ - cmp = __cmp(x, p); \ - if (cmp >= 0) cnt += kavl_size_child(__head, p, 0) + 1; \ - if (cmp < 0) p = p->__head.p[0]; \ - else if (cmp > 0) p = p->__head.p[1]; \ - else break; \ - } \ - if (cnt_) *cnt_ = cnt; \ - return (__type*)p; \ - } - -#define __KAVL_ROTATE(suf, __type, __head) \ - /* one rotation: (a,(b,c)q)p => ((a,b)p,c)q */ \ - static inline __type *kavl_rotate1_##suf(__type *p, int dir) { /* dir=0 to left; dir=1 to right */ \ - int opp = 1 - dir; /* opposite direction */ \ - __type *q = p->__head.p[opp]; \ - unsigned size_p = p->__head.size; \ - p->__head.size -= q->__head.size - kavl_size_child(__head, q, dir); \ - q->__head.size = size_p; \ - p->__head.p[opp] = q->__head.p[dir]; \ - q->__head.p[dir] = p; \ - return q; \ - } \ - /* two consecutive rotations: (a,((b,c)r,d)q)p => ((a,b)p,(c,d)q)r */ \ - static inline __type *kavl_rotate2_##suf(__type *p, int dir) { \ - int b1, opp = 1 - dir; \ - __type *q = p->__head.p[opp], *r = q->__head.p[dir]; \ - unsigned size_x_dir = kavl_size_child(__head, r, dir); \ - r->__head.size = p->__head.size; \ - p->__head.size -= q->__head.size - size_x_dir; \ - q->__head.size -= size_x_dir + 1; \ - p->__head.p[opp] = r->__head.p[dir]; \ - r->__head.p[dir] = p; \ - q->__head.p[dir] = r->__head.p[opp]; \ - r->__head.p[opp] = q; \ - b1 = dir == 0? +1 : -1; \ - if (r->__head.balance == b1) q->__head.balance = 0, p->__head.balance = -b1; \ - else if (r->__head.balance == 0) q->__head.balance = p->__head.balance = 0; \ - else q->__head.balance = b1, p->__head.balance = 0; \ - r->__head.balance = 0; \ - return r; \ - } - -#define __KAVL_INSERT(suf, __scope, __type, __head, __cmp) \ - __scope __type *kavl_insert_##suf(__type **root_, __type *x, unsigned *cnt_) { \ - unsigned char stack[KAVL_MAX_DEPTH]; \ - __type *path[KAVL_MAX_DEPTH]; \ - __type *bp, *bq; \ - __type *p, *q, *r = 0; /* _r_ is potentially the new root */ \ - int i, which = 0, top, b1, path_len; \ - unsigned cnt = 0; \ - bp = *root_, bq = 0; \ - /* find the insertion location */ \ - for (p = bp, q = bq, top = path_len = 0; p; q = p, p = p->__head.p[which]) { \ - int cmp; \ - cmp = __cmp(x, p); \ - if (cmp >= 0) cnt += kavl_size_child(__head, p, 0) + 1; \ - if (cmp == 0) { \ - if (cnt_) *cnt_ = cnt; \ - return p; \ - } \ - if (p->__head.balance != 0) \ - bq = q, bp = p, top = 0; \ - stack[top++] = which = (cmp > 0); \ - path[path_len++] = p; \ - } \ - if (cnt_) *cnt_ = cnt; \ - x->__head.balance = 0, x->__head.size = 1, x->__head.p[0] = x->__head.p[1] = 0; \ - if (q == 0) *root_ = x; \ - else q->__head.p[which] = x; \ - if (bp == 0) return x; \ - for (i = 0; i < path_len; ++i) ++path[i]->__head.size; \ - for (p = bp, top = 0; p != x; p = p->__head.p[stack[top]], ++top) /* update balance factors */ \ - if (stack[top] == 0) --p->__head.balance; \ - else ++p->__head.balance; \ - if (bp->__head.balance > -2 && bp->__head.balance < 2) return x; /* no re-balance needed */ \ - /* re-balance */ \ - which = (bp->__head.balance < 0); \ - b1 = which == 0? +1 : -1; \ - q = bp->__head.p[1 - which]; \ - if (q->__head.balance == b1) { \ - r = kavl_rotate1_##suf(bp, which); \ - q->__head.balance = bp->__head.balance = 0; \ - } else r = kavl_rotate2_##suf(bp, which); \ - if (bq == 0) *root_ = r; \ - else bq->__head.p[bp != bq->__head.p[0]] = r; \ - return x; \ - } - -#define __KAVL_ERASE(suf, __scope, __type, __head, __cmp) \ - __scope __type *kavl_erase_##suf(__type **root_, const __type *x, unsigned *cnt_) { \ - __type *p, *path[KAVL_MAX_DEPTH], fake; \ - unsigned char dir[KAVL_MAX_DEPTH]; \ - int i, d = 0, cmp; \ - unsigned cnt = 0; \ - fake.__head.p[0] = *root_, fake.__head.p[1] = 0; \ - if (cnt_) *cnt_ = 0; \ - if (x) { \ - for (cmp = -1, p = &fake; cmp; cmp = __cmp(x, p)) { \ - int which = (cmp > 0); \ - if (cmp > 0) cnt += kavl_size_child(__head, p, 0) + 1; \ - dir[d] = which; \ - path[d++] = p; \ - p = p->__head.p[which]; \ - if (p == 0) { \ - if (cnt_) *cnt_ = 0; \ - return 0; \ - } \ - } \ - cnt += kavl_size_child(__head, p, 0) + 1; /* because p==x is not counted */ \ - } else { \ - for (p = &fake, cnt = 1; p; p = p->__head.p[0]) \ - dir[d] = 0, path[d++] = p; \ - p = path[--d]; \ - } \ - if (cnt_) *cnt_ = cnt; \ - for (i = 1; i < d; ++i) --path[i]->__head.size; \ - if (p->__head.p[1] == 0) { /* ((1,.)2,3)4 => (1,3)4; p=2 */ \ - path[d-1]->__head.p[dir[d-1]] = p->__head.p[0]; \ - } else { \ - __type *q = p->__head.p[1]; \ - if (q->__head.p[0] == 0) { /* ((1,2)3,4)5 => ((1)2,4)5; p=3 */ \ - q->__head.p[0] = p->__head.p[0]; \ - q->__head.balance = p->__head.balance; \ - path[d-1]->__head.p[dir[d-1]] = q; \ - path[d] = q, dir[d++] = 1; \ - q->__head.size = p->__head.size - 1; \ - } else { /* ((1,((.,2)3,4)5)6,7)8 => ((1,(2,4)5)3,7)8; p=6 */ \ - __type *r; \ - int e = d++; /* backup _d_ */\ - for (;;) { \ - dir[d] = 0; \ - path[d++] = q; \ - r = q->__head.p[0]; \ - if (r->__head.p[0] == 0) break; \ - q = r; \ - } \ - r->__head.p[0] = p->__head.p[0]; \ - q->__head.p[0] = r->__head.p[1]; \ - r->__head.p[1] = p->__head.p[1]; \ - r->__head.balance = p->__head.balance; \ - path[e-1]->__head.p[dir[e-1]] = r; \ - path[e] = r, dir[e] = 1; \ - for (i = e + 1; i < d; ++i) --path[i]->__head.size; \ - r->__head.size = p->__head.size - 1; \ - } \ - } \ - while (--d > 0) { \ - __type *q = path[d]; \ - int which, other, b1 = 1, b2 = 2; \ - which = dir[d], other = 1 - which; \ - if (which) b1 = -b1, b2 = -b2; \ - q->__head.balance += b1; \ - if (q->__head.balance == b1) break; \ - else if (q->__head.balance == b2) { \ - __type *r = q->__head.p[other]; \ - if (r->__head.balance == -b1) { \ - path[d-1]->__head.p[dir[d-1]] = kavl_rotate2_##suf(q, which); \ - } else { \ - path[d-1]->__head.p[dir[d-1]] = kavl_rotate1_##suf(q, which); \ - if (r->__head.balance == 0) { \ - r->__head.balance = -b1; \ - q->__head.balance = b1; \ - break; \ - } else r->__head.balance = q->__head.balance = 0; \ - } \ - } \ - } \ - *root_ = fake.__head.p[0]; \ - return p; \ - } - -#define kavl_free(__type, __head, __root, __free) do { \ - __type *_p, *_q; \ - for (_p = __root; _p; _p = _q) { \ - if (_p->__head.p[0] == 0) { \ - _q = _p->__head.p[1]; \ - __free(_p); \ - } else { \ - _q = _p->__head.p[0]; \ - _p->__head.p[0] = _q->__head.p[1]; \ - _q->__head.p[1] = _p; \ - } \ - } \ - } while (0) - -#define __KAVL_ITR(suf, __scope, __type, __head, __cmp) \ - struct kavl_itr_##suf { \ - const __type *stack[KAVL_MAX_DEPTH], **top, *right; /* _right_ points to the right child of *top */ \ - }; \ - __scope void kavl_itr_first_##suf(const __type *root, struct kavl_itr_##suf *itr) { \ - const __type *p; \ - for (itr->top = itr->stack - 1, p = root; p; p = p->__head.p[0]) \ - *++itr->top = p; \ - itr->right = (*itr->top)->__head.p[1]; \ - } \ - __scope int kavl_itr_find_##suf(const __type *root, const __type *x, struct kavl_itr_##suf *itr) { \ - const __type *p = root; \ - itr->top = itr->stack - 1; \ - while (p != 0) { \ - int cmp; \ - cmp = __cmp(x, p); \ - if (cmp < 0) *++itr->top = p, p = p->__head.p[0]; \ - else if (cmp > 0) p = p->__head.p[1]; \ - else break; \ - } \ - if (p) { \ - *++itr->top = p; \ - itr->right = p->__head.p[1]; \ - return 1; \ - } else if (itr->top >= itr->stack) { \ - itr->right = (*itr->top)->__head.p[1]; \ - return 0; \ - } else return 0; \ - } \ - __scope int kavl_itr_next_##suf(struct kavl_itr_##suf *itr) { \ - for (;;) { \ - const __type *p; \ - for (p = itr->right, --itr->top; p; p = p->__head.p[0]) \ - *++itr->top = p; \ - if (itr->top < itr->stack) return 0; \ - itr->right = (*itr->top)->__head.p[1]; \ - return 1; \ - } \ - } - -/** - * Insert a node to the tree - * - * @param suf name suffix used in KAVL_INIT() - * @param proot pointer to the root of the tree (in/out: root may change) - * @param x node to insert (in) - * @param cnt number of nodes smaller than or equal to _x_; can be NULL (out) - * - * @return _x_ if not present in the tree, or the node equal to x. - */ -#define kavl_insert(suf, proot, x, cnt) kavl_insert_##suf(proot, x, cnt) - -/** - * Find a node in the tree - * - * @param suf name suffix used in KAVL_INIT() - * @param root root of the tree - * @param x node value to find (in) - * @param cnt number of nodes smaller than or equal to _x_; can be NULL (out) - * - * @return node equal to _x_ if present, or NULL if absent - */ -#define kavl_find(suf, root, x, cnt) kavl_find_##suf(root, x, cnt) - -/** - * Delete a node from the tree - * - * @param suf name suffix used in KAVL_INIT() - * @param proot pointer to the root of the tree (in/out: root may change) - * @param x node value to delete; if NULL, delete the first node (in) - * - * @return node removed from the tree if present, or NULL if absent - */ -#define kavl_erase(suf, proot, x, cnt) kavl_erase_##suf(proot, x, cnt) -#define kavl_erase_first(suf, proot) kavl_erase_##suf(proot, 0, 0) - -#define kavl_itr_t(suf) struct kavl_itr_##suf - -/** - * Place the iterator at the smallest object - * - * @param suf name suffix used in KAVL_INIT() - * @param root root of the tree - * @param itr iterator - */ -#define kavl_itr_first(suf, root, itr) kavl_itr_first_##suf(root, itr) - -/** - * Place the iterator at the object equal to or greater than the query - * - * @param suf name suffix used in KAVL_INIT() - * @param root root of the tree - * @param x query (in) - * @param itr iterator (out) - * - * @return 1 if find; 0 otherwise. kavl_at(itr) is NULL if and only if query is - * larger than all objects in the tree - */ -#define kavl_itr_find(suf, root, x, itr) kavl_itr_find_##suf(root, x, itr) - -/** - * Move to the next object in order - * - * @param itr iterator (modified) - * - * @return 1 if there is a next object; 0 otherwise - */ -#define kavl_itr_next(suf, itr) kavl_itr_next_##suf(itr) - -/** - * Return the pointer at the iterator - * - * @param itr iterator - * - * @return pointer if present; NULL otherwise - */ -#define kavl_at(itr) ((itr)->top < (itr)->stack? 0 : *(itr)->top) - -#define KAVL_INIT2(suf, __scope, __type, __head, __cmp) \ - __KAVL_FIND(suf, __scope, __type, __head, __cmp) \ - __KAVL_ROTATE(suf, __type, __head) \ - __KAVL_INSERT(suf, __scope, __type, __head, __cmp) \ - __KAVL_ERASE(suf, __scope, __type, __head, __cmp) \ - __KAVL_ITR(suf, __scope, __type, __head, __cmp) - -#define KAVL_INIT(suf, __type, __head, __cmp) \ - KAVL_INIT2(suf,, __type, __head, __cmp) - -#endif diff --git a/tpws/linux_compat.h b/tpws/linux_compat.h deleted file mode 100644 index d706ebf3..00000000 --- a/tpws/linux_compat.h +++ /dev/null @@ -1,111 +0,0 @@ -#ifdef __linux__ - -#include - -#ifndef TCP_USER_TIMEOUT -#define TCP_USER_TIMEOUT 18 -#endif - -#ifndef IP6T_SO_ORIGINAL_DST - #define IP6T_SO_ORIGINAL_DST 80 -#endif - -#ifndef PR_SET_NO_NEW_PRIVS - #define PR_SET_NO_NEW_PRIVS 38 -#endif - -// workaround for old headers - -struct tcp_info_new { - __u8 tcpi_state; - __u8 tcpi_ca_state; - __u8 tcpi_retransmits; - __u8 tcpi_probes; - __u8 tcpi_backoff; - __u8 tcpi_options; - __u8 tcpi_snd_wscale : 4, tcpi_rcv_wscale : 4; - __u8 tcpi_delivery_rate_app_limited : 1, tcpi_fastopen_client_fail : 2; - - __u32 tcpi_rto; - __u32 tcpi_ato; - __u32 tcpi_snd_mss; - __u32 tcpi_rcv_mss; - - __u32 tcpi_unacked; - __u32 tcpi_sacked; - __u32 tcpi_lost; - __u32 tcpi_retrans; - __u32 tcpi_fackets; - - /* Times. */ - __u32 tcpi_last_data_sent; - __u32 tcpi_last_ack_sent; /* Not remembered, sorry. */ - __u32 tcpi_last_data_recv; - __u32 tcpi_last_ack_recv; - - /* Metrics. */ - __u32 tcpi_pmtu; - __u32 tcpi_rcv_ssthresh; - __u32 tcpi_rtt; - __u32 tcpi_rttvar; - __u32 tcpi_snd_ssthresh; - __u32 tcpi_snd_cwnd; - __u32 tcpi_advmss; - __u32 tcpi_reordering; - - __u32 tcpi_rcv_rtt; - __u32 tcpi_rcv_space; - - __u32 tcpi_total_retrans; - - __u64 tcpi_pacing_rate; - __u64 tcpi_max_pacing_rate; - __u64 tcpi_bytes_acked; /* RFC4898 tcpEStatsAppHCThruOctetsAcked */ - __u64 tcpi_bytes_received; /* RFC4898 tcpEStatsAppHCThruOctetsReceived */ - __u32 tcpi_segs_out; /* RFC4898 tcpEStatsPerfSegsOut */ - __u32 tcpi_segs_in; /* RFC4898 tcpEStatsPerfSegsIn */ - - __u32 tcpi_notsent_bytes; - __u32 tcpi_min_rtt; - __u32 tcpi_data_segs_in; /* RFC4898 tcpEStatsDataSegsIn */ - __u32 tcpi_data_segs_out; /* RFC4898 tcpEStatsDataSegsOut */ - - __u64 tcpi_delivery_rate; - - __u64 tcpi_busy_time; /* Time (usec) busy sending data */ - __u64 tcpi_rwnd_limited; /* Time (usec) limited by receive window */ - __u64 tcpi_sndbuf_limited; /* Time (usec) limited by send buffer */ - - __u32 tcpi_delivered; - __u32 tcpi_delivered_ce; - - __u64 tcpi_bytes_sent; /* RFC4898 tcpEStatsPerfHCDataOctetsOut */ - __u64 tcpi_bytes_retrans; /* RFC4898 tcpEStatsPerfOctetsRetrans */ - __u32 tcpi_dsack_dups; /* RFC4898 tcpEStatsStackDSACKDups */ - __u32 tcpi_reord_seen; /* reordering events seen */ - - __u32 tcpi_rcv_ooopack; /* Out-of-order packets received */ - - __u32 tcpi_snd_wnd; /* peer's advertised receive window after - * scaling (bytes) - */ - __u32 tcpi_rcv_wnd; /* local advertised receive window after - * scaling (bytes) - */ - - __u32 tcpi_rehash; /* PLB or timeout triggered rehash attempts */ - - __u16 tcpi_total_rto; /* Total number of RTO timeouts, including - * SYN/SYN-ACK and recurring timeouts. - */ - __u16 tcpi_total_rto_recoveries; /* Total number of RTO - * recoveries, including any - * unfinished recovery. - */ - __u32 tcpi_total_rto_time; /* Total time spent in RTO recoveries - * in milliseconds, including any - * unfinished recovery. - */ -}; - -#endif diff --git a/tpws/macos/net/pfvar.h b/tpws/macos/net/pfvar.h deleted file mode 100644 index e6f6b6e4..00000000 --- a/tpws/macos/net/pfvar.h +++ /dev/null @@ -1,47 +0,0 @@ -#pragma once - -#include -#include - -// taken from an older apple SDK -// some fields are different from BSDs - -#define DIOCNATLOOK _IOWR('D', 23, struct pfioc_natlook) - -enum { PF_INOUT, PF_IN, PF_OUT, PF_FWD }; - -struct pf_addr { - union { - struct in_addr v4; - struct in6_addr v6; - u_int8_t addr8[16]; - u_int16_t addr16[8]; - u_int32_t addr32[4]; - } pfa; /* 128-bit address */ -#define v4 pfa.v4 -#define v6 pfa.v6 -#define addr8 pfa.addr8 -#define addr16 pfa.addr16 -#define addr32 pfa.addr32 -}; - -union pf_state_xport { - u_int16_t port; - u_int16_t call_id; - u_int32_t spi; -}; - -struct pfioc_natlook { - struct pf_addr saddr; - struct pf_addr daddr; - struct pf_addr rsaddr; - struct pf_addr rdaddr; - union pf_state_xport sxport; - union pf_state_xport dxport; - union pf_state_xport rsxport; - union pf_state_xport rdxport; - sa_family_t af; - u_int8_t proto; - u_int8_t proto_variant; - u_int8_t direction; -}; diff --git a/tpws/macos/sys/tree.h b/tpws/macos/sys/tree.h deleted file mode 100644 index 697fddfa..00000000 --- a/tpws/macos/sys/tree.h +++ /dev/null @@ -1,803 +0,0 @@ -/* $NetBSD: tree.h,v 1.8 2004/03/28 19:38:30 provos Exp $ */ -/* $OpenBSD: tree.h,v 1.7 2002/10/17 21:51:54 art Exp $ */ -/* $FreeBSD: releng/12.2/sys/sys/tree.h 326256 2017-11-27 15:01:59Z pfg $ */ - -/*- - * SPDX-License-Identifier: BSD-2-Clause-FreeBSD - * - * Copyright 2002 Niels Provos - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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 _SYS_TREE_H_ -#define _SYS_TREE_H_ - -#include - -/* - * This file defines data structures for different types of trees: - * splay trees and red-black trees. - * - * A splay tree is a self-organizing data structure. Every operation - * on the tree causes a splay to happen. The splay moves the requested - * node to the root of the tree and partly rebalances it. - * - * This has the benefit that request locality causes faster lookups as - * the requested nodes move to the top of the tree. On the other hand, - * every lookup causes memory writes. - * - * The Balance Theorem bounds the total access time for m operations - * and n inserts on an initially empty tree as O((m + n)lg n). The - * amortized cost for a sequence of m accesses to a splay tree is O(lg n); - * - * A red-black tree is a binary search tree with the node color as an - * extra attribute. It fulfills a set of conditions: - * - every search path from the root to a leaf consists of the - * same number of black nodes, - * - each red node (except for the root) has a black parent, - * - each leaf node is black. - * - * Every operation on a red-black tree is bounded as O(lg n). - * The maximum height of a red-black tree is 2lg (n+1). - */ - -#define SPLAY_HEAD(name, type) \ -struct name { \ - struct type *sph_root; /* root of the tree */ \ -} - -#define SPLAY_INITIALIZER(root) \ - { NULL } - -#define SPLAY_INIT(root) do { \ - (root)->sph_root = NULL; \ -} while (/*CONSTCOND*/ 0) - -#define SPLAY_ENTRY(type) \ -struct { \ - struct type *spe_left; /* left element */ \ - struct type *spe_right; /* right element */ \ -} - -#define SPLAY_LEFT(elm, field) (elm)->field.spe_left -#define SPLAY_RIGHT(elm, field) (elm)->field.spe_right -#define SPLAY_ROOT(head) (head)->sph_root -#define SPLAY_EMPTY(head) (SPLAY_ROOT(head) == NULL) - -/* SPLAY_ROTATE_{LEFT,RIGHT} expect that tmp hold SPLAY_{RIGHT,LEFT} */ -#define SPLAY_ROTATE_RIGHT(head, tmp, field) do { \ - SPLAY_LEFT((head)->sph_root, field) = SPLAY_RIGHT(tmp, field); \ - SPLAY_RIGHT(tmp, field) = (head)->sph_root; \ - (head)->sph_root = tmp; \ -} while (/*CONSTCOND*/ 0) - -#define SPLAY_ROTATE_LEFT(head, tmp, field) do { \ - SPLAY_RIGHT((head)->sph_root, field) = SPLAY_LEFT(tmp, field); \ - SPLAY_LEFT(tmp, field) = (head)->sph_root; \ - (head)->sph_root = tmp; \ -} while (/*CONSTCOND*/ 0) - -#define SPLAY_LINKLEFT(head, tmp, field) do { \ - SPLAY_LEFT(tmp, field) = (head)->sph_root; \ - tmp = (head)->sph_root; \ - (head)->sph_root = SPLAY_LEFT((head)->sph_root, field); \ -} while (/*CONSTCOND*/ 0) - -#define SPLAY_LINKRIGHT(head, tmp, field) do { \ - SPLAY_RIGHT(tmp, field) = (head)->sph_root; \ - tmp = (head)->sph_root; \ - (head)->sph_root = SPLAY_RIGHT((head)->sph_root, field); \ -} while (/*CONSTCOND*/ 0) - -#define SPLAY_ASSEMBLE(head, node, left, right, field) do { \ - SPLAY_RIGHT(left, field) = SPLAY_LEFT((head)->sph_root, field); \ - SPLAY_LEFT(right, field) = SPLAY_RIGHT((head)->sph_root, field);\ - SPLAY_LEFT((head)->sph_root, field) = SPLAY_RIGHT(node, field); \ - SPLAY_RIGHT((head)->sph_root, field) = SPLAY_LEFT(node, field); \ -} while (/*CONSTCOND*/ 0) - -/* Generates prototypes and inline functions */ - -#define SPLAY_PROTOTYPE(name, type, field, cmp) \ -void name##_SPLAY(struct name *, struct type *); \ -void name##_SPLAY_MINMAX(struct name *, int); \ -struct type *name##_SPLAY_INSERT(struct name *, struct type *); \ -struct type *name##_SPLAY_REMOVE(struct name *, struct type *); \ - \ -/* Finds the node with the same key as elm */ \ -static __inline struct type * \ -name##_SPLAY_FIND(struct name *head, struct type *elm) \ -{ \ - if (SPLAY_EMPTY(head)) \ - return(NULL); \ - name##_SPLAY(head, elm); \ - if ((cmp)(elm, (head)->sph_root) == 0) \ - return (head->sph_root); \ - return (NULL); \ -} \ - \ -static __inline struct type * \ -name##_SPLAY_NEXT(struct name *head, struct type *elm) \ -{ \ - name##_SPLAY(head, elm); \ - if (SPLAY_RIGHT(elm, field) != NULL) { \ - elm = SPLAY_RIGHT(elm, field); \ - while (SPLAY_LEFT(elm, field) != NULL) { \ - elm = SPLAY_LEFT(elm, field); \ - } \ - } else \ - elm = NULL; \ - return (elm); \ -} \ - \ -static __inline struct type * \ -name##_SPLAY_MIN_MAX(struct name *head, int val) \ -{ \ - name##_SPLAY_MINMAX(head, val); \ - return (SPLAY_ROOT(head)); \ -} - -/* Main splay operation. - * Moves node close to the key of elm to top - */ -#define SPLAY_GENERATE(name, type, field, cmp) \ -struct type * \ -name##_SPLAY_INSERT(struct name *head, struct type *elm) \ -{ \ - if (SPLAY_EMPTY(head)) { \ - SPLAY_LEFT(elm, field) = SPLAY_RIGHT(elm, field) = NULL; \ - } else { \ - int __comp; \ - name##_SPLAY(head, elm); \ - __comp = (cmp)(elm, (head)->sph_root); \ - if(__comp < 0) { \ - SPLAY_LEFT(elm, field) = SPLAY_LEFT((head)->sph_root, field);\ - SPLAY_RIGHT(elm, field) = (head)->sph_root; \ - SPLAY_LEFT((head)->sph_root, field) = NULL; \ - } else if (__comp > 0) { \ - SPLAY_RIGHT(elm, field) = SPLAY_RIGHT((head)->sph_root, field);\ - SPLAY_LEFT(elm, field) = (head)->sph_root; \ - SPLAY_RIGHT((head)->sph_root, field) = NULL; \ - } else \ - return ((head)->sph_root); \ - } \ - (head)->sph_root = (elm); \ - return (NULL); \ -} \ - \ -struct type * \ -name##_SPLAY_REMOVE(struct name *head, struct type *elm) \ -{ \ - struct type *__tmp; \ - if (SPLAY_EMPTY(head)) \ - return (NULL); \ - name##_SPLAY(head, elm); \ - if ((cmp)(elm, (head)->sph_root) == 0) { \ - if (SPLAY_LEFT((head)->sph_root, field) == NULL) { \ - (head)->sph_root = SPLAY_RIGHT((head)->sph_root, field);\ - } else { \ - __tmp = SPLAY_RIGHT((head)->sph_root, field); \ - (head)->sph_root = SPLAY_LEFT((head)->sph_root, field);\ - name##_SPLAY(head, elm); \ - SPLAY_RIGHT((head)->sph_root, field) = __tmp; \ - } \ - return (elm); \ - } \ - return (NULL); \ -} \ - \ -void \ -name##_SPLAY(struct name *head, struct type *elm) \ -{ \ - struct type __node, *__left, *__right, *__tmp; \ - int __comp; \ -\ - SPLAY_LEFT(&__node, field) = SPLAY_RIGHT(&__node, field) = NULL;\ - __left = __right = &__node; \ -\ - while ((__comp = (cmp)(elm, (head)->sph_root)) != 0) { \ - if (__comp < 0) { \ - __tmp = SPLAY_LEFT((head)->sph_root, field); \ - if (__tmp == NULL) \ - break; \ - if ((cmp)(elm, __tmp) < 0){ \ - SPLAY_ROTATE_RIGHT(head, __tmp, field); \ - if (SPLAY_LEFT((head)->sph_root, field) == NULL)\ - break; \ - } \ - SPLAY_LINKLEFT(head, __right, field); \ - } else if (__comp > 0) { \ - __tmp = SPLAY_RIGHT((head)->sph_root, field); \ - if (__tmp == NULL) \ - break; \ - if ((cmp)(elm, __tmp) > 0){ \ - SPLAY_ROTATE_LEFT(head, __tmp, field); \ - if (SPLAY_RIGHT((head)->sph_root, field) == NULL)\ - break; \ - } \ - SPLAY_LINKRIGHT(head, __left, field); \ - } \ - } \ - SPLAY_ASSEMBLE(head, &__node, __left, __right, field); \ -} \ - \ -/* Splay with either the minimum or the maximum element \ - * Used to find minimum or maximum element in tree. \ - */ \ -void name##_SPLAY_MINMAX(struct name *head, int __comp) \ -{ \ - struct type __node, *__left, *__right, *__tmp; \ -\ - SPLAY_LEFT(&__node, field) = SPLAY_RIGHT(&__node, field) = NULL;\ - __left = __right = &__node; \ -\ - while (1) { \ - if (__comp < 0) { \ - __tmp = SPLAY_LEFT((head)->sph_root, field); \ - if (__tmp == NULL) \ - break; \ - if (__comp < 0){ \ - SPLAY_ROTATE_RIGHT(head, __tmp, field); \ - if (SPLAY_LEFT((head)->sph_root, field) == NULL)\ - break; \ - } \ - SPLAY_LINKLEFT(head, __right, field); \ - } else if (__comp > 0) { \ - __tmp = SPLAY_RIGHT((head)->sph_root, field); \ - if (__tmp == NULL) \ - break; \ - if (__comp > 0) { \ - SPLAY_ROTATE_LEFT(head, __tmp, field); \ - if (SPLAY_RIGHT((head)->sph_root, field) == NULL)\ - break; \ - } \ - SPLAY_LINKRIGHT(head, __left, field); \ - } \ - } \ - SPLAY_ASSEMBLE(head, &__node, __left, __right, field); \ -} - -#define SPLAY_NEGINF -1 -#define SPLAY_INF 1 - -#define SPLAY_INSERT(name, x, y) name##_SPLAY_INSERT(x, y) -#define SPLAY_REMOVE(name, x, y) name##_SPLAY_REMOVE(x, y) -#define SPLAY_FIND(name, x, y) name##_SPLAY_FIND(x, y) -#define SPLAY_NEXT(name, x, y) name##_SPLAY_NEXT(x, y) -#define SPLAY_MIN(name, x) (SPLAY_EMPTY(x) ? NULL \ - : name##_SPLAY_MIN_MAX(x, SPLAY_NEGINF)) -#define SPLAY_MAX(name, x) (SPLAY_EMPTY(x) ? NULL \ - : name##_SPLAY_MIN_MAX(x, SPLAY_INF)) - -#define SPLAY_FOREACH(x, name, head) \ - for ((x) = SPLAY_MIN(name, head); \ - (x) != NULL; \ - (x) = SPLAY_NEXT(name, head, x)) - -/* Macros that define a red-black tree */ -#define RB_HEAD(name, type) \ -struct name { \ - struct type *rbh_root; /* root of the tree */ \ -} - -#define RB_INITIALIZER(root) \ - { NULL } - -#define RB_INIT(root) do { \ - (root)->rbh_root = NULL; \ -} while (/*CONSTCOND*/ 0) - -#define RB_BLACK 0 -#define RB_RED 1 -#define RB_ENTRY(type) \ -struct { \ - struct type *rbe_left; /* left element */ \ - struct type *rbe_right; /* right element */ \ - struct type *rbe_parent; /* parent element */ \ - int rbe_color; /* node color */ \ -} - -#define RB_LEFT(elm, field) (elm)->field.rbe_left -#define RB_RIGHT(elm, field) (elm)->field.rbe_right -#define RB_PARENT(elm, field) (elm)->field.rbe_parent -#define RB_COLOR(elm, field) (elm)->field.rbe_color -#define RB_ROOT(head) (head)->rbh_root -#define RB_EMPTY(head) (RB_ROOT(head) == NULL) - -#define RB_SET(elm, parent, field) do { \ - RB_PARENT(elm, field) = parent; \ - RB_LEFT(elm, field) = RB_RIGHT(elm, field) = NULL; \ - RB_COLOR(elm, field) = RB_RED; \ -} while (/*CONSTCOND*/ 0) - -#define RB_SET_BLACKRED(black, red, field) do { \ - RB_COLOR(black, field) = RB_BLACK; \ - RB_COLOR(red, field) = RB_RED; \ -} while (/*CONSTCOND*/ 0) - -#ifndef RB_AUGMENT -#define RB_AUGMENT(x) do {} while (0) -#endif - -#define RB_ROTATE_LEFT(head, elm, tmp, field) do { \ - (tmp) = RB_RIGHT(elm, field); \ - if ((RB_RIGHT(elm, field) = RB_LEFT(tmp, field)) != NULL) { \ - RB_PARENT(RB_LEFT(tmp, field), field) = (elm); \ - } \ - RB_AUGMENT(elm); \ - if ((RB_PARENT(tmp, field) = RB_PARENT(elm, field)) != NULL) { \ - if ((elm) == RB_LEFT(RB_PARENT(elm, field), field)) \ - RB_LEFT(RB_PARENT(elm, field), field) = (tmp); \ - else \ - RB_RIGHT(RB_PARENT(elm, field), field) = (tmp); \ - } else \ - (head)->rbh_root = (tmp); \ - RB_LEFT(tmp, field) = (elm); \ - RB_PARENT(elm, field) = (tmp); \ - RB_AUGMENT(tmp); \ - if ((RB_PARENT(tmp, field))) \ - RB_AUGMENT(RB_PARENT(tmp, field)); \ -} while (/*CONSTCOND*/ 0) - -#define RB_ROTATE_RIGHT(head, elm, tmp, field) do { \ - (tmp) = RB_LEFT(elm, field); \ - if ((RB_LEFT(elm, field) = RB_RIGHT(tmp, field)) != NULL) { \ - RB_PARENT(RB_RIGHT(tmp, field), field) = (elm); \ - } \ - RB_AUGMENT(elm); \ - if ((RB_PARENT(tmp, field) = RB_PARENT(elm, field)) != NULL) { \ - if ((elm) == RB_LEFT(RB_PARENT(elm, field), field)) \ - RB_LEFT(RB_PARENT(elm, field), field) = (tmp); \ - else \ - RB_RIGHT(RB_PARENT(elm, field), field) = (tmp); \ - } else \ - (head)->rbh_root = (tmp); \ - RB_RIGHT(tmp, field) = (elm); \ - RB_PARENT(elm, field) = (tmp); \ - RB_AUGMENT(tmp); \ - if ((RB_PARENT(tmp, field))) \ - RB_AUGMENT(RB_PARENT(tmp, field)); \ -} while (/*CONSTCOND*/ 0) - -/* Generates prototypes and inline functions */ -#define RB_PROTOTYPE(name, type, field, cmp) \ - RB_PROTOTYPE_INTERNAL(name, type, field, cmp,) -#define RB_PROTOTYPE_STATIC(name, type, field, cmp) \ - RB_PROTOTYPE_INTERNAL(name, type, field, cmp, __unused static) -#define RB_PROTOTYPE_INTERNAL(name, type, field, cmp, attr) \ - RB_PROTOTYPE_INSERT_COLOR(name, type, attr); \ - RB_PROTOTYPE_REMOVE_COLOR(name, type, attr); \ - RB_PROTOTYPE_INSERT(name, type, attr); \ - RB_PROTOTYPE_REMOVE(name, type, attr); \ - RB_PROTOTYPE_FIND(name, type, attr); \ - RB_PROTOTYPE_NFIND(name, type, attr); \ - RB_PROTOTYPE_NEXT(name, type, attr); \ - RB_PROTOTYPE_PREV(name, type, attr); \ - RB_PROTOTYPE_MINMAX(name, type, attr); -#define RB_PROTOTYPE_INSERT_COLOR(name, type, attr) \ - attr void name##_RB_INSERT_COLOR(struct name *, struct type *) -#define RB_PROTOTYPE_REMOVE_COLOR(name, type, attr) \ - attr void name##_RB_REMOVE_COLOR(struct name *, struct type *, struct type *) -#define RB_PROTOTYPE_REMOVE(name, type, attr) \ - attr struct type *name##_RB_REMOVE(struct name *, struct type *) -#define RB_PROTOTYPE_INSERT(name, type, attr) \ - attr struct type *name##_RB_INSERT(struct name *, struct type *) -#define RB_PROTOTYPE_FIND(name, type, attr) \ - attr struct type *name##_RB_FIND(struct name *, struct type *) -#define RB_PROTOTYPE_NFIND(name, type, attr) \ - attr struct type *name##_RB_NFIND(struct name *, struct type *) -#define RB_PROTOTYPE_NEXT(name, type, attr) \ - attr struct type *name##_RB_NEXT(struct type *) -#define RB_PROTOTYPE_PREV(name, type, attr) \ - attr struct type *name##_RB_PREV(struct type *) -#define RB_PROTOTYPE_MINMAX(name, type, attr) \ - attr struct type *name##_RB_MINMAX(struct name *, int) - -/* Main rb operation. - * Moves node close to the key of elm to top - */ -#define RB_GENERATE(name, type, field, cmp) \ - RB_GENERATE_INTERNAL(name, type, field, cmp,) -#define RB_GENERATE_STATIC(name, type, field, cmp) \ - RB_GENERATE_INTERNAL(name, type, field, cmp, __unused static) -#define RB_GENERATE_INTERNAL(name, type, field, cmp, attr) \ - RB_GENERATE_INSERT_COLOR(name, type, field, attr) \ - RB_GENERATE_REMOVE_COLOR(name, type, field, attr) \ - RB_GENERATE_INSERT(name, type, field, cmp, attr) \ - RB_GENERATE_REMOVE(name, type, field, attr) \ - RB_GENERATE_FIND(name, type, field, cmp, attr) \ - RB_GENERATE_NFIND(name, type, field, cmp, attr) \ - RB_GENERATE_NEXT(name, type, field, attr) \ - RB_GENERATE_PREV(name, type, field, attr) \ - RB_GENERATE_MINMAX(name, type, field, attr) - -#define RB_GENERATE_INSERT_COLOR(name, type, field, attr) \ -attr void \ -name##_RB_INSERT_COLOR(struct name *head, struct type *elm) \ -{ \ - struct type *parent, *gparent, *tmp; \ - while ((parent = RB_PARENT(elm, field)) != NULL && \ - RB_COLOR(parent, field) == RB_RED) { \ - gparent = RB_PARENT(parent, field); \ - if (parent == RB_LEFT(gparent, field)) { \ - tmp = RB_RIGHT(gparent, field); \ - if (tmp && RB_COLOR(tmp, field) == RB_RED) { \ - RB_COLOR(tmp, field) = RB_BLACK; \ - RB_SET_BLACKRED(parent, gparent, field);\ - elm = gparent; \ - continue; \ - } \ - if (RB_RIGHT(parent, field) == elm) { \ - RB_ROTATE_LEFT(head, parent, tmp, field);\ - tmp = parent; \ - parent = elm; \ - elm = tmp; \ - } \ - RB_SET_BLACKRED(parent, gparent, field); \ - RB_ROTATE_RIGHT(head, gparent, tmp, field); \ - } else { \ - tmp = RB_LEFT(gparent, field); \ - if (tmp && RB_COLOR(tmp, field) == RB_RED) { \ - RB_COLOR(tmp, field) = RB_BLACK; \ - RB_SET_BLACKRED(parent, gparent, field);\ - elm = gparent; \ - continue; \ - } \ - if (RB_LEFT(parent, field) == elm) { \ - RB_ROTATE_RIGHT(head, parent, tmp, field);\ - tmp = parent; \ - parent = elm; \ - elm = tmp; \ - } \ - RB_SET_BLACKRED(parent, gparent, field); \ - RB_ROTATE_LEFT(head, gparent, tmp, field); \ - } \ - } \ - RB_COLOR(head->rbh_root, field) = RB_BLACK; \ -} - -#define RB_GENERATE_REMOVE_COLOR(name, type, field, attr) \ -attr void \ -name##_RB_REMOVE_COLOR(struct name *head, struct type *parent, struct type *elm) \ -{ \ - struct type *tmp; \ - while ((elm == NULL || RB_COLOR(elm, field) == RB_BLACK) && \ - elm != RB_ROOT(head)) { \ - if (RB_LEFT(parent, field) == elm) { \ - tmp = RB_RIGHT(parent, field); \ - if (RB_COLOR(tmp, field) == RB_RED) { \ - RB_SET_BLACKRED(tmp, parent, field); \ - RB_ROTATE_LEFT(head, parent, tmp, field);\ - tmp = RB_RIGHT(parent, field); \ - } \ - if ((RB_LEFT(tmp, field) == NULL || \ - RB_COLOR(RB_LEFT(tmp, field), field) == RB_BLACK) &&\ - (RB_RIGHT(tmp, field) == NULL || \ - RB_COLOR(RB_RIGHT(tmp, field), field) == RB_BLACK)) {\ - RB_COLOR(tmp, field) = RB_RED; \ - elm = parent; \ - parent = RB_PARENT(elm, field); \ - } else { \ - if (RB_RIGHT(tmp, field) == NULL || \ - RB_COLOR(RB_RIGHT(tmp, field), field) == RB_BLACK) {\ - struct type *oleft; \ - if ((oleft = RB_LEFT(tmp, field)) \ - != NULL) \ - RB_COLOR(oleft, field) = RB_BLACK;\ - RB_COLOR(tmp, field) = RB_RED; \ - RB_ROTATE_RIGHT(head, tmp, oleft, field);\ - tmp = RB_RIGHT(parent, field); \ - } \ - RB_COLOR(tmp, field) = RB_COLOR(parent, field);\ - RB_COLOR(parent, field) = RB_BLACK; \ - if (RB_RIGHT(tmp, field)) \ - RB_COLOR(RB_RIGHT(tmp, field), field) = RB_BLACK;\ - RB_ROTATE_LEFT(head, parent, tmp, field);\ - elm = RB_ROOT(head); \ - break; \ - } \ - } else { \ - tmp = RB_LEFT(parent, field); \ - if (RB_COLOR(tmp, field) == RB_RED) { \ - RB_SET_BLACKRED(tmp, parent, field); \ - RB_ROTATE_RIGHT(head, parent, tmp, field);\ - tmp = RB_LEFT(parent, field); \ - } \ - if ((RB_LEFT(tmp, field) == NULL || \ - RB_COLOR(RB_LEFT(tmp, field), field) == RB_BLACK) &&\ - (RB_RIGHT(tmp, field) == NULL || \ - RB_COLOR(RB_RIGHT(tmp, field), field) == RB_BLACK)) {\ - RB_COLOR(tmp, field) = RB_RED; \ - elm = parent; \ - parent = RB_PARENT(elm, field); \ - } else { \ - if (RB_LEFT(tmp, field) == NULL || \ - RB_COLOR(RB_LEFT(tmp, field), field) == RB_BLACK) {\ - struct type *oright; \ - if ((oright = RB_RIGHT(tmp, field)) \ - != NULL) \ - RB_COLOR(oright, field) = RB_BLACK;\ - RB_COLOR(tmp, field) = RB_RED; \ - RB_ROTATE_LEFT(head, tmp, oright, field);\ - tmp = RB_LEFT(parent, field); \ - } \ - RB_COLOR(tmp, field) = RB_COLOR(parent, field);\ - RB_COLOR(parent, field) = RB_BLACK; \ - if (RB_LEFT(tmp, field)) \ - RB_COLOR(RB_LEFT(tmp, field), field) = RB_BLACK;\ - RB_ROTATE_RIGHT(head, parent, tmp, field);\ - elm = RB_ROOT(head); \ - break; \ - } \ - } \ - } \ - if (elm) \ - RB_COLOR(elm, field) = RB_BLACK; \ -} - -#define RB_GENERATE_REMOVE(name, type, field, attr) \ -attr struct type * \ -name##_RB_REMOVE(struct name *head, struct type *elm) \ -{ \ - struct type *child, *parent, *old = elm; \ - int color; \ - if (RB_LEFT(elm, field) == NULL) \ - child = RB_RIGHT(elm, field); \ - else if (RB_RIGHT(elm, field) == NULL) \ - child = RB_LEFT(elm, field); \ - else { \ - struct type *left; \ - elm = RB_RIGHT(elm, field); \ - while ((left = RB_LEFT(elm, field)) != NULL) \ - elm = left; \ - child = RB_RIGHT(elm, field); \ - parent = RB_PARENT(elm, field); \ - color = RB_COLOR(elm, field); \ - if (child) \ - RB_PARENT(child, field) = parent; \ - if (parent) { \ - if (RB_LEFT(parent, field) == elm) \ - RB_LEFT(parent, field) = child; \ - else \ - RB_RIGHT(parent, field) = child; \ - RB_AUGMENT(parent); \ - } else \ - RB_ROOT(head) = child; \ - if (RB_PARENT(elm, field) == old) \ - parent = elm; \ - (elm)->field = (old)->field; \ - if (RB_PARENT(old, field)) { \ - if (RB_LEFT(RB_PARENT(old, field), field) == old)\ - RB_LEFT(RB_PARENT(old, field), field) = elm;\ - else \ - RB_RIGHT(RB_PARENT(old, field), field) = elm;\ - RB_AUGMENT(RB_PARENT(old, field)); \ - } else \ - RB_ROOT(head) = elm; \ - RB_PARENT(RB_LEFT(old, field), field) = elm; \ - if (RB_RIGHT(old, field)) \ - RB_PARENT(RB_RIGHT(old, field), field) = elm; \ - if (parent) { \ - left = parent; \ - do { \ - RB_AUGMENT(left); \ - } while ((left = RB_PARENT(left, field)) != NULL); \ - } \ - goto color; \ - } \ - parent = RB_PARENT(elm, field); \ - color = RB_COLOR(elm, field); \ - if (child) \ - RB_PARENT(child, field) = parent; \ - if (parent) { \ - if (RB_LEFT(parent, field) == elm) \ - RB_LEFT(parent, field) = child; \ - else \ - RB_RIGHT(parent, field) = child; \ - RB_AUGMENT(parent); \ - } else \ - RB_ROOT(head) = child; \ -color: \ - if (color == RB_BLACK) \ - name##_RB_REMOVE_COLOR(head, parent, child); \ - return (old); \ -} \ - -#define RB_GENERATE_INSERT(name, type, field, cmp, attr) \ -/* Inserts a node into the RB tree */ \ -attr struct type * \ -name##_RB_INSERT(struct name *head, struct type *elm) \ -{ \ - struct type *tmp; \ - struct type *parent = NULL; \ - int comp = 0; \ - tmp = RB_ROOT(head); \ - while (tmp) { \ - parent = tmp; \ - comp = (cmp)(elm, parent); \ - if (comp < 0) \ - tmp = RB_LEFT(tmp, field); \ - else if (comp > 0) \ - tmp = RB_RIGHT(tmp, field); \ - else \ - return (tmp); \ - } \ - RB_SET(elm, parent, field); \ - if (parent != NULL) { \ - if (comp < 0) \ - RB_LEFT(parent, field) = elm; \ - else \ - RB_RIGHT(parent, field) = elm; \ - RB_AUGMENT(parent); \ - } else \ - RB_ROOT(head) = elm; \ - name##_RB_INSERT_COLOR(head, elm); \ - return (NULL); \ -} - -#define RB_GENERATE_FIND(name, type, field, cmp, attr) \ -/* Finds the node with the same key as elm */ \ -attr struct type * \ -name##_RB_FIND(struct name *head, struct type *elm) \ -{ \ - struct type *tmp = RB_ROOT(head); \ - int comp; \ - while (tmp) { \ - comp = cmp(elm, tmp); \ - if (comp < 0) \ - tmp = RB_LEFT(tmp, field); \ - else if (comp > 0) \ - tmp = RB_RIGHT(tmp, field); \ - else \ - return (tmp); \ - } \ - return (NULL); \ -} - -#define RB_GENERATE_NFIND(name, type, field, cmp, attr) \ -/* Finds the first node greater than or equal to the search key */ \ -attr struct type * \ -name##_RB_NFIND(struct name *head, struct type *elm) \ -{ \ - struct type *tmp = RB_ROOT(head); \ - struct type *res = NULL; \ - int comp; \ - while (tmp) { \ - comp = cmp(elm, tmp); \ - if (comp < 0) { \ - res = tmp; \ - tmp = RB_LEFT(tmp, field); \ - } \ - else if (comp > 0) \ - tmp = RB_RIGHT(tmp, field); \ - else \ - return (tmp); \ - } \ - return (res); \ -} - -#define RB_GENERATE_NEXT(name, type, field, attr) \ -/* ARGSUSED */ \ -attr struct type * \ -name##_RB_NEXT(struct type *elm) \ -{ \ - if (RB_RIGHT(elm, field)) { \ - elm = RB_RIGHT(elm, field); \ - while (RB_LEFT(elm, field)) \ - elm = RB_LEFT(elm, field); \ - } else { \ - if (RB_PARENT(elm, field) && \ - (elm == RB_LEFT(RB_PARENT(elm, field), field))) \ - elm = RB_PARENT(elm, field); \ - else { \ - while (RB_PARENT(elm, field) && \ - (elm == RB_RIGHT(RB_PARENT(elm, field), field)))\ - elm = RB_PARENT(elm, field); \ - elm = RB_PARENT(elm, field); \ - } \ - } \ - return (elm); \ -} - -#define RB_GENERATE_PREV(name, type, field, attr) \ -/* ARGSUSED */ \ -attr struct type * \ -name##_RB_PREV(struct type *elm) \ -{ \ - if (RB_LEFT(elm, field)) { \ - elm = RB_LEFT(elm, field); \ - while (RB_RIGHT(elm, field)) \ - elm = RB_RIGHT(elm, field); \ - } else { \ - if (RB_PARENT(elm, field) && \ - (elm == RB_RIGHT(RB_PARENT(elm, field), field))) \ - elm = RB_PARENT(elm, field); \ - else { \ - while (RB_PARENT(elm, field) && \ - (elm == RB_LEFT(RB_PARENT(elm, field), field)))\ - elm = RB_PARENT(elm, field); \ - elm = RB_PARENT(elm, field); \ - } \ - } \ - return (elm); \ -} - -#define RB_GENERATE_MINMAX(name, type, field, attr) \ -attr struct type * \ -name##_RB_MINMAX(struct name *head, int val) \ -{ \ - struct type *tmp = RB_ROOT(head); \ - struct type *parent = NULL; \ - while (tmp) { \ - parent = tmp; \ - if (val < 0) \ - tmp = RB_LEFT(tmp, field); \ - else \ - tmp = RB_RIGHT(tmp, field); \ - } \ - return (parent); \ -} - -#define RB_NEGINF -1 -#define RB_INF 1 - -#define RB_INSERT(name, x, y) name##_RB_INSERT(x, y) -#define RB_REMOVE(name, x, y) name##_RB_REMOVE(x, y) -#define RB_FIND(name, x, y) name##_RB_FIND(x, y) -#define RB_NFIND(name, x, y) name##_RB_NFIND(x, y) -#define RB_NEXT(name, x, y) name##_RB_NEXT(y) -#define RB_PREV(name, x, y) name##_RB_PREV(y) -#define RB_MIN(name, x) name##_RB_MINMAX(x, RB_NEGINF) -#define RB_MAX(name, x) name##_RB_MINMAX(x, RB_INF) - -#define RB_FOREACH(x, name, head) \ - for ((x) = RB_MIN(name, head); \ - (x) != NULL; \ - (x) = name##_RB_NEXT(x)) - -#define RB_FOREACH_FROM(x, name, y) \ - for ((x) = (y); \ - ((x) != NULL) && ((y) = name##_RB_NEXT(x), (x) != NULL); \ - (x) = (y)) - -#define RB_FOREACH_SAFE(x, name, head, y) \ - for ((x) = RB_MIN(name, head); \ - ((x) != NULL) && ((y) = name##_RB_NEXT(x), (x) != NULL); \ - (x) = (y)) - -#define RB_FOREACH_REVERSE(x, name, head) \ - for ((x) = RB_MAX(name, head); \ - (x) != NULL; \ - (x) = name##_RB_PREV(x)) - -#define RB_FOREACH_REVERSE_FROM(x, name, y) \ - for ((x) = (y); \ - ((x) != NULL) && ((y) = name##_RB_PREV(x), (x) != NULL); \ - (x) = (y)) - -#define RB_FOREACH_REVERSE_SAFE(x, name, head, y) \ - for ((x) = RB_MAX(name, head); \ - ((x) != NULL) && ((y) = name##_RB_PREV(x), (x) != NULL); \ - (x) = (y)) - -#endif /* _SYS_TREE_H_ */ diff --git a/tpws/params.c b/tpws/params.c deleted file mode 100644 index 0690e68f..00000000 --- a/tpws/params.c +++ /dev/null @@ -1,281 +0,0 @@ -#include "params.h" -#include -#include -#include -#ifdef __ANDROID__ -#include -#endif - -const char *progname = "tpws"; - -int DLOG_FILE(FILE *F, const char *format, va_list args) -{ - return vfprintf(F, format, args); -} -int DLOG_CON(const char *format, int syslog_priority, va_list args) -{ - return DLOG_FILE(syslog_priority==LOG_ERR ? stderr : stdout, format, args); -} -int DLOG_FILENAME(const char *filename, const char *format, va_list args) -{ - int r; - FILE *F = fopen(filename,"at"); - if (F) - { - r = DLOG_FILE(F, format, args); - fclose(F); - } - else - r=-1; - return r; -} - -typedef void (*f_log_function)(int priority, const char *line); - -static char log_buf[1024]; -static size_t log_buf_sz=0; -static void syslog_log_function(int priority, const char *line) -{ - syslog(priority,"%s",log_buf); -} -#ifdef __ANDROID__ -static enum android_LogPriority syslog_priority_to_android(int priority) -{ - enum android_LogPriority ap; - switch(priority) - { - case LOG_INFO: - case LOG_NOTICE: ap=ANDROID_LOG_INFO; break; - case LOG_ERR: ap=ANDROID_LOG_ERROR; break; - case LOG_WARNING: ap=ANDROID_LOG_WARN; break; - case LOG_EMERG: - case LOG_ALERT: - case LOG_CRIT: ap=ANDROID_LOG_FATAL; break; - case LOG_DEBUG: ap=ANDROID_LOG_DEBUG; break; - default: ap=ANDROID_LOG_UNKNOWN; - } - return ap; -} -static void android_log_function(int priority, const char *line) -{ - __android_log_print(syslog_priority_to_android(priority), progname, "%s", line); -} -#endif -static void log_buffered(f_log_function log_function, int syslog_priority, const char *format, va_list args) -{ - if (vsnprintf(log_buf+log_buf_sz,sizeof(log_buf)-log_buf_sz,format,args)>0) - { - log_buf_sz=strlen(log_buf); - // log when buffer is full or buffer ends with \n - if (log_buf_sz>=(sizeof(log_buf)-1) || (log_buf_sz && log_buf[log_buf_sz-1]=='\n')) - { - log_function(syslog_priority,log_buf); - log_buf_sz = 0; - } - } -} - -static int DLOG_VA(const char *format, int syslog_priority, bool condup, int level, va_list args) -{ - int r=0; - va_list args2; - - if (condup && !(params.debug>=level && params.debug_target==LOG_TARGET_CONSOLE)) - { - va_copy(args2,args); - DLOG_CON(format,syslog_priority,args2); - va_end(args2); - } - if (params.debug>=level) - { - switch(params.debug_target) - { - case LOG_TARGET_CONSOLE: - r = DLOG_CON(format,syslog_priority,args); - break; - case LOG_TARGET_FILE: - r = DLOG_FILENAME(params.debug_logfile,format,args); - break; - case LOG_TARGET_SYSLOG: - // skip newlines - log_buffered(syslog_log_function,syslog_priority,format,args); - r = 1; - break; -#ifdef __ANDROID__ - case LOG_TARGET_ANDROID: - // skip newlines - log_buffered(android_log_function,syslog_priority,format,args); - r = 1; - break; -#endif - default: - break; - } - } - return r; -} - -int DLOG(const char *format, int level, ...) -{ - int r; - va_list args; - va_start(args, level); - r = DLOG_VA(format, LOG_DEBUG, false, level, args); - va_end(args); - return r; -} -int DLOG_CONDUP(const char *format, ...) -{ - int r; - va_list args; - va_start(args, format); - r = DLOG_VA(format, LOG_DEBUG, true, 1, args); - va_end(args); - return r; -} -int DLOG_ERR(const char *format, ...) -{ - int r; - va_list args; - va_start(args, format); - r = DLOG_VA(format, LOG_ERR, true, 1, args); - va_end(args); - return r; -} -int DLOG_PERROR(const char *s) -{ - return DLOG_ERR("%s: %s\n", s, strerror(errno)); -} - - -int LOG_APPEND(const char *filename, const char *format, va_list args) -{ - int r; - FILE *F = fopen(filename,"at"); - if (F) - { - fprint_localtime(F); - fprintf(F, " : "); - r = vfprintf(F, format, args); - fprintf(F, "\n"); - fclose(F); - } - else - r=-1; - return r; -} - -int HOSTLIST_DEBUGLOG_APPEND(const char *format, ...) -{ - if (*params.hostlist_auto_debuglog) - { - int r; - va_list args; - - va_start(args, format); - r = LOG_APPEND(params.hostlist_auto_debuglog, format, args); - va_end(args); - return r; - } - else - return 0; -} - -void hexdump_limited_dlog(const uint8_t *data, size_t size, size_t limit) -{ - size_t k; - bool bcut = false; - if (size > limit) - { - size = limit; - bcut = true; - } - if (!size) return; - for (k = 0; k < size; k++) VPRINT("%02X ", data[k]); - VPRINT(bcut ? "... : " : ": "); - for (k = 0; k < size; k++) VPRINT("%c", data[k] >= 0x20 && data[k] <= 0x7F ? (char)data[k] : '.'); - if (bcut) VPRINT(" ..."); -} - -void dp_init(struct desync_profile *dp) -{ - LIST_INIT(&dp->hl_collection); - LIST_INIT(&dp->hl_collection_exclude); - LIST_INIT(&dp->ips_collection); - LIST_INIT(&dp->ips_collection_exclude); - LIST_INIT(&dp->pf_tcp); - - dp->filter_ipv4 = dp->filter_ipv6 = true; - memcpy(dp->hostspell, "host", 4); // default hostspell - dp->hostlist_auto_fail_threshold = HOSTLIST_AUTO_FAIL_THRESHOLD_DEFAULT; - dp->hostlist_auto_fail_time = HOSTLIST_AUTO_FAIL_TIME_DEFAULT; -} - -struct desync_profile_list *dp_list_add(struct desync_profile_list_head *head) -{ - struct desync_profile_list *entry = calloc(1,sizeof(struct desync_profile_list)); - if (!entry) return NULL; - - dp_init(&entry->dp); - - // add to the tail - struct desync_profile_list *dpn,*dpl=LIST_FIRST(head); - if (dpl) - { - while ((dpn=LIST_NEXT(dpl,next))) dpl = dpn; - LIST_INSERT_AFTER(dpl, entry, next); - } - else - LIST_INSERT_HEAD(head, entry, next); - - return entry; -} -static void dp_clear_dynamic(struct desync_profile *dp) -{ - hostlist_collection_destroy(&dp->hl_collection); - hostlist_collection_destroy(&dp->hl_collection_exclude); - ipset_collection_destroy(&dp->ips_collection); - ipset_collection_destroy(&dp->ips_collection_exclude); - port_filters_destroy(&dp->pf_tcp); - HostFailPoolDestroy(&dp->hostlist_auto_fail_counters); -} -void dp_clear(struct desync_profile *dp) -{ - dp_clear_dynamic(dp); - memset(dp,0,sizeof(*dp)); -} -void dp_entry_destroy(struct desync_profile_list *entry) -{ - dp_clear_dynamic(&entry->dp); - free(entry); -} -void dp_list_destroy(struct desync_profile_list_head *head) -{ - struct desync_profile_list *entry; - while ((entry = LIST_FIRST(head))) - { - LIST_REMOVE(entry, next); - dp_entry_destroy(entry); - } -} - - -#if !defined( __OpenBSD__) && !defined(__ANDROID__) -void cleanup_args(struct params_s *params) -{ - wordfree(¶ms->wexp); -} -#endif -void cleanup_params(struct params_s *params) -{ -#if !defined( __OpenBSD__) && !defined(__ANDROID__) - cleanup_args(params); -#endif - - dp_list_destroy(¶ms->desync_profiles); - - hostlist_files_destroy(¶ms->hostlists); - ipset_files_destroy(¶ms->ipsets); - ipcacheDestroy(¶ms->ipcache); - free(params->user); params->user=NULL; -} diff --git a/tpws/params.h b/tpws/params.h deleted file mode 100644 index 4cf51816..00000000 --- a/tpws/params.h +++ /dev/null @@ -1,172 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#if !defined( __OpenBSD__) && !defined(__ANDROID__) -#include -#endif - - -#include "tpws.h" -#include "pools.h" -#include "helpers.h" -#include "protocol.h" - -#define HOSTLIST_AUTO_FAIL_THRESHOLD_DEFAULT 3 -#define HOSTLIST_AUTO_FAIL_TIME_DEFAULT 60 - -#define FIX_SEG_DEFAULT_MAX_WAIT 50 - -#define IPCACHE_LIFETIME 7200 - -#define MAX_GIDS 64 - -enum bindll { unwanted=0, no, prefer, force }; - -#define MAX_BINDS 32 -struct bind_s -{ - char bindaddr[64],bindiface[IF_NAMESIZE]; - bool bind_if6; - enum bindll bindll; - int bind_wait_ifup,bind_wait_ip,bind_wait_ip_ll; -}; - -#define MAX_SPLITS 16 - -enum log_target { LOG_TARGET_CONSOLE=0, LOG_TARGET_FILE, LOG_TARGET_SYSLOG, LOG_TARGET_ANDROID }; - -struct desync_profile -{ - int n; // number of the profile - - bool hostcase, hostdot, hosttab, hostnospace, methodspace, methodeol, unixeol, domcase; - int hostpad; - char hostspell[4]; - bool split_any_protocol; - bool disorder, disorder_http, disorder_tls; - bool oob, oob_http, oob_tls; - uint8_t oob_byte; - - // multisplit - struct proto_pos splits[MAX_SPLITS]; - int split_count; - struct proto_pos tlsrec; - - int mss; - - bool tamper_start_n,tamper_cutoff_n; - unsigned int tamper_start,tamper_cutoff; - - bool filter_ipv4,filter_ipv6; - struct port_filters_head pf_tcp; - uint32_t filter_l7; // L7_PROTO_* bits - - // list of pointers to ipsets - struct ipset_collection_head ips_collection, ips_collection_exclude; - - // list of pointers to hostlist files - struct hostlist_collection_head hl_collection, hl_collection_exclude; - // pointer to autohostlist. NULL if no autohostlist for the profile. - struct hostlist_file *hostlist_auto; - int hostlist_auto_fail_threshold, hostlist_auto_fail_time, hostlist_auto_retrans_threshold; - - hostfail_pool *hostlist_auto_fail_counters; -}; - -#define PROFILE_IPSETS_ABSENT(dp) (!LIST_FIRST(&dp->ips_collection) && !LIST_FIRST(&dp->ips_collection_exclude)) -#define PROFILE_IPSETS_EMPTY(dp) (ipset_collection_is_empty(&dp->ips_collection) && ipset_collection_is_empty(&dp->ips_collection_exclude)) -#define PROFILE_HOSTLISTS_EMPTY(dp) (hostlist_collection_is_empty(&dp->hl_collection) && hostlist_collection_is_empty(&dp->hl_collection_exclude)) - -struct desync_profile_list { - struct desync_profile dp; - LIST_ENTRY(desync_profile_list) next; -}; -LIST_HEAD(desync_profile_list_head, desync_profile_list); -struct desync_profile_list *dp_list_add(struct desync_profile_list_head *head); -void dp_entry_destroy(struct desync_profile_list *entry); -void dp_list_destroy(struct desync_profile_list_head *head); -void dp_init(struct desync_profile *dp); -void dp_clear(struct desync_profile *dp); - -struct params_s -{ -#if !defined( __OpenBSD__) && !defined(__ANDROID__) - wordexp_t wexp; // for file based config -#endif - - int debug; - enum log_target debug_target; - char debug_logfile[PATH_MAX]; - - struct bind_s binds[MAX_BINDS]; - int binds_last; - bool bind_wait_only; - uint16_t port; - struct sockaddr_in connect_bind4; - struct sockaddr_in6 connect_bind6; - char connect_bind6_ifname[IF_NAMESIZE]; - - uint8_t proxy_type; - unsigned int fix_seg; - bool fix_seg_avail; - bool no_resolve; - bool skip_nodelay; - bool daemon; - bool droproot; - char *user; - uid_t uid; - gid_t gid[MAX_GIDS]; - int gid_count; - char pidfile[PATH_MAX]; - int maxconn,resolver_threads,maxfiles,max_orphan_time; - int local_rcvbuf,local_sndbuf,remote_rcvbuf,remote_sndbuf; -#if defined(__linux__) || defined(__APPLE__) - int tcp_user_timeout_local,tcp_user_timeout_remote; -#endif - -#if defined(BSD) - bool pf_enable; -#endif -#ifdef SPLICE_PRESENT - bool nosplice; -#endif - - int ttl_default; - char hostlist_auto_debuglog[PATH_MAX]; - - // hostlist files with data for all profiles - struct hostlist_files_head hostlists; - // ipset files with data for all profiles - struct ipset_files_head ipsets; - - bool tamper; // any tamper option is set - bool tamper_lim; // tamper-start or tamper-cutoff set in any profile - struct desync_profile_list_head desync_profiles; - - unsigned int ipcache_lifetime; - bool cache_hostname; - ip_cache ipcache; -}; - -extern struct params_s params; -extern const char *progname; -#if !defined( __OpenBSD__) && !defined(__ANDROID__) -void cleanup_args(struct params_s *params); -#endif -void cleanup_params(struct params_s *params); - -int DLOG(const char *format, int level, ...); -int DLOG_CONDUP(const char *format, ...); -int DLOG_ERR(const char *format, ...); -int DLOG_PERROR(const char *s); -int HOSTLIST_DEBUGLOG_APPEND(const char *format, ...); -void hexdump_limited_dlog(const uint8_t *data, size_t size, size_t limit); - -#define VPRINT(format, ...) DLOG(format, 1, ##__VA_ARGS__) -#define DBGPRINT(format, ...) DLOG(format, 2, ##__VA_ARGS__) diff --git a/tpws/pools.c b/tpws/pools.c deleted file mode 100644 index 80063ebe..00000000 --- a/tpws/pools.c +++ /dev/null @@ -1,823 +0,0 @@ -#define _GNU_SOURCE -#include "pools.h" -#include -#include -#include -#include - -#define DESTROY_STR_POOL(etype, ppool) \ - etype *elem, *tmp; \ - HASH_ITER(hh, *ppool, elem, tmp) { \ - free(elem->str); \ - HASH_DEL(*ppool, elem); \ - free(elem); \ - } - -#define ADD_STR_POOL(etype, ppool, keystr, keystr_len) \ - etype *elem; \ - if (!(elem = (etype*)malloc(sizeof(etype)))) \ - return false; \ - if (!(elem->str = malloc(keystr_len + 1))) \ - { \ - free(elem); \ - return false; \ - } \ - memcpy(elem->str, keystr, keystr_len); \ - elem->str[keystr_len] = 0; \ - oom = false; \ - HASH_ADD_KEYPTR(hh, *ppool, elem->str, keystr_len, elem); \ - if (oom) \ - { \ - free(elem->str); \ - free(elem); \ - return false; \ - } -#define ADD_HOSTLIST_POOL(etype, ppool, keystr, keystr_len, flg) \ - etype *elem_find; \ - HASH_FIND(hh, *ppool, keystr, keystr_len, elem_find); \ - if (!elem_find) { \ - ADD_STR_POOL(etype,ppool,keystr,keystr_len); \ - elem->flags = flg; \ - } - -#undef uthash_nonfatal_oom -#define uthash_nonfatal_oom(elt) ut_oom_recover(elt) -static bool oom = false; -static void ut_oom_recover(void *elem) -{ - oom = true; -} - -// for not zero terminated strings -bool HostlistPoolAddStrLen(hostlist_pool **pp, const char *s, size_t slen, uint32_t flags) -{ - ADD_HOSTLIST_POOL(hostlist_pool, pp, s, slen, flags) - return true; -} -// for zero terminated strings -bool HostlistPoolAddStr(hostlist_pool **pp, const char *s, uint32_t flags) -{ - return HostlistPoolAddStrLen(pp, s, strlen(s), flags); -} - -hostlist_pool *HostlistPoolGetStr(hostlist_pool *p, const char *s) -{ - hostlist_pool *elem; - HASH_FIND_STR(p, s, elem); - return elem; -} -bool HostlistPoolCheckStr(hostlist_pool *p, const char *s) -{ - return !!HostlistPoolGetStr(p,s); -} - -void HostlistPoolDestroy(hostlist_pool **pp) -{ - DESTROY_STR_POOL(hostlist_pool, pp) -} - - - -void HostFailPoolDestroy(hostfail_pool **pp) -{ - DESTROY_STR_POOL(hostfail_pool, pp) -} -hostfail_pool * HostFailPoolAdd(hostfail_pool **pp,const char *s,int fail_time) -{ - size_t slen = strlen(s); - ADD_STR_POOL(hostfail_pool, pp, s, slen) - elem->expire = time(NULL) + fail_time; - elem->counter = 0; - return elem; -} -hostfail_pool *HostFailPoolFind(hostfail_pool *p,const char *s) -{ - hostfail_pool *elem; - HASH_FIND_STR(p, s, elem); - return elem; -} -void HostFailPoolDel(hostfail_pool **p, hostfail_pool *elem) -{ - HASH_DEL(*p, elem); - free(elem); -} -void HostFailPoolPurge(hostfail_pool **pp) -{ - hostfail_pool *elem, *tmp; - time_t now = time(NULL); - HASH_ITER(hh, *pp, elem, tmp) - { - if (now >= elem->expire) - { - free(elem->str); - HASH_DEL(*pp, elem); - free(elem); - } - } -} -static time_t host_fail_purge_prev=0; -void HostFailPoolPurgeRateLimited(hostfail_pool **pp) -{ - time_t now = time(NULL); - // do not purge too often to save resources - if (host_fail_purge_prev != now) - { - HostFailPoolPurge(pp); - host_fail_purge_prev = now; - } -} -void HostFailPoolDump(hostfail_pool *p) -{ - hostfail_pool *elem, *tmp; - time_t now = time(NULL); - HASH_ITER(hh, p, elem, tmp) - printf("host=%s counter=%d time_left=%lld\n",elem->str,elem->counter,(long long int)elem->expire-now); -} - - -bool strlist_add(struct str_list_head *head, const char *filename) -{ - struct str_list *entry = malloc(sizeof(struct str_list)); - if (!entry) return false; - entry->str = strdup(filename); - if (!entry->str) - { - free(entry); - return false; - } - LIST_INSERT_HEAD(head, entry, next); - return true; -} -static void strlist_entry_destroy(struct str_list *entry) -{ - free(entry->str); - free(entry); -} -void strlist_destroy(struct str_list_head *head) -{ - struct str_list *entry; - while ((entry = LIST_FIRST(head))) - { - LIST_REMOVE(entry, next); - strlist_entry_destroy(entry); - } -} - - - -struct hostlist_file *hostlist_files_add(struct hostlist_files_head *head, const char *filename) -{ - struct hostlist_file *entry = malloc(sizeof(struct hostlist_file)); - if (entry) - { - if (filename) - { - if (!(entry->filename = strdup(filename))) - { - free(entry); - return false; - } - } - else - entry->filename = NULL; - FILE_MOD_RESET(&entry->mod_sig); - entry->hostlist = NULL; - LIST_INSERT_HEAD(head, entry, next); - } - return entry; -} -static void hostlist_files_entry_destroy(struct hostlist_file *entry) -{ - free(entry->filename); - HostlistPoolDestroy(&entry->hostlist); - free(entry); -} -void hostlist_files_destroy(struct hostlist_files_head *head) -{ - struct hostlist_file *entry; - while ((entry = LIST_FIRST(head))) - { - LIST_REMOVE(entry, next); - hostlist_files_entry_destroy(entry); - } -} -struct hostlist_file *hostlist_files_search(struct hostlist_files_head *head, const char *filename) -{ - struct hostlist_file *hfile; - - LIST_FOREACH(hfile, head, next) - { - if (hfile->filename && !strcmp(hfile->filename,filename)) - return hfile; - } - return NULL; -} -void hostlist_files_reset_modtime(struct hostlist_files_head *list) -{ - struct hostlist_file *hfile; - - LIST_FOREACH(hfile, list, next) - FILE_MOD_RESET(&hfile->mod_sig); -} - -struct hostlist_item *hostlist_collection_add(struct hostlist_collection_head *head, struct hostlist_file *hfile) -{ - struct hostlist_item *entry = malloc(sizeof(struct hostlist_item)); - if (entry) - { - entry->hfile = hfile; - LIST_INSERT_HEAD(head, entry, next); - } - return entry; -} -void hostlist_collection_destroy(struct hostlist_collection_head *head) -{ - struct hostlist_item *entry; - while ((entry = LIST_FIRST(head))) - { - LIST_REMOVE(entry, next); - free(entry); - } -} -struct hostlist_item *hostlist_collection_search(struct hostlist_collection_head *head, const char *filename) -{ - struct hostlist_item *item; - - LIST_FOREACH(item, head, next) - { - if (item->hfile->filename && !strcmp(item->hfile->filename,filename)) - return item; - } - return NULL; -} -bool hostlist_collection_is_empty(const struct hostlist_collection_head *head) -{ - const struct hostlist_item *item; - - LIST_FOREACH(item, head, next) - { - if (item->hfile->hostlist) - return false; - } - return true; -} - - -static int kavl_bit_cmp(const struct kavl_bit_elem *p, const struct kavl_bit_elem *q) -{ - unsigned int bitlen = q->bitlen < p->bitlen ? q->bitlen : p->bitlen; - unsigned int df = bitlen & 7, bytes = bitlen >> 3; - int cmp = memcmp(p->data, q->data, bytes); - - if (cmp || !df) return cmp; - - uint8_t c1 = p->data[bytes] >> (8 - df); - uint8_t c2 = q->data[bytes] >> (8 - df); - return c1data); - free(e); - } -} -void kavl_bit_delete(struct kavl_bit_elem **hdr, const void *data, unsigned int bitlen) -{ - struct kavl_bit_elem temp = { - .bitlen = bitlen, .data = (uint8_t*)data - }; - kavl_bit_destroy_elem(kavl_erase(kavl_bit, hdr, &temp, 0)); -} -void kavl_bit_destroy(struct kavl_bit_elem **hdr) -{ - while (*hdr) - { - struct kavl_bit_elem *e = kavl_erase_first(kavl_bit, hdr); - if (!e) break; - kavl_bit_destroy_elem(e); - } - free(*hdr); -} -struct kavl_bit_elem *kavl_bit_add(struct kavl_bit_elem **hdr, void *data, unsigned int bitlen, size_t struct_size) -{ - if (!struct_size) struct_size=sizeof(struct kavl_bit_elem); - - struct kavl_bit_elem *v, *e = calloc(1, struct_size); - if (!e) return 0; - - e->bitlen = bitlen; - e->data = data; - - v = kavl_insert(kavl_bit, hdr, e, 0); - while (e != v && e->bitlen < v->bitlen) - { - kavl_bit_delete(hdr, v->data, v->bitlen); - v = kavl_insert(kavl_bit, hdr, e, 0); - } - if (e != v) kavl_bit_destroy_elem(e); - return v; -} -struct kavl_bit_elem *kavl_bit_get(const struct kavl_bit_elem *hdr, const void *data, unsigned int bitlen) -{ - struct kavl_bit_elem temp = { - .bitlen = bitlen, .data = (uint8_t*)data - }; - return kavl_find(kavl_bit, hdr, &temp, 0); -} - -static bool ipset_kavl_add(struct kavl_bit_elem **ipset, const void *a, uint8_t preflen) -{ - uint8_t bytelen = (preflen+7)>>3; - uint8_t *abuf = malloc(bytelen); - if (!abuf) return false; - memcpy(abuf,a,bytelen); - if (!kavl_bit_add(ipset,abuf,preflen,0)) - { - free(abuf); - return false; - } - return true; -} - - -bool ipset4Check(const struct kavl_bit_elem *ipset, const struct in_addr *a, uint8_t preflen) -{ - return !!kavl_bit_get(ipset,a,preflen); -} -bool ipset4Add(struct kavl_bit_elem **ipset, const struct in_addr *a, uint8_t preflen) -{ - if (preflen>32) return false; - return ipset_kavl_add(ipset,a,preflen); -} -void ipset4Print(struct kavl_bit_elem *ipset) -{ - if (!ipset) return; - - struct cidr4 c; - const struct kavl_bit_elem *elem; - kavl_itr_t(kavl_bit) itr; - kavl_itr_first(kavl_bit, ipset, &itr); - do - { - elem = kavl_at(&itr); - c.preflen = elem->bitlen; - expand_bits(&c.addr, elem->data, elem->bitlen, sizeof(c.addr)); - print_cidr4(&c); - printf("\n"); - } - while (kavl_itr_next(kavl_bit, &itr)); -} - -bool ipset6Check(const struct kavl_bit_elem *ipset, const struct in6_addr *a, uint8_t preflen) -{ - return !!kavl_bit_get(ipset,a,preflen); -} -bool ipset6Add(struct kavl_bit_elem **ipset, const struct in6_addr *a, uint8_t preflen) -{ - if (preflen>128) return false; - return ipset_kavl_add(ipset,a,preflen); -} -void ipset6Print(struct kavl_bit_elem *ipset) -{ - if (!ipset) return; - - struct cidr6 c; - const struct kavl_bit_elem *elem; - kavl_itr_t(kavl_bit) itr; - kavl_itr_first(kavl_bit, ipset, &itr); - do - { - elem = kavl_at(&itr); - c.preflen = elem->bitlen; - expand_bits(&c.addr, elem->data, elem->bitlen, sizeof(c.addr)); - print_cidr6(&c); - printf("\n"); - } - while (kavl_itr_next(kavl_bit, &itr)); -} - -void ipsetDestroy(ipset *ipset) -{ - kavl_bit_destroy(&ipset->ips4); - kavl_bit_destroy(&ipset->ips6); -} -void ipsetPrint(ipset *ipset) -{ - ipset4Print(ipset->ips4); - ipset6Print(ipset->ips6); -} - - -struct ipset_file *ipset_files_add(struct ipset_files_head *head, const char *filename) -{ - struct ipset_file *entry = malloc(sizeof(struct ipset_file)); - if (entry) - { - if (filename) - { - if (!(entry->filename = strdup(filename))) - { - free(entry); - return false; - } - } - else - entry->filename = NULL; - FILE_MOD_RESET(&entry->mod_sig); - memset(&entry->ipset,0,sizeof(entry->ipset)); - LIST_INSERT_HEAD(head, entry, next); - } - return entry; -} -static void ipset_files_entry_destroy(struct ipset_file *entry) -{ - free(entry->filename); - ipsetDestroy(&entry->ipset); - free(entry); -} -void ipset_files_destroy(struct ipset_files_head *head) -{ - struct ipset_file *entry; - while ((entry = LIST_FIRST(head))) - { - LIST_REMOVE(entry, next); - ipset_files_entry_destroy(entry); - } -} -struct ipset_file *ipset_files_search(struct ipset_files_head *head, const char *filename) -{ - struct ipset_file *hfile; - - LIST_FOREACH(hfile, head, next) - { - if (hfile->filename && !strcmp(hfile->filename,filename)) - return hfile; - } - return NULL; -} -void ipset_files_reset_modtime(struct ipset_files_head *list) -{ - struct ipset_file *hfile; - - LIST_FOREACH(hfile, list, next) - FILE_MOD_RESET(&hfile->mod_sig); -} - -struct ipset_item *ipset_collection_add(struct ipset_collection_head *head, struct ipset_file *hfile) -{ - struct ipset_item *entry = malloc(sizeof(struct ipset_item)); - if (entry) - { - entry->hfile = hfile; - LIST_INSERT_HEAD(head, entry, next); - } - return entry; -} -void ipset_collection_destroy(struct ipset_collection_head *head) -{ - struct ipset_item *entry; - while ((entry = LIST_FIRST(head))) - { - LIST_REMOVE(entry, next); - free(entry); - } -} -struct ipset_item *ipset_collection_search(struct ipset_collection_head *head, const char *filename) -{ - struct ipset_item *item; - - LIST_FOREACH(item, head, next) - { - if (item->hfile->filename && !strcmp(item->hfile->filename,filename)) - return item; - } - return NULL; -} -bool ipset_collection_is_empty(const struct ipset_collection_head *head) -{ - const struct ipset_item *item; - - LIST_FOREACH(item, head, next) - { - if (!IPSET_EMPTY(&item->hfile->ipset)) - return false; - } - return true; -} - - -bool port_filter_add(struct port_filters_head *head, const port_filter *pf) -{ - struct port_filter_item *entry = malloc(sizeof(struct port_filter_item)); - if (entry) - { - entry->pf = *pf; - LIST_INSERT_HEAD(head, entry, next); - } - return entry; -} -void port_filters_destroy(struct port_filters_head *head) -{ - struct port_filter_item *entry; - while ((entry = LIST_FIRST(head))) - { - LIST_REMOVE(entry, next); - free(entry); - } -} -bool port_filters_in_range(const struct port_filters_head *head, uint16_t port) -{ - const struct port_filter_item *item; - - if (!LIST_FIRST(head)) return true; - LIST_FOREACH(item, head, next) - { - if (pf_in_range(port, &item->pf)) - return true; - } - return false; -} -bool port_filters_deny_if_empty(struct port_filters_head *head) -{ - port_filter pf; - if (LIST_FIRST(head)) return true; - return pf_parse("0",&pf) && port_filter_add(head,&pf); -} - - - -struct blob_item *blob_collection_add(struct blob_collection_head *head) -{ - struct blob_item *entry = calloc(1,sizeof(struct blob_item)); - if (entry) - { - // insert to the end - struct blob_item *itemc,*iteml=LIST_FIRST(head); - if (iteml) - { - while ((itemc=LIST_NEXT(iteml,next))) iteml = itemc; - LIST_INSERT_AFTER(iteml, entry, next); - } - else - LIST_INSERT_HEAD(head, entry, next); - } - return entry; -} -struct blob_item *blob_collection_add_blob(struct blob_collection_head *head, const void *data, size_t size, size_t size_reserve) -{ - struct blob_item *entry = calloc(1,sizeof(struct blob_item)); - if (!entry) return NULL; - if (!(entry->data = malloc(size+size_reserve))) - { - free(entry); - return NULL; - } - if (data) memcpy(entry->data,data,size); - entry->size = size; - entry->size_buf = size+size_reserve; - - // insert to the end - struct blob_item *itemc,*iteml=LIST_FIRST(head); - if (iteml) - { - while ((itemc=LIST_NEXT(iteml,next))) iteml = itemc; - LIST_INSERT_AFTER(iteml, entry, next); - } - else - LIST_INSERT_HEAD(head, entry, next); - - return entry; -} - -void blob_collection_destroy(struct blob_collection_head *head) -{ - struct blob_item *entry; - while ((entry = LIST_FIRST(head))) - { - LIST_REMOVE(entry, next); - free(entry->extra); - free(entry->extra2); - free(entry->data); - free(entry); - } -} -bool blob_collection_empty(const struct blob_collection_head *head) -{ - return !LIST_FIRST(head); -} - - - -static void ipcache_item_touch(ip_cache_item *item) -{ - time(&item->last); -} -static void ipcache_item_init(ip_cache_item *item) -{ - ipcache_item_touch(item); - item->hostname = NULL; - item->hostname_is_ip = false; -} -static void ipcache_item_destroy(ip_cache_item *item) -{ - free(item->hostname); -} - -static void ipcache4Destroy(ip_cache4 **ipcache) -{ - ip_cache4 *elem, *tmp; - HASH_ITER(hh, *ipcache, elem, tmp) - { - HASH_DEL(*ipcache, elem); - ipcache_item_destroy(&elem->data); - free(elem); - } -} -static void ipcache4Key(ip4if *key, const struct in_addr *a) -{ - memset(key,0,sizeof(*key)); // make sure everything is zero - key->addr = *a; -} -static ip_cache4 *ipcache4Find(ip_cache4 *ipcache, const struct in_addr *a) -{ - ip_cache4 *entry; - struct ip4if key; - - ipcache4Key(&key,a); - HASH_FIND(hh, ipcache, &key, sizeof(key), entry); - return entry; -} -static ip_cache4 *ipcache4Add(ip_cache4 **ipcache, const struct in_addr *a) -{ - // avoid dups - ip_cache4 *entry = ipcache4Find(*ipcache,a); - if (entry) return entry; // already included - - entry = malloc(sizeof(ip_cache4)); - if (!entry) return NULL; - ipcache4Key(&entry->key,a); - - oom = false; - HASH_ADD(hh, *ipcache, key, sizeof(entry->key), entry); - if (oom) { free(entry); return NULL; } - - ipcache_item_init(&entry->data); - - return entry; -} -static void ipcache4Print(ip_cache4 *ipcache) -{ - char s_ip[16]; - time_t now; - ip_cache4 *ipc, *tmp; - - time(&now); - HASH_ITER(hh, ipcache , ipc, tmp) - { - *s_ip=0; - inet_ntop(AF_INET, &ipc->key.addr, s_ip, sizeof(s_ip)); - printf("%s : hostname=%s hostname_is_ip=%u now=last+%llu\n", s_ip, ipc->data.hostname ? ipc->data.hostname : "", ipc->data.hostname_is_ip, (unsigned long long)(now-ipc->data.last)); - } -} - -static void ipcache6Destroy(ip_cache6 **ipcache) -{ - ip_cache6 *elem, *tmp; - HASH_ITER(hh, *ipcache, elem, tmp) - { - HASH_DEL(*ipcache, elem); - ipcache_item_destroy(&elem->data); - free(elem); - } -} -static void ipcache6Key(ip6if *key, const struct in6_addr *a) -{ - memset(key,0,sizeof(*key)); // make sure everything is zero - key->addr = *a; -} -static ip_cache6 *ipcache6Find(ip_cache6 *ipcache, const struct in6_addr *a) -{ - ip_cache6 *entry; - ip6if key; - - ipcache6Key(&key,a); - HASH_FIND(hh, ipcache, &key, sizeof(key), entry); - return entry; -} -static ip_cache6 *ipcache6Add(ip_cache6 **ipcache, const struct in6_addr *a) -{ - // avoid dups - ip_cache6 *entry = ipcache6Find(*ipcache,a); - if (entry) return entry; // already included - - entry = malloc(sizeof(ip_cache6)); - if (!entry) return NULL; - ipcache6Key(&entry->key,a); - - oom = false; - HASH_ADD(hh, *ipcache, key, sizeof(entry->key), entry); - if (oom) { free(entry); return NULL; } - - ipcache_item_init(&entry->data); - - return entry; -} -static void ipcache6Print(ip_cache6 *ipcache) -{ - char s_ip[40]; - time_t now; - ip_cache6 *ipc, *tmp; - - time(&now); - HASH_ITER(hh, ipcache , ipc, tmp) - { - *s_ip=0; - inet_ntop(AF_INET6, &ipc->key.addr, s_ip, sizeof(s_ip)); - printf("%s : hostname=%s hostname_is_ip=%u now=last+%llu\n", s_ip, ipc->data.hostname ? ipc->data.hostname : "", ipc->data.hostname_is_ip, (unsigned long long)(now-ipc->data.last)); - } -} - -void ipcacheDestroy(ip_cache *ipcache) -{ - ipcache4Destroy(&ipcache->ipcache4); - ipcache6Destroy(&ipcache->ipcache6); -} -void ipcachePrint(ip_cache *ipcache) -{ - ipcache4Print(ipcache->ipcache4); - ipcache6Print(ipcache->ipcache6); -} - -ip_cache_item *ipcacheTouch(ip_cache *ipcache, const struct in_addr *a4, const struct in6_addr *a6) -{ - ip_cache4 *ipcache4; - ip_cache6 *ipcache6; - if (a4) - { - if ((ipcache4 = ipcache4Add(&ipcache->ipcache4,a4))) - { - ipcache_item_touch(&ipcache4->data); - return &ipcache4->data; - } - } - else if (a6) - { - if ((ipcache6 = ipcache6Add(&ipcache->ipcache6,a6))) - { - ipcache_item_touch(&ipcache6->data); - return &ipcache6->data; - } - } - return NULL; -} - -static void ipcache4_purge(ip_cache4 **ipcache, time_t lifetime) -{ - ip_cache4 *elem, *tmp; - time_t now = time(NULL); - HASH_ITER(hh, *ipcache, elem, tmp) - { - if (now >= (elem->data.last + lifetime)) - { - HASH_DEL(*ipcache, elem); - ipcache_item_destroy(&elem->data); - free(elem); - } - } -} -static void ipcache6_purge(ip_cache6 **ipcache, time_t lifetime) -{ - ip_cache6 *elem, *tmp; - time_t now = time(NULL); - HASH_ITER(hh, *ipcache, elem, tmp) - { - if (now >= (elem->data.last + lifetime)) - { - HASH_DEL(*ipcache, elem); - ipcache_item_destroy(&elem->data); - free(elem); - } - } -} -static void ipcache_purge(ip_cache *ipcache, time_t lifetime) -{ - if (lifetime) // 0 = no expire - { - ipcache4_purge(&ipcache->ipcache4, lifetime); - ipcache6_purge(&ipcache->ipcache6, lifetime); - } -} -static time_t ipcache_purge_prev=0; -void ipcachePurgeRateLimited(ip_cache *ipcache, time_t lifetime) -{ - time_t now = time(NULL); - // do not purge too often to save resources - if (ipcache_purge_prev != now) - { - ipcache_purge(ipcache, lifetime); - ipcache_purge_prev = now; - } -} diff --git a/tpws/pools.h b/tpws/pools.h deleted file mode 100644 index 361a66c0..00000000 --- a/tpws/pools.h +++ /dev/null @@ -1,205 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include - -#include "helpers.h" - -//#define HASH_BLOOM 20 -#define HASH_NONFATAL_OOM 1 -#define HASH_FUNCTION HASH_BER -#include "uthash.h" - -#include "kavl.h" - -#define HOSTLIST_POOL_FLAG_STRICT_MATCH 1 - -typedef struct hostlist_pool { - char *str; /* key */ - uint32_t flags; /* custom data */ - UT_hash_handle hh; /* makes this structure hashable */ -} hostlist_pool; - -void HostlistPoolDestroy(hostlist_pool **pp); -bool HostlistPoolAddStr(hostlist_pool **pp, const char *s, uint32_t flags); -bool HostlistPoolAddStrLen(hostlist_pool **pp, const char *s, size_t slen, uint32_t flags); -hostlist_pool *HostlistPoolGetStr(hostlist_pool *p, const char *s); - -struct str_list { - char *str; - LIST_ENTRY(str_list) next; -}; -LIST_HEAD(str_list_head, str_list); - -typedef struct hostfail_pool { - char *str; /* key */ - int counter; /* value */ - time_t expire; /* when to expire record (unixtime) */ - UT_hash_handle hh; /* makes this structure hashable */ -} hostfail_pool; - -void HostFailPoolDestroy(hostfail_pool **pp); -hostfail_pool *HostFailPoolAdd(hostfail_pool **pp,const char *s,int fail_time); -hostfail_pool *HostFailPoolFind(hostfail_pool *p,const char *s); -void HostFailPoolDel(hostfail_pool **pp, hostfail_pool *elem); -void HostFailPoolPurge(hostfail_pool **pp); -void HostFailPoolPurgeRateLimited(hostfail_pool **pp); -void HostFailPoolDump(hostfail_pool *p); - -bool strlist_add(struct str_list_head *head, const char *filename); -void strlist_destroy(struct str_list_head *head); - - - -struct hostlist_file { - char *filename; - file_mod_sig mod_sig; - hostlist_pool *hostlist; - LIST_ENTRY(hostlist_file) next; -}; -LIST_HEAD(hostlist_files_head, hostlist_file); - -struct hostlist_file *hostlist_files_add(struct hostlist_files_head *head, const char *filename); -void hostlist_files_destroy(struct hostlist_files_head *head); -struct hostlist_file *hostlist_files_search(struct hostlist_files_head *head, const char *filename); -void hostlist_files_reset_modtime(struct hostlist_files_head *list); - -struct hostlist_item { - struct hostlist_file *hfile; - LIST_ENTRY(hostlist_item) next; -}; -LIST_HEAD(hostlist_collection_head, hostlist_item); -struct hostlist_item *hostlist_collection_add(struct hostlist_collection_head *head, struct hostlist_file *hfile); -void hostlist_collection_destroy(struct hostlist_collection_head *head); -struct hostlist_item *hostlist_collection_search(struct hostlist_collection_head *head, const char *filename); -bool hostlist_collection_is_empty(const struct hostlist_collection_head *head); - - -struct kavl_bit_elem -{ - unsigned int bitlen; - uint8_t *data; - KAVL_HEAD(struct kavl_bit_elem) head; -}; - -struct kavl_bit_elem *kavl_bit_get(const struct kavl_bit_elem *hdr, const void *data, unsigned int bitlen); -struct kavl_bit_elem *kavl_bit_add(struct kavl_bit_elem **hdr, void *data, unsigned int bitlen, size_t struct_size); -void kavl_bit_delete(struct kavl_bit_elem **hdr, const void *data, unsigned int bitlen); -void kavl_bit_destroy(struct kavl_bit_elem **hdr); - -// combined ipset ipv4 and ipv6 -typedef struct ipset { - struct kavl_bit_elem *ips4,*ips6; -} ipset; - -#define IPSET_EMPTY(ips) (!(ips)->ips4 && !(ips)->ips6) - -bool ipset4Add(struct kavl_bit_elem **ipset, const struct in_addr *a, uint8_t preflen); -static inline bool ipset4AddCidr(struct kavl_bit_elem **ipset, const struct cidr4 *cidr) -{ - return ipset4Add(ipset,&cidr->addr,cidr->preflen); -} -bool ipset4Check(const struct kavl_bit_elem *ipset, const struct in_addr *a, uint8_t preflen); -void ipset4Print(struct kavl_bit_elem *ipset); - -bool ipset6Add(struct kavl_bit_elem **ipset, const struct in6_addr *a, uint8_t preflen); -static inline bool ipset6AddCidr(struct kavl_bit_elem **ipset, const struct cidr6 *cidr) -{ - return ipset6Add(ipset,&cidr->addr,cidr->preflen); -} -bool ipset6Check(const struct kavl_bit_elem *ipset, const struct in6_addr *a, uint8_t preflen); -void ipset6Print(struct kavl_bit_elem *ipset); - -void ipsetDestroy(ipset *ipset); -void ipsetPrint(ipset *ipset); - - -struct ipset_file { - char *filename; - file_mod_sig mod_sig; - ipset ipset; - LIST_ENTRY(ipset_file) next; -}; -LIST_HEAD(ipset_files_head, ipset_file); - -struct ipset_file *ipset_files_add(struct ipset_files_head *head, const char *filename); -void ipset_files_destroy(struct ipset_files_head *head); -struct ipset_file *ipset_files_search(struct ipset_files_head *head, const char *filename); -void ipset_files_reset_modtime(struct ipset_files_head *list); - -struct ipset_item { - struct ipset_file *hfile; - LIST_ENTRY(ipset_item) next; -}; -LIST_HEAD(ipset_collection_head, ipset_item); -struct ipset_item * ipset_collection_add(struct ipset_collection_head *head, struct ipset_file *hfile); -void ipset_collection_destroy(struct ipset_collection_head *head); -struct ipset_item *ipset_collection_search(struct ipset_collection_head *head, const char *filename); -bool ipset_collection_is_empty(const struct ipset_collection_head *head); - - -struct port_filter_item { - port_filter pf; - LIST_ENTRY(port_filter_item) next; -}; -LIST_HEAD(port_filters_head, port_filter_item); -bool port_filter_add(struct port_filters_head *head, const port_filter *pf); -void port_filters_destroy(struct port_filters_head *head); -bool port_filters_in_range(const struct port_filters_head *head, uint16_t port); -bool port_filters_deny_if_empty(struct port_filters_head *head); - - -struct blob_item { - uint8_t *data; // main data blob - size_t size; // main data blob size - size_t size_buf;// main data blob allocated size - void *extra; // any data without size - void *extra2; // any data without size - LIST_ENTRY(blob_item) next; -}; -LIST_HEAD(blob_collection_head, blob_item); -struct blob_item *blob_collection_add(struct blob_collection_head *head); -struct blob_item *blob_collection_add_blob(struct blob_collection_head *head, const void *data, size_t size, size_t size_reserve); -void blob_collection_destroy(struct blob_collection_head *head); -bool blob_collection_empty(const struct blob_collection_head *head); - - -typedef struct ip4if -{ - struct in_addr addr; -} ip4if; -typedef struct ip6if -{ - struct in6_addr addr; -} ip6if; -typedef struct ip_cache_item -{ - time_t last; - char *hostname; - bool hostname_is_ip; -} ip_cache_item; -typedef struct ip_cache4 -{ - ip4if key; - ip_cache_item data; - UT_hash_handle hh; /* makes this structure hashable */ -} ip_cache4; -typedef struct ip_cache6 -{ - ip6if key; - ip_cache_item data; - UT_hash_handle hh; /* makes this structure hashable */ -} ip_cache6; -typedef struct ip_cache -{ - ip_cache4 *ipcache4; - ip_cache6 *ipcache6; -} ip_cache; - -ip_cache_item *ipcacheTouch(ip_cache *ipcache, const struct in_addr *a4, const struct in6_addr *a6); -void ipcachePurgeRateLimited(ip_cache *ipcache, time_t lifetime); -void ipcacheDestroy(ip_cache *ipcache); -void ipcachePrint(ip_cache *ipcache); diff --git a/tpws/protocol.c b/tpws/protocol.c deleted file mode 100644 index 2668d4df..00000000 --- a/tpws/protocol.c +++ /dev/null @@ -1,547 +0,0 @@ -#define _GNU_SOURCE - -#include "protocol.h" -#include "helpers.h" -#include -#include -#include -#include - -// find N level domain -static bool FindNLD(const uint8_t *dom, size_t dlen, int level, const uint8_t **p, size_t *len) -{ - int i; - const uint8_t *p1,*p2; - for (i=1,p2=dom+dlen;idom && *p2!='.'; p2--); - if (p2<=dom) return false; - } - for (p1=p2-1 ; p1>dom && *p1!='.'; p1--); - if (*p1=='.') p1++; - if (p) *p = p1; - if (len) *len = p2-p1; - return true; -} - -const char *l7proto_str(t_l7proto l7) -{ - switch(l7) - { - case HTTP: return "http"; - case TLS: return "tls"; - case QUIC: return "quic"; - case WIREGUARD: return "wireguard"; - case DHT: return "dht"; - default: return "unknown"; - } -} -bool l7_proto_match(t_l7proto l7proto, uint32_t filter_l7) -{ - return (l7proto==UNKNOWN && (filter_l7 & L7_PROTO_UNKNOWN)) || - (l7proto==HTTP && (filter_l7 & L7_PROTO_HTTP)) || - (l7proto==TLS && (filter_l7 & L7_PROTO_TLS)) || - (l7proto==QUIC && (filter_l7 & L7_PROTO_QUIC)) || - (l7proto==WIREGUARD && (filter_l7 & L7_PROTO_WIREGUARD)) || - (l7proto==DHT && (filter_l7 & L7_PROTO_DHT)); -} - -#define PM_ABS 0 -#define PM_HOST 1 -#define PM_HOST_END 2 -#define PM_HOST_SLD 3 -#define PM_HOST_MIDSLD 4 -#define PM_HOST_ENDSLD 5 -#define PM_HTTP_METHOD 6 -#define PM_SNI_EXT 7 -bool IsHostMarker(uint8_t posmarker) -{ - switch(posmarker) - { - case PM_HOST: - case PM_HOST_END: - case PM_HOST_SLD: - case PM_HOST_MIDSLD: - case PM_HOST_ENDSLD: - return true; - default: - return false; - } -} -const char *posmarker_name(uint8_t posmarker) -{ - switch(posmarker) - { - case PM_ABS: return "abs"; - case PM_HOST: return "host"; - case PM_HOST_END: return "endhost"; - case PM_HOST_SLD: return "sld"; - case PM_HOST_MIDSLD: return "midsld"; - case PM_HOST_ENDSLD: return "endsld"; - case PM_HTTP_METHOD: return "method"; - case PM_SNI_EXT: return "sniext"; - default: return "?"; - } -} - -static size_t CheckPos(size_t sz, ssize_t offset) -{ - return (offset>=0 && offsetmarker, sp->pos, data, sz); - case TLS: - return TLSPos(sp->marker, sp->pos, data, sz); - default: - return AnyProtoPos(sp->marker, sp->pos, data, sz); - } -} -void ResolveMultiPos(const uint8_t *data, size_t sz, t_l7proto l7proto, const struct proto_pos *splits, int split_count, size_t *pos, int *pos_count) -{ - int i,j; - for(i=j=0;i14 && !memcmp(data,"HTTP/1.",7) && (data[7]=='0' || data[7]=='1') && data[8]==' ' && - data[9]>='0' && data[9]<='9' && - data[10]>='0' && data[10]<='9' && - data[11]>='0' && data[11]<='9'; -} -int HttpReplyCode(const uint8_t *data, size_t len) -{ - return (data[9]-'0')*100 + (data[10]-'0')*10 + (data[11]-'0'); -} -bool HttpExtractHeader(const uint8_t *data, size_t len, const char *header, char *buf, size_t len_buf) -{ - const uint8_t *p, *s, *e = data + len; - - p = (uint8_t*)strncasestr((char*)data, header, len); - if (!p) return false; - p += strlen(header); - while (p < e && (*p == ' ' || *p == '\t')) p++; - s = p; - while (s < e && (*s != '\r' && *s != '\n' && *s != ' ' && *s != '\t')) s++; - if (s > p) - { - size_t slen = s - p; - if (buf && len_buf) - { - if (slen >= len_buf) slen = len_buf - 1; - for (size_t i = 0; i < slen; i++) buf[i] = tolower(p[i]); - buf[slen] = 0; - } - return true; - } - return false; -} -bool HttpExtractHost(const uint8_t *data, size_t len, char *host, size_t len_host) -{ - return HttpExtractHeader(data, len, "\nHost:", host, len_host); -} -// DPI redirects are global redirects to another domain -bool HttpReplyLooksLikeDPIRedirect(const uint8_t *data, size_t len, const char *host) -{ - char loc[256],*redirect_host, *p; - int code; - - if (!host || !*host) return false; - - code = HttpReplyCode(data,len); - - if ((code!=302 && code!=307) || !HttpExtractHeader(data,len,"\nLocation:",loc,sizeof(loc))) return false; - - // something like : https://censor.net/badpage.php?reason=denied&source=RKN - - if (!strncmp(loc,"http://",7)) - redirect_host=loc+7; - else if (!strncmp(loc,"https://",8)) - redirect_host=loc+8; - else - return false; - - // somethinkg like : censor.net/badpage.php?reason=denied&source=RKN - - for(p=redirect_host; *p && *p!='/' ; p++); - *p=0; - if (!*redirect_host) return false; - - // somethinkg like : censor.net - - // extract 2nd level domains - const char *dhost, *drhost; - if (!FindNLD((uint8_t*)host,strlen(host),2,(const uint8_t**)&dhost,NULL) || !FindNLD((uint8_t*)redirect_host,strlen(redirect_host),2,(const uint8_t**)&drhost,NULL)) - return false; - - // compare 2nd level domains - return strcasecmp(dhost, drhost)!=0; -} -size_t HttpPos(uint8_t posmarker, int16_t pos, const uint8_t *data, size_t sz) -{ - const uint8_t *method, *host=NULL, *p; - size_t offset_host,len_host; - ssize_t offset; - int i; - - switch(posmarker) - { - case PM_HTTP_METHOD: - // recognize some tpws pre-applied hacks - method=data; - if (sz<10) break; - if (*method=='\n' || *method=='\r') method++; - if (*method=='\n' || *method=='\r') method++; - for (p=method,i=0;i<7;i++) if (*p>='A' && *p<='Z') p++; - if (i<3 || *p!=' ') break; - return CheckPos(sz,method-data+pos); - case PM_HOST: - case PM_HOST_END: - case PM_HOST_SLD: - case PM_HOST_MIDSLD: - case PM_HOST_ENDSLD: - if (HttpFindHostConst(&host,data,sz) && (host-data+7)>12)==((tlsver>>4)&0xF))) ? "GREASE" : "UNKNOWN"; - } -} - -uint16_t TLSRecordDataLen(const uint8_t *data) -{ - return pntoh16(data + 3); -} -size_t TLSRecordLen(const uint8_t *data) -{ - return TLSRecordDataLen(data) + 5; -} -bool IsTLSRecordFull(const uint8_t *data, size_t len) -{ - return TLSRecordLen(data)<=len; -} -bool IsTLSClientHello(const uint8_t *data, size_t len, bool bPartialIsOK) -{ - return len >= 6 && data[0] == 0x16 && data[1] == 0x03 && data[2] <= 0x03 && data[5] == 0x01 && (bPartialIsOK || TLSRecordLen(data) <= len); -} - -// bPartialIsOK=true - accept partial packets not containing the whole TLS message -bool TLSFindExtInHandshake(const uint8_t *data, size_t len, uint16_t type, const uint8_t **ext, size_t *len_ext, bool bPartialIsOK) -{ - // +0 - // u8 HandshakeType: ClientHello - // u24 Length - // u16 Version - // c[32] random - // u8 SessionIDLength - // - // u16 CipherSuitesLength - // - // u8 CompressionMethodsLength - // - // u16 ExtensionsLength - - size_t l, ll; - - l = 1 + 3 + 2 + 32; - // SessionIDLength - if (len < (l + 1)) return false; - if (!bPartialIsOK) - { - ll = data[1] << 16 | data[2] << 8 | data[3]; // HandshakeProtocol length - if (len < (ll + 4)) return false; - } - l += data[l] + 1; - // CipherSuitesLength - if (len < (l + 2)) return false; - l += pntoh16(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 = pntoh16(data); - data += 2; len -= 2; - - if (bPartialIsOK) - { - if (len < l) l = len; - } - else - { - if (len < l) return false; - } - - while (l >= 4) - { - uint16_t etype = pntoh16(data); - size_t elen = pntoh16(data + 2); - data += 4; l -= 4; - if (l < elen) break; - if (etype == type) - { - if (ext && len_ext) - { - *ext = data; - *len_ext = elen; - } - return true; - } - data += elen; l -= elen; - } - - return false; -} -bool TLSFindExt(const uint8_t *data, size_t len, uint16_t type, const uint8_t **ext, size_t *len_ext, bool bPartialIsOK) -{ - // +0 - // u8 ContentType: Handshake - // u16 Version: TLS1.0 - // u16 Length - size_t reclen; - if (!IsTLSClientHello(data, len, bPartialIsOK)) return false; - reclen=TLSRecordLen(data); - if (reclen= len_host) slen = len_host - 1; - for (size_t i = 0; i < slen; i++) host[i] = tolower(ext[i]); - host[slen] = 0; - } - return true; -} -bool TLSHelloExtractHost(const uint8_t *data, size_t len, char *host, size_t len_host, bool bPartialIsOK) -{ - const uint8_t *ext; - size_t elen; - - if (!TLSFindExt(data, len, 0, &ext, &elen, bPartialIsOK)) return false; - return TLSExtractHostFromExt(ext, elen, host, len_host); -} -bool TLSHelloExtractHostFromHandshake(const uint8_t *data, size_t len, char *host, size_t len_host, bool bPartialIsOK) -{ - const uint8_t *ext; - size_t elen; - - if (!TLSFindExtInHandshake(data, len, 0, &ext, &elen, bPartialIsOK)) return false; - return TLSExtractHostFromExt(ext, elen, host, len_host); -} - -// find N level domain in SNI -static bool TLSHelloFindNLDInSNI(const uint8_t *ext, size_t elen, int level, const uint8_t **p, size_t *len) -{ - size_t slen; - return TLSAdvanceToHostInSNI(&ext,&elen,&slen) && FindNLD(ext,slen,level,p,len); -} -// find the middle of second level domain (SLD) in SNI ext : www.sobaka.ru => aka.ru -// return false if SNI ext is bad or SLD is not found -static bool TLSHelloFindMiddleOfSLDInSNI(const uint8_t *ext, size_t elen, const uint8_t **p) -{ - size_t len; - if (!TLSHelloFindNLDInSNI(ext,elen,2,p,&len)) - return false; - // in case of one letter SLD (x.com) we split at '.' to prevent appearance of the whole SLD - *p = (len==1) ? *p+1 : *p+len/2; - return true; -} -size_t TLSPos(uint8_t posmarker, int16_t pos, const uint8_t *data, size_t sz) -{ - size_t elen; - const uint8_t *ext, *p; - size_t offset_host,len_host; - ssize_t offset; - - switch(posmarker) - { - case PM_HOST: - case PM_HOST_END: - case PM_HOST_SLD: - case PM_HOST_MIDSLD: - case PM_HOST_ENDSLD: - case PM_SNI_EXT: - if (TLSFindExt(data,sz,0,&ext,&elen,false)) - { - if (posmarker==PM_SNI_EXT) - { - return CheckPos(sz,ext-data+pos); - } - else - { - if (!TLSAdvanceToHostInSNI(&ext,&elen,&len_host)) - return 0; - offset_host = ext-data; - return HostPos(posmarker,pos,data,sz,offset_host,len_host); - } - } - return 0; - default: - return AnyProtoPos(posmarker,pos,data,sz); - } -} diff --git a/tpws/protocol.h b/tpws/protocol.h deleted file mode 100644 index 7bd0cf41..00000000 --- a/tpws/protocol.h +++ /dev/null @@ -1,64 +0,0 @@ -#pragma once - -#include -#include -#include - -typedef enum {UNKNOWN=0, HTTP, TLS, QUIC, WIREGUARD, DHT} t_l7proto; -#define L7_PROTO_HTTP 0x00000001 -#define L7_PROTO_TLS 0x00000002 -#define L7_PROTO_QUIC 0x00000004 -#define L7_PROTO_WIREGUARD 0x00000008 -#define L7_PROTO_DHT 0x00000010 -#define L7_PROTO_UNKNOWN 0x80000000 -const char *l7proto_str(t_l7proto l7); -bool l7_proto_match(t_l7proto l7proto, uint32_t filter_l7); - -// pos markers -#define PM_ABS 0 -#define PM_HOST 1 -#define PM_HOST_END 2 -#define PM_HOST_SLD 3 -#define PM_HOST_MIDSLD 4 -#define PM_HOST_ENDSLD 5 -#define PM_HTTP_METHOD 6 -#define PM_SNI_EXT 7 -struct proto_pos -{ - int16_t pos; - uint8_t marker; -}; -#define PROTO_POS_EMPTY(sp) ((sp)->marker==PM_ABS && (sp)->pos==0) -bool IsHostMarker(uint8_t posmarker); -const char *posmarker_name(uint8_t posmarker); -size_t AnyProtoPos(uint8_t posmarker, int16_t pos, const uint8_t *data, size_t sz); -size_t HttpPos(uint8_t posmarker, int16_t pos, const uint8_t *data, size_t sz); -size_t TLSPos(uint8_t posmarker, int16_t pos, const uint8_t *data, size_t sz); -size_t ResolvePos(const uint8_t *data, size_t sz, t_l7proto l7proto, const struct proto_pos *sp); -void ResolveMultiPos(const uint8_t *data, size_t sz, t_l7proto l7proto, const struct proto_pos *splits, int split_count, size_t *pos, int *pos_count); - - -extern const char *http_methods[9]; -const char *HttpMethod(const uint8_t *data, size_t len); -bool IsHttp(const uint8_t *data, size_t len); -bool HttpFindHost(uint8_t **pHost,uint8_t *buf,size_t bs); -bool HttpFindHostConst(const uint8_t **pHost,const uint8_t *buf,size_t bs); -// header must be passed like this : "\nHost:" -bool HttpExtractHeader(const uint8_t *data, size_t len, const char *header, char *buf, size_t len_buf); -bool HttpExtractHost(const uint8_t *data, size_t len, char *host, size_t len_host); -bool IsHttpReply(const uint8_t *data, size_t len); -const char *HttpFind2ndLevelDomain(const char *host); -// must be pre-checked by IsHttpReply -int HttpReplyCode(const uint8_t *data, size_t len); -// must be pre-checked by IsHttpReply -bool HttpReplyLooksLikeDPIRedirect(const uint8_t *data, size_t len, const char *host); - -const char *TLSVersionStr(uint16_t tlsver); -uint16_t TLSRecordDataLen(const uint8_t *data); -size_t TLSRecordLen(const uint8_t *data); -bool IsTLSRecordFull(const uint8_t *data, size_t len); -bool IsTLSClientHello(const uint8_t *data, size_t len, bool bPartialIsOK); -bool TLSFindExt(const uint8_t *data, size_t len, uint16_t type, const uint8_t **ext, size_t *len_ext, bool bPartialIsOK); -bool TLSFindExtInHandshake(const uint8_t *data, size_t len, uint16_t type, const uint8_t **ext, size_t *len_ext, bool bPartialIsOK); -bool TLSHelloExtractHost(const uint8_t *data, size_t len, char *host, size_t len_host, bool bPartialIsOK); -bool TLSHelloExtractHostFromHandshake(const uint8_t *data, size_t len, char *host, size_t len_host, bool bPartialIsOK); diff --git a/tpws/redirect.c b/tpws/redirect.c deleted file mode 100644 index 58491ecc..00000000 --- a/tpws/redirect.c +++ /dev/null @@ -1,228 +0,0 @@ -#include "redirect.h" -#include -#include -#include -#include -#include -#include -#include - -#include "params.h" -#include "helpers.h" -#include "linux_compat.h" - -#ifdef __linux__ - #include -#endif - - -#if defined(BSD) - -#include -#include - -static int redirector_fd=-1; - -void redir_close(void) -{ - if (redirector_fd!=-1) - { - close(redirector_fd); - redirector_fd = -1; - DBGPRINT("closed redirector\n"); - } -} -static bool redir_open_private(const char *fname, int flags) -{ - redir_close(); - redirector_fd = open(fname, flags); - if (redirector_fd < 0) - { - DLOG_PERROR("redir_openv_private"); - return false; - } - DBGPRINT("opened redirector %s\n",fname); - return true; -} -bool redir_init(void) -{ - return params.pf_enable ? redir_open_private("/dev/pf", O_RDONLY) : true; -} - -static bool destination_from_pf(const struct sockaddr *accept_sa, struct sockaddr_storage *orig_dst) -{ - struct pfioc_natlook nl; - struct sockaddr_storage asa2; - - if (redirector_fd==-1) return false; - - if (params.debug>=2) - { - char s[48],s2[48]; - *s=0; ntop46_port(accept_sa, s, sizeof(s)); - *s2=0; ntop46_port((struct sockaddr *)orig_dst, s2, sizeof(s2)); - DBGPRINT("destination_from_pf %s %s\n",s,s2); - } - - saconvmapped(orig_dst); - if (accept_sa->sa_family==AF_INET6 && orig_dst->ss_family==AF_INET) - { - memcpy(&asa2,accept_sa,sizeof(struct sockaddr_in6)); - saconvmapped(&asa2); - accept_sa = (struct sockaddr*)&asa2; - } - - if (params.debug>=2) - { - char s[48],s2[48]; - *s=0; ntop46_port(accept_sa, s, sizeof(s)); - *s2=0; ntop46_port((struct sockaddr *)orig_dst, s2, sizeof(s2)); - DBGPRINT("destination_from_pf (saconvmapped) %s %s\n",s,s2); - } - - if (accept_sa->sa_family!=orig_dst->ss_family) - { - DBGPRINT("accept_sa and orig_dst sa_family mismatch : %d %d\n", accept_sa->sa_family, orig_dst->ss_family); - return false; - } - - memset(&nl, 0, sizeof(nl)); - nl.proto = IPPROTO_TCP; - nl.direction = PF_OUT; - nl.af = orig_dst->ss_family; - switch(orig_dst->ss_family) - { - case AF_INET: - { - struct sockaddr_in *sin = (struct sockaddr_in *)orig_dst; - nl.daddr.v4.s_addr = sin->sin_addr.s_addr; - nl.saddr.v4.s_addr = ((struct sockaddr_in*)accept_sa)->sin_addr.s_addr; -#ifdef __APPLE__ - nl.sxport.port = ((struct sockaddr_in*)accept_sa)->sin_port; - nl.dxport.port = sin->sin_port; -#else - nl.sport = ((struct sockaddr_in*)accept_sa)->sin_port; - nl.dport = sin->sin_port; -#endif - } - break; - case AF_INET6: - { - struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)orig_dst; - nl.daddr.v6 = sin6->sin6_addr; - nl.saddr.v6 = ((struct sockaddr_in6*)accept_sa)->sin6_addr; -#ifdef __APPLE__ - nl.sxport.port = ((struct sockaddr_in6*)accept_sa)->sin6_port; - nl.dxport.port = sin6->sin6_port; -#else - nl.sport = ((struct sockaddr_in6*)accept_sa)->sin6_port; - nl.dport = sin6->sin6_port; -#endif - } - break; - default: - DBGPRINT("destination_from_pf : unexpected address family %d\n",orig_dst->ss_family); - return false; - } - - if (ioctl(redirector_fd, DIOCNATLOOK, &nl) < 0) - { - DLOG_PERROR("ioctl(DIOCNATLOOK) failed"); - return false; - } - DBGPRINT("destination_from_pf : got orig dest addr from pf\n"); - - switch(nl.af) - { - case AF_INET: - orig_dst->ss_family = nl.af; -#ifdef __APPLE__ - ((struct sockaddr_in*)orig_dst)->sin_port = nl.rdxport.port; -#else - ((struct sockaddr_in*)orig_dst)->sin_port = nl.rdport; -#endif - ((struct sockaddr_in*)orig_dst)->sin_addr = nl.rdaddr.v4; - break; - case AF_INET6: - orig_dst->ss_family = nl.af; -#ifdef __APPLE__ - ((struct sockaddr_in6*)orig_dst)->sin6_port = nl.rdxport.port; -#else - ((struct sockaddr_in6*)orig_dst)->sin6_port = nl.rdport; -#endif - ((struct sockaddr_in6*)orig_dst)->sin6_addr = nl.rdaddr.v6; - break; - default: - DBGPRINT("destination_from_pf : DIOCNATLOOK returned unexpected address family %d\n",nl.af); - return false; - } - - return true; -} - - -#else - -bool redir_init(void) {return true;} -void redir_close(void) {}; - -#endif - - - -//Store the original destination address in orig_dst -bool get_dest_addr(int sockfd, const struct sockaddr *accept_sa, 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 - -#ifdef __linux__ - // 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) - { - DBGPRINT("both SO_ORIGINAL_DST and IP6T_SO_ORIGINAL_DST failed !\n"); -#endif - // TPROXY : socket is bound to original destination - r=getsockname(sockfd, (struct sockaddr*) orig_dst, &addrlen); - if (r<0) - { - DLOG_PERROR("getsockname"); - return false; - } - if (orig_dst->ss_family==AF_INET6) - ((struct sockaddr_in6*)orig_dst)->sin6_scope_id=0; // or MacOS will not connect() -#ifdef BSD - if (params.pf_enable && !destination_from_pf(accept_sa, orig_dst)) - DBGPRINT("pf filter destination_from_pf failed\n"); -#endif -#ifdef __linux__ - } -#endif - if (saconvmapped(orig_dst)) - DBGPRINT("Original destination : converted ipv6 mapped address to ipv4\n"); - - if (params.debug) - { - 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\n", 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\n", sockfd,orig_dst_str, htons(((struct sockaddr_in6*) orig_dst)->sin6_port)); - } - } - return true; -} diff --git a/tpws/redirect.h b/tpws/redirect.h deleted file mode 100644 index ee46267c..00000000 --- a/tpws/redirect.h +++ /dev/null @@ -1,9 +0,0 @@ -#pragma once - -#include -#include -#include - -bool get_dest_addr(int sockfd, const struct sockaddr *accept_sa, struct sockaddr_storage *orig_dst); -bool redir_init(void); -void redir_close(void); diff --git a/tpws/resolver.c b/tpws/resolver.c deleted file mode 100644 index 1653756e..00000000 --- a/tpws/resolver.c +++ /dev/null @@ -1,270 +0,0 @@ -#define _GNU_SOURCE - -#include "resolver.h" -#include "params.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define SIG_BREAK SIGUSR1 - -#ifdef __APPLE__ - static const char *sem_name="/tpws_resolver"; -#endif - -TAILQ_HEAD(resolve_tailhead, resolve_item); - -typedef struct -{ - int fd_signal_pipe; - sem_t *sem; -#ifndef __APPLE__ - sem_t _sem; -#endif - struct resolve_tailhead resolve_list; - pthread_mutex_t resolve_list_lock; - int threads; - pthread_t *thread; - bool bInit, bStop; -} t_resolver; -static t_resolver resolver = { .bInit = false }; - -#define rlist_lock pthread_mutex_lock(&resolver.resolve_list_lock) -#define rlist_unlock pthread_mutex_unlock(&resolver.resolve_list_lock) - -static void resolver_clear_list(void) -{ - struct resolve_item *ri; - - for (;;) - { - ri = TAILQ_FIRST(&resolver.resolve_list); - if (!ri) break; - TAILQ_REMOVE(&resolver.resolve_list, ri, next); - free(ri); - } -} - -int resolver_thread_count(void) -{ - return resolver.bInit ? resolver.threads : 0; -} - -static void *resolver_thread(void *arg) -{ - int r; - sigset_t signal_mask; - - sigemptyset(&signal_mask); - sigaddset(&signal_mask, SIG_BREAK); - - //printf("resolver_thread %d start\n",syscall(SYS_gettid)); - for(;;) - { - if (resolver.bStop) break; - r = sem_wait(resolver.sem); - if (resolver.bStop) break; - if (r) - { - if (errno!=EINTR) - { - DLOG_PERROR("sem_wait (resolver_thread)"); - break; // fatal err - } - } - else - { - struct resolve_item *ri; - ssize_t wr; - - rlist_lock; - ri = TAILQ_FIRST(&resolver.resolve_list); - if (ri) TAILQ_REMOVE(&resolver.resolve_list, ri, next); - rlist_unlock; - - if (ri) - { - struct addrinfo *ai,hints; - char sport[6]; - - //printf("THREAD %d GOT JOB %s\n", syscall(SYS_gettid), ri->dom); - snprintf(sport,sizeof(sport),"%u",ri->port); - memset(&hints, 0, sizeof(struct addrinfo)); - hints.ai_socktype = SOCK_STREAM; - // unfortunately getaddrinfo cannot be interrupted with a signal. we cannot cancel a query - ri->ga_res = getaddrinfo(ri->dom,sport,&hints,&ai); - if (!ri->ga_res) - { - if (ai->ai_addrlen>sizeof(ri->ss)) - { - DLOG_ERR("getaddrinfo returned too large address\n"); - ri->ga_res = EAI_FAIL; - } - else - memcpy(&ri->ss, ai->ai_addr, ai->ai_addrlen); - freeaddrinfo(ai); - } - //printf("THREAD %d END JOB %s FIRST=%p\n", syscall(SYS_gettid), ri->dom, TAILQ_FIRST(&resolver.resolve_list)); - - // never interrupt this - pthread_sigmask(SIG_BLOCK, &signal_mask, NULL); - wr = write(resolver.fd_signal_pipe,&ri,sizeof(void*)); - if (wr<0) - { - free(ri); - DLOG_PERROR("write resolve_pipe"); - } - else if (wr!=sizeof(void*)) - { - // partial pointer write is FATAL. in any case it will cause pointer corruption and coredump - free(ri); - DLOG_ERR("write resolve_pipe : not full write\n"); - exit(1000); - } - pthread_sigmask(SIG_UNBLOCK, &signal_mask, NULL); - } - } - } - //printf("resolver_thread %d exit\n",syscall(SYS_gettid)); - return NULL; -} - -static void sigbreak(int sig) -{ -} - -void resolver_deinit(void) -{ - if (resolver.bInit) - { - resolver.bStop = true; - - // wait all threads to terminate - for (int t = 0; t < resolver.threads; t++) - pthread_kill(resolver.thread[t], SIGUSR1); - for (int t = 0; t < resolver.threads; t++) - { - pthread_kill(resolver.thread[t], SIGUSR1); - pthread_join(resolver.thread[t], NULL); - } - - pthread_mutex_destroy(&resolver.resolve_list_lock); - free(resolver.thread); - - #ifdef __APPLE__ - sem_close(resolver.sem); - #else - sem_destroy(resolver.sem); - #endif - - resolver_clear_list(); - - memset(&resolver,0,sizeof(resolver)); - } -} - -bool resolver_init(int threads, int fd_signal_pipe) -{ - int t; - struct sigaction action; - - if (threads<1 || resolver.bInit) return false; - - memset(&resolver,0,sizeof(resolver)); - resolver.bInit = true; - -#ifdef __APPLE__ - // MacOS does not support unnamed semaphores - - char sn[64]; - snprintf(sn,sizeof(sn),"%s_%d",sem_name,getpid()); - resolver.sem = sem_open(sn,O_CREAT,0600,0); - if (resolver.sem==SEM_FAILED) - { - DLOG_PERROR("sem_open"); - goto ex; - } - // unlink immediately to remove tails - sem_unlink(sn); -#else - if (sem_init(&resolver._sem,0,0)==-1) - { - DLOG_PERROR("sem_init"); - goto ex; - } - resolver.sem = &resolver._sem; -#endif - - if (pthread_mutex_init(&resolver.resolve_list_lock, NULL)) goto ex; - - resolver.fd_signal_pipe = fd_signal_pipe; - TAILQ_INIT(&resolver.resolve_list); - - // start as many threads as we can up to specified number - resolver.thread = malloc(sizeof(pthread_t)*threads); - if (!resolver.thread) goto ex; - - memset(&action,0,sizeof(action)); - action.sa_handler = sigbreak; - sigaction(SIG_BREAK, &action, NULL); - - - pthread_attr_t attr; - if (pthread_attr_init(&attr)) goto ex; - // set minimum thread stack size - - if (pthread_attr_setstacksize(&attr,PTHREAD_STACK_MIN>32768 ? PTHREAD_STACK_MIN : 32768)) - { - pthread_attr_destroy(&attr); - goto ex; - } - - for(t=0, resolver.threads=threads ; tdom,dom,sizeof(ri->dom)); - ri->dom[sizeof(ri->dom)-1] = 0; - ri->port = port; - ri->ptr = ptr; - - rlist_lock; - TAILQ_INSERT_TAIL(&resolver.resolve_list, ri, next); - rlist_unlock; - if (sem_post(resolver.sem)<0) - { - DLOG_PERROR("resolver_queue sem_post"); - free(ri); - return NULL; - } - return ri; -} diff --git a/tpws/resolver.h b/tpws/resolver.h deleted file mode 100644 index d27c578f..00000000 --- a/tpws/resolver.h +++ /dev/null @@ -1,24 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include - -#include "helpers.h" - -struct resolve_item -{ - char dom[256]; // request dom - sockaddr_in46 ss; // resolve result - int ga_res; // getaddrinfo result code - uint16_t port; // request port - void *ptr; - TAILQ_ENTRY(resolve_item) next; -}; - -struct resolve_item *resolver_queue(const char *dom, uint16_t port, void *ptr); -void resolver_deinit(void); -bool resolver_init(int threads, int fd_signal_pipe); -int resolver_thread_count(void); diff --git a/tpws/sec.c b/tpws/sec.c deleted file mode 100644 index ed6bd4d3..00000000 --- a/tpws/sec.c +++ /dev/null @@ -1,372 +0,0 @@ -#define _GNU_SOURCE - -#include -#include -#include "sec.h" -#include -#include -#include - -#ifdef __linux__ - -#include -#include -#include -#include -// __X32_SYSCALL_BIT defined in linux/unistd.h -#include -#include -#include - -/************ SECCOMP ************/ - -// block most of the undesired syscalls to harden against code execution -static long blocked_syscalls[] = { -#ifdef SYS_execv -SYS_execv, -#endif -SYS_execve, -#ifdef SYS_execveat -SYS_execveat, -#endif -#ifdef SYS_exec_with_loader -SYS_exec_with_loader, -#endif -#ifdef SYS_osf_execve -SYS_osf_execve, -#endif -#ifdef SYS_uselib -SYS_uselib, -#endif -#ifdef SYS_unlink -SYS_unlink, -#endif -SYS_unlinkat, -#ifdef SYS_chmod -SYS_chmod, -#endif -SYS_fchmod,SYS_fchmodat, -#ifdef SYS_chown -SYS_chown, -#endif -#ifdef SYS_chown32 -SYS_chown32, -#endif -SYS_fchown, -#ifdef SYS_fchown32 -SYS_fchown32, -#endif -#ifdef SYS_lchown -SYS_lchown, -#endif -#ifdef SYS_lchown32 -SYS_lchown32, -#endif -SYS_fchownat, -#ifdef SYS_symlink -SYS_symlink, -#endif -SYS_symlinkat, -#ifdef SYS_link -SYS_link, -#endif -SYS_linkat, -SYS_truncate, -#ifdef SYS_truncate64 -SYS_truncate64, -#endif -SYS_ftruncate, -#ifdef SYS_ftruncate64 -SYS_ftruncate64, -#endif -#ifdef SYS_mknod -SYS_mknod, -#endif -SYS_mknodat, -#ifdef SYS_mkdir -SYS_mkdir, -#endif -SYS_mkdirat, -#ifdef SYS_rmdir -SYS_rmdir, -#endif -#ifdef SYS_rename -SYS_rename, -#endif -#ifdef SYS_renameat2 -SYS_renameat2, -#endif -#ifdef SYS_renameat -SYS_renameat, -#endif -#ifdef SYS_readdir -SYS_readdir, -#endif -#ifdef SYS_getdents -SYS_getdents, -#endif -#ifdef SYS_getdents64 -SYS_getdents64, -#endif -#ifdef SYS_process_vm_readv -SYS_process_vm_readv, -#endif -#ifdef SYS_process_vm_writev -SYS_process_vm_writev, -#endif -#ifdef SYS_process_madvise -SYS_process_madvise, -#endif -SYS_kill, SYS_ptrace -}; -#define BLOCKED_SYSCALL_COUNT (sizeof(blocked_syscalls)/sizeof(*blocked_syscalls)) - -static void set_filter(struct sock_filter *filter, __u16 code, __u8 jt, __u8 jf, __u32 k) -{ - filter->code = code; - filter->jt = jt; - filter->jf = jf; - filter->k = k; -} -// deny all blocked syscalls -static bool set_seccomp(void) -{ -#ifdef __X32_SYSCALL_BIT - #define SECCOMP_PROG_SIZE (6 + BLOCKED_SYSCALL_COUNT) -#else - #define SECCOMP_PROG_SIZE (5 + BLOCKED_SYSCALL_COUNT) -#endif - struct sock_filter sockf[SECCOMP_PROG_SIZE]; - struct sock_fprog prog = { .len = SECCOMP_PROG_SIZE, .filter = sockf }; - int i,idx=0; - - set_filter(&prog.filter[idx++], BPF_LD + BPF_W + BPF_ABS, 0, 0, arch_nr); -#ifdef __X32_SYSCALL_BIT - // x86 only - set_filter(&prog.filter[idx++], BPF_JMP + BPF_JEQ + BPF_K, 0, 3 + BLOCKED_SYSCALL_COUNT, ARCH_NR); // fail - set_filter(&prog.filter[idx++], BPF_LD + BPF_W + BPF_ABS, 0, 0, syscall_nr); - set_filter(&prog.filter[idx++], BPF_JMP + BPF_JGT + BPF_K, 1 + BLOCKED_SYSCALL_COUNT, 0, __X32_SYSCALL_BIT - 1); // fail -#else - set_filter(&prog.filter[idx++], BPF_JMP + BPF_JEQ + BPF_K, 0, 2 + BLOCKED_SYSCALL_COUNT, ARCH_NR); // fail - set_filter(&prog.filter[idx++], BPF_LD + BPF_W + BPF_ABS, 0, 0, syscall_nr); -#endif - -/* - // ! THIS IS NOT WORKING BECAUSE perror() in glibc dups() stderr - set_filter(&prog.filter[idx++], BPF_JMP + BPF_JEQ + BPF_K, 0, 3, SYS_write); // special check for write call - set_filter(&prog.filter[idx++], BPF_LD + BPF_W + BPF_ABS, 0, 0, syscall_arg(0)); // fd - set_filter(&prog.filter[idx++], BPF_JMP + BPF_JGT + BPF_K, 2 + BLOCKED_SYSCALL_COUNT, 0, 2); // 1 - stdout, 2 - stderr. greater are bad - set_filter(&prog.filter[idx++], BPF_LD + BPF_W + BPF_ABS, 0, 0, syscall_nr); // reload syscall_nr -*/ - for(i=0 ; i= 0; -} - -bool sec_harden(void) -{ - bool bRes = true; - if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) - { - DLOG_PERROR("PR_SET_NO_NEW_PRIVS(prctl)"); - bRes = false; - } -#if ARCH_NR!=0 - if (!set_seccomp()) - { - DLOG_PERROR("seccomp"); - if (errno==EINVAL) DLOG_ERR("seccomp: this can be safely ignored if kernel does not support seccomp\n"); - bRes = false; - } -#endif - return bRes; -} - - -bool checkpcap(uint64_t caps) -{ - if (!caps) return true; // no special caps reqd - - struct __user_cap_header_struct ch = {_LINUX_CAPABILITY_VERSION_3, getpid()}; - struct __user_cap_data_struct cd[2]; - uint32_t c0 = (uint32_t)caps; - uint32_t c1 = (uint32_t)(caps>>32); - - return !capget(&ch,cd) && (cd[0].effective & c0)==c0 && (cd[1].effective & c1)==c1; -} -bool setpcap(uint64_t caps) -{ - struct __user_cap_header_struct ch = {_LINUX_CAPABILITY_VERSION_3, getpid()}; - struct __user_cap_data_struct cd[2]; - - cd[0].effective = cd[0].permitted = (uint32_t)caps; - cd[0].inheritable = 0; - cd[1].effective = cd[1].permitted = (uint32_t)(caps>>32); - cd[1].inheritable = 0; - - return !capset(&ch,cd); -} -int getmaxcap(void) -{ - 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(void) -{ - uint64_t caps = 0; - int maxcap = getmaxcap(); - - if (setpcap(caps|(1< -#include - -#ifdef __linux__ - -#include -#include -#include - -bool checkpcap(uint64_t caps); -bool setpcap(uint64_t caps); -int getmaxcap(void); -bool dropcaps(void); - -#define syscall_nr (offsetof(struct seccomp_data, nr)) -#define arch_nr (offsetof(struct seccomp_data, arch)) -#define syscall_arg(x) (offsetof(struct seccomp_data, args[x])) - -#if defined(__aarch64__) - -# define ARCH_NR AUDIT_ARCH_AARCH64 - -#elif defined(__amd64__) - -# define ARCH_NR AUDIT_ARCH_X86_64 - -#elif defined(__arm__) && (defined(__ARM_EABI__) || defined(__thumb__)) - -# if __BYTE_ORDER == __LITTLE_ENDIAN -# define ARCH_NR AUDIT_ARCH_ARM -# else -# define ARCH_NR AUDIT_ARCH_ARMEB -# endif - -#elif defined(__i386__) - -# define ARCH_NR AUDIT_ARCH_I386 - -#elif defined(__mips__) - -#if _MIPS_SIM == _MIPS_SIM_ABI32 -# if __BYTE_ORDER == __LITTLE_ENDIAN -# define ARCH_NR AUDIT_ARCH_MIPSEL -# else -# define ARCH_NR AUDIT_ARCH_MIPS -# endif -#elif _MIPS_SIM == _MIPS_SIM_ABI64 -# if __BYTE_ORDER == __LITTLE_ENDIAN -# define ARCH_NR AUDIT_ARCH_MIPSEL64 -# else -# define ARCH_NR AUDIT_ARCH_MIPS64 -# endif -#else -# error "Unsupported mips abi" -#endif - -#elif defined(__PPC64__) - -# if __BYTE_ORDER == __LITTLE_ENDIAN -# define ARCH_NR AUDIT_ARCH_PPC64LE -# else -# define ARCH_NR AUDIT_ARCH_PPC64 -# endif - -#elif defined(__PPC__) - -# define ARCH_NR AUDIT_ARCH_PPC - -#elif __riscv && __riscv_xlen == 64 - -# define ARCH_NR AUDIT_ARCH_RISCV64 - -#else - -# error "Platform does not support seccomp filter yet" - -#endif - -#endif - -bool sec_harden(void); -bool can_drop_root(); -bool droproot(uid_t uid, const char *user, const gid_t *gid, int gid_count); -void print_id(void); -void daemonize(void); -bool writepid(const char *filename); diff --git a/tpws/socks.h b/tpws/socks.h deleted file mode 100644 index 5c9feb06..00000000 --- a/tpws/socks.h +++ /dev/null @@ -1,93 +0,0 @@ -#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/tamper.c b/tpws/tamper.c deleted file mode 100644 index 8e917562..00000000 --- a/tpws/tamper.c +++ /dev/null @@ -1,684 +0,0 @@ -#define _GNU_SOURCE - -#include -#include -#include "tamper.h" -#include "hostlist.h" -#include "ipset.h" -#include "protocol.h" -#include "helpers.h" -#include "pools.h" - -#define PKTDATA_MAXDUMP 32 - -void packet_debug(const uint8_t *data, size_t sz) -{ - hexdump_limited_dlog(data, sz, PKTDATA_MAXDUMP); VPRINT("\n"); -} - -static void TLSDebugHandshake(const uint8_t *tls,size_t sz) -{ - if (!params.debug) return; - - if (sz<6) return; - - const uint8_t *ext; - size_t len,len2; - - uint16_t v_handshake=pntoh16(tls+4), v, v2; - VPRINT("TLS handshake version : %s\n",TLSVersionStr(v_handshake)); - - if (TLSFindExtInHandshake(tls,sz,43,&ext,&len,false)) - { - if (len) - { - len2 = ext[0]; - if (len2=2) - { - len2 = pntoh16(ext); - if (len2<=(len-2)) - { - char s[32]; - for(ext+=2; len2 ;) - { - v = *ext; ext++; len2--; - if (v<=len2) - { - v2 = vhostname || strcmp(ipc->hostname,hostname)) - { - free(ipc->hostname); - if (!(ipc->hostname = strdup(hostname))) - { - DLOG_ERR("ipcache_put_hostname: out of memory\n"); - return false; - } - ipc->hostname_is_ip = hostname_is_ip; - VPRINT("hostname cached (is_ip=%u): %s\n", hostname_is_ip, hostname); - } - return true; -} -static bool ipcache_get_hostname(const struct in_addr *a4, const struct in6_addr *a6, char *hostname, size_t hostname_buf_len, bool *hostname_is_ip) -{ - if (!params.cache_hostname) - { - *hostname = 0; - return true; - } - ip_cache_item *ipc = ipcacheTouch(¶ms.ipcache,a4,a6); - if (!ipc) - { - DLOG_ERR("ipcache_get_hostname: out of memory\n"); - return false; - } - if (ipc->hostname) - { - VPRINT("got cached hostname (is_ip=%u): %s\n", ipc->hostname_is_ip, ipc->hostname); - snprintf(hostname,hostname_buf_len,"%s",ipc->hostname); - if (hostname_is_ip) *hostname_is_ip = ipc->hostname_is_ip; - } - else - *hostname = 0; - return true; -} - -static bool dp_match(struct desync_profile *dp, const struct sockaddr *dest, const char *hostname, bool bNoSubdom, t_l7proto l7proto) -{ - bool bHostlistsEmpty; - - if (!HostlistsReloadCheckForProfile(dp)) return false; - - if ((dest->sa_family==AF_INET && !dp->filter_ipv4) || (dest->sa_family==AF_INET6 && !dp->filter_ipv6)) - // L3 filter does not match - return false; - if (!port_filters_in_range(&dp->pf_tcp,saport(dest))) - // L4 filter does not match - return false; - if (dp->filter_l7 && !l7_proto_match(l7proto, dp->filter_l7)) - // L7 filter does not match - return false; - bHostlistsEmpty = PROFILE_HOSTLISTS_EMPTY(dp); - if (!dp->hostlist_auto && !hostname && !bHostlistsEmpty) - // avoid cpu consuming ipset check. profile cannot win if regular hostlists are present without auto hostlist and hostname is unknown. - return false; - if (!IpsetCheck(dp, dest->sa_family==AF_INET ? &((struct sockaddr_in*)dest)->sin_addr : NULL, dest->sa_family==AF_INET6 ? &((struct sockaddr_in6*)dest)->sin6_addr : NULL)) - // target ip does not match - return false; - - // autohostlist profile matching l3/l4/l7 filter always win - if (dp->hostlist_auto) return true; - - if (bHostlistsEmpty) - // profile without hostlist filter wins - return true; - else if (hostname) - // if hostlists are present profile matches only if hostname is known and satisfy profile hostlists - return HostlistCheck(dp, hostname, bNoSubdom, NULL, true); - - return false; -} -static struct desync_profile *dp_find(struct desync_profile_list_head *head, const struct sockaddr *dest, const char *hostname, bool bNoSubdom, t_l7proto l7proto) -{ - struct desync_profile_list *dpl; - if (params.debug) - { - char ip_port[48]; - ntop46_port(dest, ip_port,sizeof(ip_port)); - VPRINT("desync profile search for tcp target=%s l7proto=%s hostname='%s'\n", ip_port, l7proto_str(l7proto), hostname ? hostname : ""); - } - LIST_FOREACH(dpl, head, next) - { - if (dp_match(&dpl->dp,dest,hostname,bNoSubdom,l7proto)) - { - VPRINT("desync profile %d matches\n",dpl->dp.n); - return &dpl->dp; - } - } - VPRINT("desync profile not found\n"); - return NULL; -} - -void apply_desync_profile(t_ctrack *ctrack, const struct sockaddr *dest) -{ - ipcachePurgeRateLimited(¶ms.ipcache, params.ipcache_lifetime); - if (!ctrack->hostname) - { - char host[256]; - if (ipcache_get_hostname(dest->sa_family==AF_INET ? &((struct sockaddr_in*)dest)->sin_addr : NULL, dest->sa_family==AF_INET6 ? &((struct sockaddr_in6*)dest)->sin6_addr : NULL , host, sizeof(host), &ctrack->hostname_is_ip) && *host) - if (!(ctrack->hostname=strdup(host))) - DLOG_ERR("hostname dup : out of memory"); - } - ctrack->dp = dp_find(¶ms.desync_profiles, dest, ctrack->hostname, ctrack->hostname_is_ip, ctrack->l7proto); -} - - - -// segment buffer has at least 5 extra bytes to extend data block -void tamper_out(t_ctrack *ctrack, const struct sockaddr *dest, uint8_t *segment,size_t segment_buffer_size,size_t *size, size_t *multisplit_pos, int *multisplit_count, uint8_t *split_flags) -{ - uint8_t *p, *pp, *pHost = NULL; - size_t method_len = 0, pos, tpos, orig_size=*size; - const char *method; - bool bHaveHost = false, bHostIsIp = false; - char *pc, Host[256]; - t_l7proto l7proto; - - DBGPRINT("tamper_out\n"); - - if (!ctrack->dp) return; - - if (params.debug) - { - char ip_port[48]; - ntop46_port(dest,ip_port,sizeof(ip_port)); - VPRINT("tampering tcp segment with size %zu to %s\n", *size, ip_port); - if (ctrack->dp) VPRINT("using cached desync profile %d\n",ctrack->dp->n); - if (ctrack->hostname) VPRINT("connection hostname: %s\n", ctrack->hostname); - } - - if (dest->sa_family!=AF_INET && dest->sa_family!=AF_INET6) - { - DLOG_ERR("tamper_out dest family unknown\n"); - return; - } - - if (multisplit_count) *multisplit_count=0; - if (split_flags) *split_flags=0; - - if ((method = HttpMethod(segment,*size))) - { - method_len = strlen(method)-2; - VPRINT("Data block looks like http request start : %s\n", method); - l7proto=HTTP; - if (HttpFindHost(&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'; - bHaveHost = true; - for(pc = Host; *pc; pc++) *pc=tolower(*pc); - } - } - else if (IsTLSClientHello(segment,*size,false)) - { - VPRINT("Data block contains TLS ClientHello\n"); - l7proto=TLS; - TLSDebug(segment,*size); - bHaveHost=TLSHelloExtractHost((uint8_t*)segment,*size,Host,sizeof(Host),false); - } - else - { - VPRINT("Data block contains unknown payload\n"); - l7proto = UNKNOWN; - } - - if (bHaveHost) - { - bHostIsIp = strip_host_to_ip(Host); - VPRINT("request hostname: %s\n", Host); - if (!ipcache_put_hostname(dest->sa_family==AF_INET ? &((struct sockaddr_in*)dest)->sin_addr : NULL, dest->sa_family==AF_INET6 ? &((struct sockaddr_in6*)dest)->sin6_addr : NULL , Host, bHostIsIp)) - DLOG_ERR("ipcache_put_hostname: out of memory"); - } - - bool bDiscoveredL7 = ctrack->l7proto==UNKNOWN && l7proto!=UNKNOWN; - if (bDiscoveredL7) - { - VPRINT("discovered l7 protocol\n"); - ctrack->l7proto=l7proto; - } - - bool bDiscoveredHostname = bHaveHost && !ctrack->hostname_discovered; - if (bDiscoveredHostname) - { - VPRINT("discovered hostname\n"); - free(ctrack->hostname); - if (!(ctrack->hostname=strdup(Host))) - { - DLOG_ERR("strdup hostname : out of memory\n"); - return; - } - ctrack->hostname_is_ip = bHostIsIp; - ctrack->hostname_discovered = true; - } - - if (bDiscoveredL7 || bDiscoveredHostname) - { - struct desync_profile *dp_prev = ctrack->dp; - apply_desync_profile(ctrack, dest); - if (ctrack->dp!=dp_prev) - { - VPRINT("desync profile changed by revealed l7 protocol or hostname !\n"); - ctrack->b_host_checked = ctrack->b_ah_check = false; - } - } - - if (l7proto!=UNKNOWN && ctrack->dp->hostlist_auto) - { - if (bHaveHost && !ctrack->b_host_checked) - { - bool bHostExcluded; - ctrack->b_host_matches = HostlistCheck(ctrack->dp, Host, bHostIsIp, &bHostExcluded, false); - ctrack->b_host_checked = true; - if (!ctrack->b_host_matches) - ctrack->b_ah_check = !bHostExcluded; - } - if (!ctrack->b_host_matches) - { - VPRINT("Not acting on this request\n"); - return; - } - } - switch(l7proto) - { - case HTTP: - if (ctrack->dp->unixeol) - { - p = pp = segment; - while ((p = memmem(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 %td. Stop replacing.\n", pp - segment); - break; - } - pp = p; - } - pHost = NULL; // invalidate - } - if (ctrack->dp->methodeol && (*size+1+!ctrack->dp->unixeol)<=segment_buffer_size) - { - VPRINT("Adding EOL before method\n"); - if (ctrack->dp->unixeol) - { - memmove(segment + 1, segment, *size); - (*size)++;; - segment[0] = '\n'; - } - else - { - memmove(segment + 2, segment, *size); - *size += 2; - segment[0] = '\r'; - segment[1] = '\n'; - } - pHost = NULL; // invalidate - } - if (ctrack->dp->methodspace && *sizedp->hostdot || ctrack->dp->hosttab) && *sizedp->hostdot ? "dot" : "tab", pos); - memmove(p + 1, p, *size - pos); - *p = ctrack->dp->hostdot ? '.' : '\t'; // insert dot or tab - (*size)++; // block will grow by 1 byte - } - } - if (ctrack->dp->domcase && HttpFindHost(&pHost,segment,*size)) - { - p = pHost + 5; - pos = p - segment; - VPRINT("Mixing domain case at pos %zu\n",pos); - for (; p < (segment + *size) && *p != '\r' && *p != '\n'; p++) - *p = (((size_t)p) & 1) ? tolower(*p) : toupper(*p); - } - if (ctrack->dp->hostnospace && HttpFindHost(&pHost,segment,*size) && (pHost+5)<(segment+*size) && pHost[5] == ' ') - { - p = pHost + 6; - pos = p - segment; - VPRINT("Removing space before host name at pos %zu\n", pos); - memmove(p - 1, p, *size - pos); - (*size)--; // block will shrink by 1 byte - } - if (ctrack->dp->hostcase && HttpFindHost(&pHost,segment,*size)) - { - VPRINT("Changing 'Host:' => '%c%c%c%c:' at pos %td\n", ctrack->dp->hostspell[0], ctrack->dp->hostspell[1], ctrack->dp->hostspell[2], ctrack->dp->hostspell[3], pHost - segment); - memcpy(pHost, ctrack->dp->hostspell, 4); - } - if (ctrack->dp->hostpad && HttpFindHost(&pHost,segment,*size)) - { - // add : XXXXX: dp->unixeol ? 8 : 9; - size_t hostpad = ctrack->dp->hostpaddp->hostpad; - - if ((hsize+*size)>segment_buffer_size) - VPRINT("could not add host padding : buffer too small\n"); - else - { - if ((hostpad+*size)>segment_buffer_size) - { - hostpad=segment_buffer_size-*size; - VPRINT("host padding reduced to %zu bytes : buffer too small\n", hostpad); - } - else - VPRINT("host padding with %zu bytes\n", 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)dp->unixeol) - *p++='\n'; - else - { - *p++='\r'; - *p++='\n'; - } - hostpad-=hsize+padsize; - } - pHost = NULL; // invalidate - } - } - if (multisplit_pos) ResolveMultiPos(segment, *size, l7proto, ctrack->dp->splits, ctrack->dp->split_count, multisplit_pos, multisplit_count); - if (split_flags) - { - if (ctrack->dp->disorder_http) *split_flags |= SPLIT_FLAG_DISORDER; - if (ctrack->dp->oob_http) *split_flags |= SPLIT_FLAG_OOB; - } - break; - - case TLS: - if (multisplit_pos) ResolveMultiPos(segment, *size, l7proto, ctrack->dp->splits, ctrack->dp->split_count, multisplit_pos, multisplit_count); - if ((5+*size)<=segment_buffer_size) - { - tpos = ResolvePos(segment, *size, l7proto, &ctrack->dp->tlsrec); - if (tpos>5) - { - // construct 2 TLS records from one - uint16_t l = pntoh16(segment+3); // length - if (l>=2) - { - int i; - size_t dlen; - // length is checked in IsTLSClientHello and cannot exceed buffer size - if ((tpos-5)>=l) tpos=5+1; - VPRINT("making 2 TLS records at pos %zu\n",tpos); - memmove(segment+tpos+5,segment+tpos,*size-tpos); - segment[tpos] = segment[0]; - segment[tpos+1] = segment[1]; - segment[tpos+2] = segment[2]; - phton16(segment+tpos+3,l-(tpos-5)); - phton16(segment+3,tpos-5); - *size += 5; - VPRINT("-2nd TLS record: "); - dlen = tpos<16 ? tpos : 16; - packet_debug(segment+tpos-dlen,dlen); - VPRINT("+2nd TLS record: "); - packet_debug(segment+tpos,*size-tpos); - // fix split positions after tlsrec. increase split pos by tlsrec header size (5 bytes) - if (multisplit_pos) - for(i=0;i<*multisplit_count;i++) - if (multisplit_pos[i]>tpos) multisplit_pos[i]+=5; - } - } - } - if (split_flags) - { - if (ctrack->dp->disorder_tls) *split_flags |= SPLIT_FLAG_DISORDER; - if (ctrack->dp->oob_tls) *split_flags |= SPLIT_FLAG_OOB; - } - break; - - default: - if (multisplit_pos && ctrack->dp->split_any_protocol) - ResolveMultiPos(segment, *size, l7proto, ctrack->dp->splits, ctrack->dp->split_count, multisplit_pos, multisplit_count); - } - - if (split_flags) - { - if (ctrack->dp->disorder) *split_flags |= SPLIT_FLAG_DISORDER; - if (ctrack->dp->oob) *split_flags |= SPLIT_FLAG_OOB; - } - if (orig_size!=*size) - { - VPRINT("segment size changed: %zu -> %zu\n", orig_size, *size); - } - if (params.debug && multisplit_count && *multisplit_count) - { - VPRINT("multisplit pos: "); - for (int i=0;i<*multisplit_count;i++) VPRINT("%zu ",multisplit_pos[i]); - VPRINT("\n"); - } -} - -static void auto_hostlist_reset_fail_counter(struct desync_profile *dp, const char *hostname, const char *client_ip_port, t_l7proto l7proto) -{ - if (hostname) - { - hostfail_pool *fail_counter; - - fail_counter = HostFailPoolFind(dp->hostlist_auto_fail_counters, hostname); - if (fail_counter) - { - HostFailPoolDel(&dp->hostlist_auto_fail_counters, fail_counter); - VPRINT("auto hostlist (profile %d) : %s : fail counter reset. website is working.\n", dp->n, hostname); - HOSTLIST_DEBUGLOG_APPEND("%s : profile %d : client %s : proto %s : fail counter reset. website is working.", hostname, dp->n, client_ip_port, l7proto_str(l7proto)); - } - } -} - -static void auto_hostlist_failed(struct desync_profile *dp, const char *hostname, bool bNoSubdom, const char *client_ip_port, t_l7proto l7proto) -{ - hostfail_pool *fail_counter; - - fail_counter = HostFailPoolFind(dp->hostlist_auto_fail_counters, hostname); - if (!fail_counter) - { - fail_counter = HostFailPoolAdd(&dp->hostlist_auto_fail_counters, hostname, dp->hostlist_auto_fail_time); - if (!fail_counter) - { - DLOG_ERR("HostFailPoolAdd: out of memory\n"); - return; - } - } - fail_counter->counter++; - VPRINT("auto hostlist (profile %d) : %s : fail counter %d/%d\n", dp->n , hostname, fail_counter->counter, dp->hostlist_auto_fail_threshold); - HOSTLIST_DEBUGLOG_APPEND("%s : profile %d : client %s : proto %s : fail counter %d/%d", hostname, dp->n, client_ip_port, l7proto_str(l7proto), fail_counter->counter, dp->hostlist_auto_fail_threshold); - if (fail_counter->counter >= dp->hostlist_auto_fail_threshold) - { - VPRINT("auto hostlist (profile %d) : fail threshold reached. adding %s to auto hostlist\n", dp->n , hostname); - HostFailPoolDel(&dp->hostlist_auto_fail_counters, fail_counter); - - VPRINT("auto hostlist (profile %d) : rechecking %s to avoid duplicates\n", dp->n, hostname); - bool bExcluded=false; - if (!HostlistCheck(dp, hostname, bNoSubdom, &bExcluded, false) && !bExcluded) - { - VPRINT("auto hostlist (profile %d) : adding %s to %s\n", dp->n, hostname, dp->hostlist_auto->filename); - HOSTLIST_DEBUGLOG_APPEND("%s : profile %d : client %s : proto %s : adding to %s", hostname, dp->n, client_ip_port, l7proto_str(l7proto), dp->hostlist_auto->filename); - if (!HostlistPoolAddStr(&dp->hostlist_auto->hostlist, hostname, 0)) - { - DLOG_ERR("StrPoolAddStr out of memory\n"); - return; - } - if (!append_to_list_file(dp->hostlist_auto->filename, hostname)) - { - DLOG_PERROR("write to auto hostlist:"); - return; - } - if (!file_mod_signature(dp->hostlist_auto->filename, &dp->hostlist_auto->mod_sig)) - DLOG_PERROR("file_mod_signature"); - } - else - { - VPRINT("auto hostlist (profile %d) : NOT adding %s\n", dp->n, hostname); - HOSTLIST_DEBUGLOG_APPEND("%s : profile %d : client %s : proto %s : NOT adding, duplicate detected", hostname, dp->n, client_ip_port, l7proto_str(l7proto)); - } - } -} - -void tamper_in(t_ctrack *ctrack, const struct sockaddr *client, uint8_t *segment,size_t segment_buffer_size,size_t *size) -{ - DBGPRINT("tamper_in hostname=%s\n", ctrack->hostname); - - bool bFail=false; - - char client_ip_port[48]; - if (*params.hostlist_auto_debuglog) - ntop46_port((struct sockaddr*)client,client_ip_port,sizeof(client_ip_port)); - else - *client_ip_port=0; - - if (ctrack->dp && ctrack->b_ah_check) - { - HostFailPoolPurgeRateLimited(&ctrack->dp->hostlist_auto_fail_counters); - - if (ctrack->l7proto==HTTP && ctrack->hostname) - { - if (IsHttpReply(segment,*size)) - { - VPRINT("incoming HTTP reply detected for hostname %s\n", ctrack->hostname); - bFail = HttpReplyLooksLikeDPIRedirect(segment, *size, ctrack->hostname); - if (bFail) - { - VPRINT("redirect to another domain detected. possibly DPI redirect.\n"); - HOSTLIST_DEBUGLOG_APPEND("%s : profile %d : client %s : proto %s : redirect to another domain", ctrack->hostname, ctrack->dp->n, client_ip_port, l7proto_str(ctrack->l7proto)); - } - else - VPRINT("local or in-domain redirect detected. it's not a DPI redirect.\n"); - } - else - { - // received not http reply. do not monitor this connection anymore - VPRINT("incoming unknown HTTP data detected for hostname %s\n", ctrack->hostname); - } - if (bFail) auto_hostlist_failed(ctrack->dp, ctrack->hostname, ctrack->hostname_is_ip, client_ip_port, ctrack->l7proto); - } - if (!bFail) auto_hostlist_reset_fail_counter(ctrack->dp, ctrack->hostname, client_ip_port, ctrack->l7proto); - } - ctrack->bTamperInCutoff = true; -} - -void rst_in(t_ctrack *ctrack, const struct sockaddr *client) -{ - DBGPRINT("rst_in hostname=%s\n", ctrack->hostname); - - char client_ip_port[48]; - if (*params.hostlist_auto_debuglog) - ntop46_port((struct sockaddr*)client,client_ip_port,sizeof(client_ip_port)); - else - *client_ip_port=0; - - if (ctrack->dp && ctrack->b_ah_check) - { - HostFailPoolPurgeRateLimited(&ctrack->dp->hostlist_auto_fail_counters); - - if (!ctrack->bTamperInCutoff && ctrack->hostname) - { - VPRINT("incoming RST detected for hostname %s\n", ctrack->hostname); - HOSTLIST_DEBUGLOG_APPEND("%s : profile %d : client %s : proto %s : incoming RST", ctrack->hostname, ctrack->dp->n, client_ip_port, l7proto_str(ctrack->l7proto)); - auto_hostlist_failed(ctrack->dp, ctrack->hostname, ctrack->hostname_is_ip, client_ip_port, ctrack->l7proto); - } - } -} -void hup_out(t_ctrack *ctrack, const struct sockaddr *client) -{ - DBGPRINT("hup_out hostname=%s\n", ctrack->hostname); - - char client_ip_port[48]; - if (*params.hostlist_auto_debuglog) - ntop46_port((struct sockaddr*)client,client_ip_port,sizeof(client_ip_port)); - else - *client_ip_port=0; - - if (ctrack->dp && ctrack->b_ah_check) - { - HostFailPoolPurgeRateLimited(&ctrack->dp->hostlist_auto_fail_counters); - - if (!ctrack->bTamperInCutoff && ctrack->hostname) - { - // local leg dropped connection after first request. probably due to timeout. - VPRINT("local leg closed connection after first request (timeout ?). hostname: %s\n", ctrack->hostname); - HOSTLIST_DEBUGLOG_APPEND("%s : profile %d : client %s : proto %s : client closed connection without server reply", ctrack->hostname, ctrack->dp->n, client_ip_port, l7proto_str(ctrack->l7proto)); - auto_hostlist_failed(ctrack->dp, ctrack->hostname, ctrack->hostname_is_ip, client_ip_port, ctrack->l7proto); - } - } -} diff --git a/tpws/tamper.h b/tpws/tamper.h deleted file mode 100644 index 5b5aabf4..00000000 --- a/tpws/tamper.h +++ /dev/null @@ -1,34 +0,0 @@ -#pragma once - -#include -#include -#include - -#include "params.h" - -#define SPLIT_FLAG_DISORDER 0x01 -#define SPLIT_FLAG_OOB 0x02 - -typedef struct -{ - // common state - t_l7proto l7proto; - bool bTamperInCutoff; - bool b_host_checked,b_host_matches,b_ah_check; - bool hostname_discovered; - bool hostname_is_ip; - char *hostname; - struct desync_profile *dp; // desync profile cache -} t_ctrack; - -void apply_desync_profile(t_ctrack *ctrack, const struct sockaddr *dest); -bool ipcache_put_hostname(const struct in_addr *a4, const struct in6_addr *a6, const char *hostname, bool hostname_is_ip); - -void tamper_out(t_ctrack *ctrack, const struct sockaddr *dest, uint8_t *segment,size_t segment_buffer_size,size_t *size, size_t *multisplit_pos, int *multisplit_count, uint8_t *split_flags); -void tamper_in(t_ctrack *ctrack, const struct sockaddr *client, uint8_t *segment,size_t segment_buffer_size,size_t *size); -// connection reset by remote leg -void rst_in(t_ctrack *ctrack, const struct sockaddr *client); -// local leg closed connection (timeout waiting response ?) -void hup_out(t_ctrack *ctrack, const struct sockaddr *client); - -void packet_debug(const uint8_t *data, size_t sz); diff --git a/tpws/tpws.c b/tpws/tpws.c deleted file mode 100644 index a205e7f1..00000000 --- a/tpws/tpws.c +++ /dev/null @@ -1,2194 +0,0 @@ -#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 - -#ifdef __ANDROID__ -#include "andr/ifaddrs.h" -#else -#include -#endif - -#include "tpws.h" - -#ifdef BSD - #include -#endif - -#include "tpws_conn.h" -#include "hostlist.h" -#include "ipset.h" -#include "params.h" -#include "sec.h" -#include "redirect.h" -#include "helpers.h" -#include "gzip.h" -#include "pools.h" - - -#define MAX_CONFIG_FILE_SIZE 16384 - -struct params_s params; -static bool bReload=false; - -static void onhup(int sig) -{ - printf("HUP received ! Lists will be reloaded.\n"); - bReload=true; -} -void ReloadCheck() -{ - if (bReload) - { - ResetAllHostlistsModTime(); - if (!LoadAllHostLists()) - { - DLOG_ERR("hostlists load failed. this is fatal.\n"); - exit(1); - } - ResetAllIpsetModTime(); - if (!LoadAllIpsets()) - { - DLOG_ERR("ipset load failed. this is fatal.\n"); - exit(1); - } - bReload=false; - } -} - -static void onusr2(int sig) -{ - printf("\nHOSTFAIL POOL DUMP\n"); - - struct desync_profile_list *dpl; - LIST_FOREACH(dpl, ¶ms.desync_profiles, next) - { - printf("\nDESYNC PROFILE %d\n",dpl->dp.n); - HostFailPoolDump(dpl->dp.hostlist_auto_fail_counters); - } - - if (params.cache_hostname) - { - printf("\nIPCACHE\n"); - ipcachePrint(¶ms.ipcache); - } - - printf("\n"); -} - - -static int8_t block_sigpipe(void) -{ - sigset_t sigset; - memset(&sigset, 0, sizeof(sigset)); - - //Get the old sigset, add SIGPIPE and update sigset - if (sigprocmask(SIG_BLOCK, NULL, &sigset) == -1) { - DLOG_PERROR("sigprocmask (get)"); - return -1; - } - - if (sigaddset(&sigset, SIGPIPE) == -1) { - DLOG_PERROR("sigaddset"); - return -1; - } - - if (sigprocmask(SIG_BLOCK, &sigset, NULL) == -1) { - DLOG_PERROR("sigprocmask (set)"); - return -1; - } - - return 0; -} - -static bool test_list_files() -{ - struct hostlist_file *hfile; - struct ipset_file *ifile; - - LIST_FOREACH(hfile, ¶ms.hostlists, next) - if (hfile->filename && !file_open_test(hfile->filename, O_RDONLY)) - { - DLOG_PERROR("file_open_test"); - DLOG_ERR("cannot access hostlist file '%s'\n",hfile->filename); - return false; - } - LIST_FOREACH(ifile, ¶ms.ipsets, next) - if (ifile->filename && !file_open_test(ifile->filename, O_RDONLY)) - { - DLOG_PERROR("file_open_test"); - DLOG_ERR("cannot access ipset file '%s'\n",ifile->filename); - return false; - } - return true; -} - -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-1] = 0; - ioctl(sock, SIOCGIFFLAGS, &ifr); - close(sock); - return !!(ifr.ifr_flags & IFF_UP); -} -static int get_default_ttl(void) -{ - int sock,ttl=0; - socklen_t optlen=sizeof(ttl); - - if ((sock=socket(PF_INET, SOCK_DGRAM, IPPROTO_IP))!=-1) - { - getsockopt(sock, IPPROTO_IP, IP_TTL, &ttl, &optlen); - close(sock); - } - return ttl; -} - - -static void exithelp(void) -{ - printf( -#if !defined( __OpenBSD__) && !defined(__ANDROID__) - " @|$\t\t; read file for options. must be the only argument. other options are ignored.\n\n" -#endif - " --bind-addr=|\t; for v6 link locals append %%interface_name\n" - " --bind-iface4=\t\t; bind to the first ipv4 addr of interface\n" - " --bind-iface6=\t\t; bind to the first ipv6 addr of interface\n" - " --bind-linklocal=no|unwanted|prefer|force ; prohibit, accept, prefer or force ipv6 link local bind\n" - " --bind-wait-ifup=\t\t\t; wait for interface to appear and up\n" - " --bind-wait-ip=\t\t\t; after ifup wait for ip address to appear up to N seconds\n" - " --bind-wait-ip-linklocal=\t\t; (prefer) accept only LL first N seconds then any (unwanted) accept only globals first N seconds then LL\n" - " --bind-wait-only\t\t\t; wait for bind conditions satisfaction then exit. return code 0 if success.\n" - " * multiple binds are supported. each bind-addr, bind-iface* start new bind\n" - " --connect-bind-addr=| ; address for outbound connections. for v6 link locals append %%interface_name\n" - " --port=\t\t\t\t; only one port number for all binds is supported\n" - " --socks\t\t\t\t; implement socks4/5 proxy instead of transparent proxy\n" - " --no-resolve\t\t\t\t; disable socks5 remote dns ability\n" - " --resolver-threads=\t\t; number of resolver worker threads\n" - " --local-rcvbuf=\n" - " --local-sndbuf=\n" - " --remote-rcvbuf=\n" - " --remote-sndbuf=\n" -#ifdef SPLICE_PRESENT - " --nosplice\t\t\t\t; do not use splice to transfer data between sockets\n" -#endif - " --skip-nodelay\t\t\t\t; do not set TCP_NODELAY option for outgoing connections (incompatible with split options)\n" -#if defined(__linux__) || defined(__APPLE__) - " --local-tcp-user-timeout=\t; set tcp user timeout for local leg (default : %d, 0 = system default)\n" - " --remote-tcp-user-timeout=\t; set tcp user timeout for remote leg (default : %d, 0 = system default)\n" -#endif - " --maxconn=\n" -#ifdef SPLICE_PRESENT - " --maxfiles=\t\t; should be at least (X*connections+16), where X=6 in tcp proxy mode, X=4 in tampering mode\n" -#else - " --maxfiles=\t\t; should be at least (connections*2+16)\n" -#endif - " --max-orphan-time=\t\t; if local leg sends something and closes and remote leg is still connecting then cancel connection attempt after N seconds\n" - " --daemon\t\t\t\t; daemonize\n" - " --pidfile=\t\t\t; write pid to file\n" - " --user=\t\t\t; drop root privs\n" - " --uid=uid[:gid1,gid2,...]\t\t; drop root privs\n" -#if defined(__FreeBSD__) || defined(__OpenBSD__) - " --enable-pf\t\t\t\t; enable PF redirector support. required in FreeBSD when used with PF firewall.\n" -#endif -#if defined(__linux__) - " --fix-seg=\t\t\t; fix segmentation failures at the cost of possible slowdown. wait up to N msec (default %u)\n" -#endif - " --ipcache-lifetime=\t\t; time in seconds to keep cached domain name (default %u). 0 = no expiration\n" - " --ipcache-hostname=[0|1]\t\t; 1 or no argument enables ip->hostname caching\n" -#ifdef __ANDROID__ - " --debug=0|1|2|syslog|android|@ ; 1 and 2 means log to console and set debug level. for other targets use --debug-level.\n" -#else - " --debug=0|1|2|syslog|@\t; 1 and 2 means log to console and set debug level. for other targets use --debug-level.\n" -#endif - " --debug-level=0|1|2\t\t\t; specify debug level\n" - " --dry-run\t\t\t\t; verify parameters and exit with code 0 if successful\n" - " --version\t\t\t\t; print version and exit\n" - " --comment=any_text\n" - "\nMULTI-STRATEGY:\n" - " --new\t\t\t\t\t; begin new strategy\n" - " --skip\t\t\t\t\t; do not use this strategy\n" - " --filter-l3=ipv4|ipv6\t\t\t; L3 protocol filter. multiple comma separated values allowed.\n" - " --filter-tcp=[~]port1[-port2]|*\t; TCP port filter. ~ means negation. multiple comma separated values allowed.\n" - " --filter-l7=[http|tls|unknown]\t\t; L6-L7 protocol filter. multiple comma separated values allowed.\n" - " --ipset=\t\t\t; ipset include filter (one ip/CIDR per line, ipv4 and ipv6 accepted, gzip supported, multiple ipsets allowed)\n" - " --ipset-ip=\t\t\t; comma separated fixed subnet list\n" - " --ipset-exclude=\t\t; ipset exclude filter (one ip/CIDR per line, ipv4 and ipv6 accepted, gzip supported, multiple ipsets allowed)\n" - " --ipset-exclude-ip=\t\t; comma separated fixed subnet list\n" - "\nHOSTLIST FILTER:\n" - " --hostlist=\t\t\t; only act on hosts in the list (one host per line, subdomains auto apply, gzip supported, multiple hostlists allowed)\n" - " --hostlist-domains=\t; comma separated fixed domain list\n" - " --hostlist-exclude=\t\t; do not act on hosts in the list (one host per line, subdomains auto apply, gzip supported, multiple hostlists allowed)\n" - " --hostlist-exclude-domains= ; comma separated fixed domain list\n" - " --hostlist-auto=\t\t; detect DPI blocks and build hostlist automatically\n" - " --hostlist-auto-fail-threshold=\t; how many failed attempts cause hostname to be added to auto hostlist (default : %d)\n" - " --hostlist-auto-fail-time=\t; all failed attemps must be within these seconds (default : %d)\n" - " --hostlist-auto-debug=\t; debug auto hostlist positives\n" - "\nTAMPER:\n" - " --split-pos=N|-N|marker+N|marker-N\t; comma separated list of split positions\n" - "\t\t\t\t\t; markers: method,host,endhost,sld,endsld,midsld,sniext\n" - " --split-any-protocol\t\t\t; split not only http and TLS\n" -#if defined(BSD) && !defined(__APPLE__) - " --disorder[=http|tls]\t\t\t; when splitting simulate sending second fragment first (BSD sends entire message instead of first fragment, this is not good)\n" -#else - " --disorder[=http|tls]\t\t\t; when splitting simulate sending second fragment first\n" -#endif - " --oob[=http|tls]\t\t\t; when splitting send out of band byte. default is HEX 0x00.\n" - " --oob-data=|0xHEX\t\t; override default 0x00 OOB byte.\n" - " --hostcase\t\t\t\t; change Host: => host:\n" - " --hostspell\t\t\t\t; exact spelling of \"Host\" header. must be 4 chars. default is \"host\"\n" - " --hostdot\t\t\t\t; add \".\" after Host: name\n" - " --hosttab\t\t\t\t; add tab after Host: name\n" - " --hostnospace\t\t\t\t; remove space after Host:\n" - " --hostpad=\t\t\t; add dummy padding headers before Host:\n" - " --domcase\t\t\t\t; mix domain case : Host: TeSt.cOm\n" - " --methodspace\t\t\t\t; add extra space after method\n" - " --methodeol\t\t\t\t; add end-of-line before method\n" - " --unixeol\t\t\t\t; replace 0D0A to 0A\n" - " --tlsrec=N|-N|marker+N|marker-N\t; make 2 TLS records. split records at specified position.\n" -#ifdef __linux__ - " --mss=\t\t\t\t; set client MSS. forces server to split messages but significantly decreases speed !\n" -#endif - " --tamper-start=[n]\t\t; start tampering only from specified outbound stream position. default is 0. 'n' means data block number.\n" - " --tamper-cutoff=[n]\t\t; do not tamper anymore after specified outbound stream position. default is unlimited.\n", -#if defined(__linux__) || defined(__APPLE__) - DEFAULT_TCP_USER_TIMEOUT_LOCAL,DEFAULT_TCP_USER_TIMEOUT_REMOTE, -#endif -#ifdef __linux__ - FIX_SEG_DEFAULT_MAX_WAIT, -#endif - IPCACHE_LIFETIME, - HOSTLIST_AUTO_FAIL_THRESHOLD_DEFAULT, HOSTLIST_AUTO_FAIL_TIME_DEFAULT - ); - exit(1); -} -static void exithelp_clean(void) -{ - cleanup_params(¶ms); - exithelp(); -} -static void exit_clean(int code) -{ - cleanup_params(¶ms); - exit(code); -} -static void nextbind_clean(void) -{ - params.binds_last++; - if (params.binds_last>=MAX_BINDS) - { - DLOG_ERR("maximum of %d binds are supported\n",MAX_BINDS); - exit_clean(1); - } -} -static void checkbind_clean(void) -{ - if (params.binds_last<0) - { - DLOG_ERR("start new bind with --bind-addr,--bind-iface*\n"); - exit_clean(1); - } -} - - -void save_default_ttl(void) -{ - if (!params.ttl_default) - { - params.ttl_default = get_default_ttl(); - if (!params.ttl_default) - { - DLOG_ERR("could not get default ttl\n"); - exit_clean(1); - } - } -} - -static bool parse_httpreqpos(const char *s, struct proto_pos *sp) -{ - if (!strcmp(s, "method")) - { - sp->marker = PM_HTTP_METHOD; - sp->pos=2; - } - else if (!strcmp(s, "host")) - { - sp->marker = PM_HOST; - sp->pos=1; - } - else - return false; - return true; -} -static bool parse_tlspos(const char *s, struct proto_pos *sp) -{ - if (!strcmp(s, "sni")) - { - sp->marker = PM_HOST; - sp->pos=1; - } - else if (!strcmp(s, "sniext")) - { - sp->marker = PM_SNI_EXT; - sp->pos=1; - } - else if (!strcmp(s, "snisld")) - { - sp->marker = PM_HOST_MIDSLD; - sp->pos=0; - } - else - return false; - return true; -} - -static bool parse_int16(const char *p, int16_t *v) -{ - if (*p=='+' || *p=='-' || *p>='0' && *p<='9') - { - int i = atoi(p); - *v = (int16_t)i; - return *v==i; // check overflow - } - return false; -} -static bool parse_posmarker(const char *opt, uint8_t *posmarker) -{ - if (!strcmp(opt,"host")) - *posmarker = PM_HOST; - else if (!strcmp(opt,"endhost")) - *posmarker = PM_HOST_END; - else if (!strcmp(opt,"sld")) - *posmarker = PM_HOST_SLD; - else if (!strcmp(opt,"midsld")) - *posmarker = PM_HOST_MIDSLD; - else if (!strcmp(opt,"endsld")) - *posmarker = PM_HOST_ENDSLD; - else if (!strcmp(opt,"method")) - *posmarker = PM_HTTP_METHOD; - else if (!strcmp(opt,"sniext")) - *posmarker = PM_SNI_EXT; - else - return false; - return true; -} -static bool parse_split_pos(char *opt, struct proto_pos *split) -{ - if (parse_int16(opt,&split->pos)) - { - split->marker = PM_ABS; - return !!split->pos; - } - else - { - char c,*p=opt; - bool b; - - for (; *opt && *opt!='+' && *opt!='-'; opt++); - c=*opt; *opt=0; - b=parse_posmarker(p,&split->marker); - *opt=c; - if (!b) return false; - if (*opt) - return parse_int16(opt,&split->pos); - else - split->pos = 0; - } - return true; -} -static bool parse_split_pos_list(char *opt, struct proto_pos *splits, int splits_size, int *split_count) -{ - char c,*e,*p; - - for (p=opt, *split_count=0 ; p && *split_countdp; - for(x=0;xsplit_count;x++) - VPRINT("profile %d multisplit %s %d\n",dp->n,posmarker_name(dp->splits[x].marker),dp->splits[x].pos); - if (!PROTO_POS_EMPTY(&dp->tlsrec)) - VPRINT("profile %d tlsrec %s %d\n",dp->n,posmarker_name(dp->tlsrec.marker),dp->tlsrec.pos); - } -} - -static bool wf_make_l3(char *opt, bool *ipv4, bool *ipv6) -{ - char *e,*p,c; - - for (p=opt,*ipv4=*ipv6=false ; p ; ) - { - if ((e = strchr(p,','))) - { - c=*e; - *e=0; - } - - if (!strcmp(p,"ipv4")) - *ipv4 = true; - else if (!strcmp(p,"ipv6")) - *ipv6 = true; - else return false; - - if (e) *e++=c; - p = e; - } - return true; -} - -static bool parse_l7_list(char *opt, uint32_t *l7) -{ - char *e,*p,c; - - for (p=opt,*l7=0 ; p ; ) - { - if ((e = strchr(p,','))) - { - c=*e; - *e=0; - } - - if (!strcmp(p,"http")) - *l7 |= L7_PROTO_HTTP; - else if (!strcmp(p,"tls")) - *l7 |= L7_PROTO_TLS; - else if (!strcmp(p,"unknown")) - *l7 |= L7_PROTO_UNKNOWN; - else return false; - - if (e) *e++=c; - p = e; - } - return true; -} - -static bool parse_pf_list(char *opt, struct port_filters_head *pfl) -{ - char *e,*p,c; - port_filter pf; - bool b; - - for (p=opt ; p ; ) - { - if ((e = strchr(p,','))) - { - c=*e; - *e=0; - } - - b = pf_parse(p,&pf) && port_filter_add(pfl,&pf); - if (e) *e++=c; - if (!b) return false; - - p = e; - } - return true; -} - -static bool parse_domain_list(char *opt, hostlist_pool **pp) -{ - char *e,*p,c; - - for (p=opt ; p ; ) - { - if ((e = strchr(p,','))) - { - c=*e; - *e=0; - } - - if (*p && !AppendHostlistItem(pp,p)) return false; - - if (e) *e++=c; - p = e; - } - return true; -} - -static bool parse_ip_list(char *opt, ipset *pp) -{ - char *e,*p,c; - - for (p=opt ; p ; ) - { - if ((e = strchr(p,','))) - { - c=*e; - *e=0; - } - - if (*p && !AppendIpsetItem(pp,p)) return false; - - if (e) *e++=c; - p = e; - } - return true; -} - -static bool parse_uid(const char *opt, uid_t *uid, gid_t *gid, int *gid_count, int max_gids) -{ - unsigned int u; - char c, *p, *e; - - *gid_count=0; - if ((e = strchr(optarg,':'))) *e++=0; - if (sscanf(opt,"%u",&u)!=1) return false; - *uid = (uid_t)u; - for (p=e ; p ; ) - { - if ((e = strchr(p,','))) - { - c=*e; - *e=0; - } - if (p) - { - if (sscanf(p,"%u",&u)!=1) return false; - if (*gid_count>=max_gids) return false; - gid[(*gid_count)++] = (gid_t)u; - } - if (e) *e++=c; - p = e; - } - return true; -} - - -#if !defined( __OpenBSD__) && !defined(__ANDROID__) -// no static to not allow optimizer to inline this func (save stack) -void config_from_file(const char *filename) -{ - // config from a file - char buf[MAX_CONFIG_FILE_SIZE]; - buf[0]='x'; // fake argv[0] - buf[1]=' '; - size_t bufsize=sizeof(buf)-3; - if (!load_file(filename,buf+2,&bufsize)) - { - DLOG_ERR("could not load config file '%s'\n",filename); - exit_clean(1); - } - buf[bufsize+2]=0; - // wordexp fails if it sees \t \n \r between args - replace_char(buf,'\n',' '); - replace_char(buf,'\r',' '); - replace_char(buf,'\t',' '); - if (wordexp(buf, ¶ms.wexp, WRDE_NOCMD)) - { - DLOG_ERR("failed to split command line options from file '%s'\n",filename); - exit_clean(1); - } -} -#endif - -#ifndef __linux__ -static bool check_oob_disorder(const struct desync_profile *dp) -{ - return !( - dp->oob && (dp->disorder || dp->disorder_http || dp->disorder_tls) || - dp->oob_http && (dp->disorder || dp->disorder_http) || - dp->oob_tls && (dp->disorder || dp->disorder_tls)); -} -#endif - -enum opt_indices { - IDX_HELP, - IDX_H, - IDX_BIND_ADDR, - IDX_BIND_IFACE4, - IDX_BIND_IFACE6, - IDX_BIND_LINKLOCAL, - IDX_BIND_WAIT_IFUP, - IDX_BIND_WAIT_IP, - IDX_BIND_WAIT_IP_LINKLOCAL, - IDX_BIND_WAIT_ONLY, - IDX_PORT, - IDX_DAEMON, - IDX_USER, - IDX_UID, - IDX_MAXCONN, - IDX_MAXFILES, - IDX_MAX_ORPHAN_TIME, - IDX_IPCACHE_LIFETIME, - IDX_IPCACHE_HOSTNAME, - IDX_HOSTCASE, - IDX_HOSTSPELL, - IDX_HOSTDOT, - IDX_HOSTNOSPACE, - IDX_HOSTPAD, - IDX_DOMCASE, - IDX_SPLIT_HTTP_REQ, - IDX_SPLIT_TLS, - IDX_SPLIT_POS, - IDX_SPLIT_ANY_PROTOCOL, - IDX_DISORDER, - IDX_OOB, - IDX_OOB_DATA, - IDX_METHODSPACE, - IDX_METHODEOL, - IDX_HOSTTAB, - IDX_UNIXEOL, - IDX_TLSREC, - IDX_TLSREC_POS, - IDX_HOSTLIST, - IDX_HOSTLIST_DOMAINS, - IDX_HOSTLIST_EXCLUDE, - IDX_HOSTLIST_EXCLUDE_DOMAINS, - IDX_HOSTLIST_AUTO, - IDX_HOSTLIST_AUTO_FAIL_THRESHOLD, - IDX_HOSTLIST_AUTO_FAIL_TIME, - IDX_HOSTLIST_AUTO_DEBUG, - IDX_PIDFILE, - IDX_DEBUG, - IDX_DEBUG_LEVEL, - IDX_DRY_RUN, - IDX_VERSION, - IDX_COMMENT, - IDX_LOCAL_RCVBUF, - IDX_LOCAL_SNDBUF, - IDX_REMOTE_RCVBUF, - IDX_REMOTE_SNDBUF, - IDX_SOCKS, - IDX_NO_RESOLVE, - IDX_RESOLVER_THREADS, - IDX_SKIP_NODELAY, - IDX_TAMPER_START, - IDX_TAMPER_CUTOFF, - IDX_CONNECT_BIND_ADDR, - - IDX_NEW, - IDX_SKIP, - IDX_FILTER_L3, - IDX_FILTER_TCP, - IDX_FILTER_L7, - IDX_IPSET, - IDX_IPSET_IP, - IDX_IPSET_EXCLUDE, - IDX_IPSET_EXCLUDE_IP, - -#if defined(__FreeBSD__) || defined(__OpenBSD__) - IDX_ENABLE_PF, -#elif defined(__APPLE__) - IDX_LOCAL_TCP_USER_TIMEOUT, - IDX_REMOTE_TCP_USER_TIMEOUT, -#elif defined(__linux__) - IDX_LOCAL_TCP_USER_TIMEOUT, - IDX_REMOTE_TCP_USER_TIMEOUT, - IDX_MSS, - IDX_FIX_SEG, -#ifdef SPLICE_PRESENT - IDX_NOSPLICE, -#endif -#endif - IDX_HOSTLIST_AUTO_RETRANS_THRESHOLD, // ignored. for nfqws command line compatibility - IDX_LAST, -}; - -static const struct option long_options[] = { - [IDX_HELP] = {"help", no_argument, 0, 0}, - [IDX_H] = {"h", no_argument, 0, 0}, - [IDX_BIND_ADDR] = {"bind-addr", required_argument, 0, 0}, - [IDX_BIND_IFACE4] = {"bind-iface4", required_argument, 0, 0}, - [IDX_BIND_IFACE6] = {"bind-iface6", required_argument, 0, 0}, - [IDX_BIND_LINKLOCAL] = {"bind-linklocal", required_argument, 0, 0}, - [IDX_BIND_WAIT_IFUP] = {"bind-wait-ifup", required_argument, 0, 0}, - [IDX_BIND_WAIT_IP] = {"bind-wait-ip", required_argument, 0, 0}, - [IDX_BIND_WAIT_IP_LINKLOCAL] = {"bind-wait-ip-linklocal", required_argument, 0, 0}, - [IDX_BIND_WAIT_ONLY] = {"bind-wait-only", no_argument, 0, 0}, - [IDX_PORT] = {"port", required_argument, 0, 0}, - [IDX_DAEMON] = {"daemon", no_argument, 0, 0}, - [IDX_USER] = {"user", required_argument, 0, 0}, - [IDX_UID] = {"uid", required_argument, 0, 0}, - [IDX_MAXCONN] = {"maxconn", required_argument, 0, 0}, - [IDX_MAXFILES] = {"maxfiles", required_argument, 0, 0}, - [IDX_IPCACHE_LIFETIME] = {"ipcache-lifetime", required_argument, 0, 0}, - [IDX_IPCACHE_HOSTNAME] = {"ipcache-hostname", optional_argument, 0, 0}, - [IDX_MAX_ORPHAN_TIME] = {"max-orphan-time", required_argument, 0, 0}, - [IDX_HOSTCASE] = {"hostcase", no_argument, 0, 0}, - [IDX_HOSTSPELL] = {"hostspell", required_argument, 0, 0}, - [IDX_HOSTDOT] = {"hostdot", no_argument, 0, 0}, - [IDX_HOSTNOSPACE] = {"hostnospace", no_argument, 0, 0}, - [IDX_HOSTPAD] = {"hostpad", required_argument, 0, 0}, - [IDX_DOMCASE] = {"domcase", no_argument, 0, 0}, - [IDX_SPLIT_HTTP_REQ] = {"split-http-req", required_argument, 0, 0}, - [IDX_SPLIT_TLS] = {"split-tls", required_argument, 0, 0}, - [IDX_SPLIT_POS] = {"split-pos", required_argument, 0, 0}, - [IDX_SPLIT_ANY_PROTOCOL] = {"split-any-protocol", optional_argument, 0, 0}, - [IDX_DISORDER] = {"disorder", optional_argument, 0, 0}, - [IDX_OOB] = {"oob", optional_argument, 0, 0}, - [IDX_OOB_DATA] = {"oob-data", required_argument, 0, 0}, - [IDX_METHODSPACE] = {"methodspace", no_argument, 0, 0}, - [IDX_METHODEOL] = {"methodeol", no_argument, 0, 0}, - [IDX_HOSTTAB] = {"hosttab", no_argument, 0, 0}, - [IDX_UNIXEOL] = {"unixeol", no_argument, 0, 0}, - [IDX_TLSREC] = {"tlsrec", required_argument, 0, 0}, - [IDX_TLSREC_POS] = {"tlsrec-pos", required_argument, 0, 0}, - [IDX_HOSTLIST] = {"hostlist", required_argument, 0, 0}, - [IDX_HOSTLIST_DOMAINS] = {"hostlist-domains", required_argument, 0, 0}, - [IDX_HOSTLIST_EXCLUDE] = {"hostlist-exclude", required_argument, 0, 0}, - [IDX_HOSTLIST_EXCLUDE_DOMAINS] = {"hostlist-exclude-domains", required_argument, 0, 0}, - [IDX_HOSTLIST_AUTO] = {"hostlist-auto", required_argument, 0, 0}, - [IDX_HOSTLIST_AUTO_FAIL_THRESHOLD] = {"hostlist-auto-fail-threshold", required_argument, 0, 0}, - [IDX_HOSTLIST_AUTO_FAIL_TIME] = {"hostlist-auto-fail-time", required_argument, 0, 0}, - [IDX_HOSTLIST_AUTO_DEBUG] = {"hostlist-auto-debug", required_argument, 0, 0}, - [IDX_PIDFILE] = {"pidfile", required_argument, 0, 0}, - [IDX_DEBUG] = {"debug", optional_argument, 0, 0}, - [IDX_DEBUG_LEVEL] = {"debug-level", required_argument, 0, 0}, - [IDX_DRY_RUN] = {"dry-run", no_argument, 0, 0}, - [IDX_VERSION] = {"version", no_argument, 0, 0}, - [IDX_COMMENT] = {"comment", optional_argument, 0, 0}, - [IDX_LOCAL_RCVBUF] = {"local-rcvbuf", required_argument, 0, 0}, - [IDX_LOCAL_SNDBUF] = {"local-sndbuf", required_argument, 0, 0}, - [IDX_REMOTE_RCVBUF] = {"remote-rcvbuf", required_argument, 0, 0}, - [IDX_REMOTE_SNDBUF] = {"remote-sndbuf", required_argument, 0, 0}, - [IDX_SOCKS] = {"socks", no_argument, 0, 0}, - [IDX_NO_RESOLVE] = {"no-resolve", no_argument, 0, 0}, - [IDX_RESOLVER_THREADS] = {"resolver-threads", required_argument, 0, 0}, - [IDX_SKIP_NODELAY] = {"skip-nodelay", no_argument, 0, 0}, - [IDX_TAMPER_START] = {"tamper-start", required_argument, 0, 0}, - [IDX_TAMPER_CUTOFF] = {"tamper-cutoff", required_argument, 0, 0}, - [IDX_CONNECT_BIND_ADDR] = {"connect-bind-addr", required_argument, 0, 0}, - - [IDX_NEW] = {"new", no_argument, 0, 0}, - [IDX_SKIP] = {"skip", no_argument, 0, 0}, - [IDX_FILTER_L3] = {"filter-l3", required_argument, 0, 0}, - [IDX_FILTER_TCP] = {"filter-tcp", required_argument, 0, 0}, - [IDX_FILTER_L7] = {"filter-l7", required_argument, 0, 0}, - [IDX_IPSET] = {"ipset", required_argument, 0, 0}, - [IDX_IPSET_IP] = {"ipset-ip", required_argument, 0, 0}, - [IDX_IPSET_EXCLUDE] = {"ipset-exclude", required_argument, 0, 0}, - [IDX_IPSET_EXCLUDE_IP] = {"ipset-exclude-ip", required_argument, 0, 0}, - -#if defined(__FreeBSD__) || defined(__OpenBSD__) - [IDX_ENABLE_PF] = {"enable-pf", no_argument, 0, 0}, -#elif defined(__APPLE__) - [IDX_LOCAL_TCP_USER_TIMEOUT] = {"local-tcp-user-timeout", required_argument, 0, 0}, - [IDX_REMOTE_TCP_USER_TIMEOUT] = {"remote-tcp-user-timeout", required_argument, 0, 0}, -#elif defined(__linux__) - [IDX_LOCAL_TCP_USER_TIMEOUT] = {"local-tcp-user-timeout", required_argument, 0, 0}, - [IDX_REMOTE_TCP_USER_TIMEOUT] = {"remote-tcp-user-timeout", required_argument, 0, 0}, - [IDX_MSS] = {"mss", required_argument, 0, 0}, - [IDX_FIX_SEG] = {"fix-seg", optional_argument, 0, 0}, -#ifdef SPLICE_PRESENT - [IDX_NOSPLICE] = {"nosplice", no_argument, 0, 0}, -#endif -#endif - [IDX_HOSTLIST_AUTO_RETRANS_THRESHOLD] = {"hostlist-auto-retrans-threshold", optional_argument, 0, 0}, - [IDX_LAST] = {NULL, 0, NULL, 0}, -}; - -void parse_params(int argc, char *argv[]) -{ - int option_index = 0; - int v, i; - bool bSkip=false, bDry=false; - struct hostlist_file *anon_hl = NULL, *anon_hl_exclude = NULL; - struct ipset_file *anon_ips = NULL, *anon_ips_exclude = NULL; - - memset(¶ms, 0, sizeof(params)); - params.maxconn = DEFAULT_MAX_CONN; - params.max_orphan_time = DEFAULT_MAX_ORPHAN_TIME; - params.binds_last = -1; - params.ipcache_lifetime = IPCACHE_LIFETIME; -#if defined(__linux__) || defined(__APPLE__) - params.tcp_user_timeout_local = DEFAULT_TCP_USER_TIMEOUT_LOCAL; - params.tcp_user_timeout_remote = DEFAULT_TCP_USER_TIMEOUT_REMOTE; -#endif - -#if defined(__APPLE__) - params.pf_enable = true; // OpenBSD and MacOS have no other choice -#endif - -#ifdef __linux__ - params.fix_seg_avail = socket_supports_notsent(); -#endif - - LIST_INIT(¶ms.hostlists); - LIST_INIT(¶ms.ipsets); - - if (can_drop_root()) - { - params.uid = params.gid[0] = 0x7FFFFFFF; // default uid:gid - params.gid_count = 1; - params.droproot = true; - } - - struct desync_profile_list *dpl; - struct desync_profile *dp; - int desync_profile_count=0; - if (!(dpl = dp_list_add(¶ms.desync_profiles))) - { - DLOG_ERR("desync_profile_add: out of memory\n"); - exit_clean(1); - } - dp = &dpl->dp; - dp->n = ++desync_profile_count; - -#if !defined( __OpenBSD__) && !defined(__ANDROID__) - if (argc>=2 && (argv[1][0]=='@' || argv[1][0]=='$')) - { - config_from_file(argv[1]+1); - argv=params.wexp.we_wordv; - argc=params.wexp.we_wordc; - } -#endif - - while ((v = getopt_long_only(argc, argv, "", long_options, &option_index)) != -1) - { - if (v) - { - if (bDry) - exit_clean(1); - else - exithelp_clean(); - } - switch (option_index) - { - case IDX_HELP: - case IDX_H: - exithelp_clean(); - break; - case IDX_BIND_ADDR: - nextbind_clean(); - { - char *p = strchr(optarg,'%'); - if (p) - { - *p=0; - strncpy(params.binds[params.binds_last].bindiface, p+1, sizeof(params.binds[params.binds_last].bindiface)); - } - strncpy(params.binds[params.binds_last].bindaddr, optarg, sizeof(params.binds[params.binds_last].bindaddr)); - } - params.binds[params.binds_last].bindaddr[sizeof(params.binds[params.binds_last].bindaddr) - 1] = 0; - break; - case IDX_BIND_IFACE4: - nextbind_clean(); - params.binds[params.binds_last].bind_if6=false; - strncpy(params.binds[params.binds_last].bindiface, optarg, sizeof(params.binds[params.binds_last].bindiface)); - params.binds[params.binds_last].bindiface[sizeof(params.binds[params.binds_last].bindiface) - 1] = 0; - break; - case IDX_BIND_IFACE6: - nextbind_clean(); - params.binds[params.binds_last].bind_if6=true; - strncpy(params.binds[params.binds_last].bindiface, optarg, sizeof(params.binds[params.binds_last].bindiface)); - params.binds[params.binds_last].bindiface[sizeof(params.binds[params.binds_last].bindiface) - 1] = 0; - break; - case IDX_BIND_LINKLOCAL: - checkbind_clean(); - params.binds[params.binds_last].bindll = true; - if (!strcmp(optarg, "no")) - params.binds[params.binds_last].bindll=no; - else if (!strcmp(optarg, "prefer")) - params.binds[params.binds_last].bindll=prefer; - else if (!strcmp(optarg, "force")) - params.binds[params.binds_last].bindll=force; - else if (!strcmp(optarg, "unwanted")) - params.binds[params.binds_last].bindll=unwanted; - else - { - DLOG_ERR("invalid parameter in bind-linklocal : %s\n",optarg); - exit_clean(1); - } - break; - case IDX_BIND_WAIT_IFUP: - checkbind_clean(); - params.binds[params.binds_last].bind_wait_ifup = atoi(optarg); - break; - case IDX_BIND_WAIT_IP: - checkbind_clean(); - params.binds[params.binds_last].bind_wait_ip = atoi(optarg); - break; - case IDX_BIND_WAIT_IP_LINKLOCAL: - checkbind_clean(); - params.binds[params.binds_last].bind_wait_ip_ll = atoi(optarg); - break; - case IDX_BIND_WAIT_ONLY: - params.bind_wait_only = true; - break; - case IDX_PORT: - i = atoi(optarg); - if (i <= 0 || i > 65535) - { - DLOG_ERR("bad port number\n"); - exit_clean(1); - } - params.port = (uint16_t)i; - break; - case IDX_DAEMON: - params.daemon = true; - break; - case IDX_USER: - { - free(params.user); params.user=NULL; - struct passwd *pwd = getpwnam(optarg); - if (!pwd) - { - DLOG_ERR("non-existent username supplied\n"); - exit_clean(1); - } - params.uid = pwd->pw_uid; - params.gid[0]=pwd->pw_gid; - params.gid_count=1; - if (!(params.user=strdup(optarg))) - { - DLOG_ERR("strdup: out of memory\n"); - exit_clean(1); - } - params.droproot = true; - break; - } - case IDX_UID: - free(params.user); params.user=NULL; - if (!parse_uid(optarg,¶ms.uid,params.gid,¶ms.gid_count,MAX_GIDS)) - { - DLOG_ERR("--uid should be : uid[:gid,gid,...]\n"); - exit_clean(1); - } - if (!params.gid_count) - { - params.gid[0] = 0x7FFFFFFF; - params.gid_count = 1; - } - params.droproot = true; - break; - case IDX_MAXCONN: - params.maxconn = atoi(optarg); - if (params.maxconn <= 0 || params.maxconn > 10000) - { - DLOG_ERR("bad maxconn\n"); - exit_clean(1); - } - break; - case IDX_MAXFILES: - params.maxfiles = atoi(optarg); - if (params.maxfiles < 0) - { - DLOG_ERR("bad maxfiles\n"); - exit_clean(1); - } - break; - case IDX_MAX_ORPHAN_TIME: - params.max_orphan_time = atoi(optarg); - if (params.max_orphan_time < 0) - { - DLOG_ERR("bad max_orphan_time\n"); - exit_clean(1); - } - break; - case IDX_IPCACHE_LIFETIME: - if (sscanf(optarg, "%u", ¶ms.ipcache_lifetime)!=1) - { - DLOG_ERR("invalid ipcache-lifetime value\n"); - exit_clean(1); - } - break; - case IDX_IPCACHE_HOSTNAME: - params.cache_hostname = !optarg || !!atoi(optarg); - break; - case IDX_HOSTCASE: - dp->hostcase = true; - params.tamper = true; - break; - case IDX_HOSTSPELL: - if (strlen(optarg) != 4) - { - DLOG_ERR("hostspell must be exactly 4 chars long\n"); - exit_clean(1); - } - dp->hostcase = true; - memcpy(dp->hostspell, optarg, 4); - params.tamper = true; - break; - case IDX_HOSTDOT: - dp->hostdot = true; - params.tamper = true; - break; - case IDX_HOSTNOSPACE: - dp->hostnospace = true; - params.tamper = true; - break; - case IDX_HOSTPAD: - dp->hostpad = atoi(optarg); - params.tamper = true; - break; - case IDX_DOMCASE: - dp->domcase = true; - params.tamper = true; - break; - case IDX_SPLIT_HTTP_REQ: - DLOG_CONDUP("WARNING ! --split-http-req is deprecated. use --split-pos with markers.\n",MAX_SPLITS); - if (dp->split_count>=MAX_SPLITS) - { - DLOG_ERR("Too much splits. max splits: %u\n",MAX_SPLITS); - exit_clean(1); - } - if (!parse_httpreqpos(optarg, dp->splits + dp->split_count)) - { - DLOG_ERR("Invalid argument for split-http-req\n"); - exit_clean(1); - } - dp->split_count++; - params.tamper = true; - break; - case IDX_SPLIT_TLS: - // obsolete arg - DLOG_CONDUP("WARNING ! --split-tls is deprecated. use --split-pos with markers.\n",MAX_SPLITS); - if (dp->split_count>=MAX_SPLITS) - { - DLOG_ERR("Too much splits. max splits: %u\n",MAX_SPLITS); - exit_clean(1); - } - if (!parse_tlspos(optarg, dp->splits + dp->split_count)) - { - DLOG_ERR("Invalid argument for split-tls\n"); - exit_clean(1); - } - dp->split_count++; - params.tamper = true; - break; - case IDX_SPLIT_POS: - { - int ct; - if (!parse_split_pos_list(optarg,dp->splits+dp->split_count,MAX_SPLITS-dp->split_count,&ct)) - { - DLOG_ERR("could not parse split pos list or too much positions (before parsing - %u, max - %u) : %s\n",dp->split_count,MAX_SPLITS,optarg); - exit_clean(1); - } - dp->split_count += ct; - } - params.tamper = true; - break; - case IDX_SPLIT_ANY_PROTOCOL: - dp->split_any_protocol = true; - break; - case IDX_DISORDER: - if (optarg) - { - if (!strcmp(optarg,"http")) dp->disorder_http=true; - else if (!strcmp(optarg,"tls")) dp->disorder_tls=true; - else - { - DLOG_ERR("Invalid argument for disorder\n"); - exit_clean(1); - } - } - else - dp->disorder = true; -#ifndef __linux__ - if (!check_oob_disorder(dp)) - { - DLOG_ERR("--oob and --disorder work simultaneously only in linux. in this system it's guaranteed to fail.\n"); - exit_clean(1); - } -#endif - break; - case IDX_OOB: - if (optarg) - { - if (!strcmp(optarg,"http")) dp->oob_http=true; - else if (!strcmp(optarg,"tls")) dp->oob_tls=true; - else - { - DLOG_ERR("Invalid argument for oob\n"); - exit_clean(1); - } - } - else - dp->oob = true; -#ifndef __linux__ - if (!check_oob_disorder(dp)) - { - DLOG_ERR("--oob and --disorder work simultaneously only in linux. in this system it's guaranteed to fail.\n"); - exit_clean(1); - } -#endif - break; - case IDX_OOB_DATA: - { - size_t l = strlen(optarg); - unsigned int bt; - if (l==1) dp->oob_byte = (uint8_t)*optarg; - else if (l!=4 || sscanf(optarg,"0x%02X",&bt)!=1) - { - DLOG_ERR("Invalid argument for oob-data\n"); - exit_clean(1); - } - else dp->oob_byte = (uint8_t)bt; - } - break; - case IDX_METHODSPACE: - dp->methodspace = true; - params.tamper = true; - break; - case IDX_METHODEOL: - dp->methodeol = true; - params.tamper = true; - break; - case IDX_HOSTTAB: - dp->hosttab = true; - params.tamper = true; - break; - case IDX_UNIXEOL: - dp->unixeol = true; - params.tamper = true; - break; - case IDX_TLSREC: - if (!parse_split_pos(optarg, &dp->tlsrec) && !parse_tlspos(optarg, &dp->tlsrec)) - { - DLOG_ERR("Invalid argument for tlsrec\n"); - exit_clean(1); - } - params.tamper = true; - break; - case IDX_TLSREC_POS: - // obsolete arg - i = atoi(optarg); - dp->tlsrec.marker = PM_ABS; - dp->tlsrec.pos = (int16_t)i; - if (!dp->tlsrec.pos || i!=dp->tlsrec.pos) - { - DLOG_ERR("Invalid argument for tlsrec-pos\n"); - exit_clean(1); - } - params.tamper = true; - break; - case IDX_HOSTLIST: - if (bSkip) break; - if (!RegisterHostlist(dp, false, optarg)) - { - DLOG_ERR("failed to register hostlist '%s'\n", optarg); - exit_clean(1); - } - params.tamper = true; - break; - case IDX_HOSTLIST_DOMAINS: - if (bSkip) break; - if (!anon_hl && !(anon_hl=RegisterHostlist(dp, false, NULL))) - { - DLOG_ERR("failed to register anonymous hostlist\n"); - exit_clean(1); - } - if (!parse_domain_list(optarg, &anon_hl->hostlist)) - { - DLOG_ERR("failed to add domains to anonymous hostlist\n"); - exit_clean(1); - } - params.tamper = true; - break; - case IDX_HOSTLIST_EXCLUDE: - if (bSkip) break; - if (!RegisterHostlist(dp, true, optarg)) - { - DLOG_ERR("failed to register hostlist '%s'\n", optarg); - exit_clean(1); - } - params.tamper = true; - break; - case IDX_HOSTLIST_EXCLUDE_DOMAINS: - if (bSkip) break; - if (!anon_hl_exclude && !(anon_hl_exclude=RegisterHostlist(dp, true, NULL))) - { - DLOG_ERR("failed to register anonymous hostlist\n"); - exit_clean(1); - } - if (!parse_domain_list(optarg, &anon_hl_exclude->hostlist)) - { - DLOG_ERR("failed to add domains to anonymous hostlist\n"); - exit_clean(1); - } - params.tamper = true; - break; - case IDX_HOSTLIST_AUTO: - if (bSkip) break; - if (dp->hostlist_auto) - { - DLOG_ERR("only one auto hostlist per profile is supported\n"); - exit_clean(1); - } - { - FILE *F = fopen(optarg,"a+b"); - if (!F) - { - DLOG_ERR("cannot create %s\n", optarg); - exit_clean(1); - } - bool bGzip = is_gzip(F); - fclose(F); - if (bGzip) - { - DLOG_ERR("gzipped auto hostlists are not supported\n"); - exit_clean(1); - } - } - if (!(dp->hostlist_auto=RegisterHostlist(dp, false, optarg))) - { - DLOG_ERR("failed to register hostlist '%s'\n", optarg); - exit_clean(1); - } - params.tamper = true; // need to detect blocks and update autohostlist. cannot just slice. - break; - case IDX_HOSTLIST_AUTO_FAIL_THRESHOLD: - dp->hostlist_auto_fail_threshold = (uint8_t)atoi(optarg); - if (dp->hostlist_auto_fail_threshold<1 || dp->hostlist_auto_fail_threshold>20) - { - DLOG_ERR("auto hostlist fail threshold must be within 1..20\n"); - exit_clean(1); - } - break; - case IDX_HOSTLIST_AUTO_FAIL_TIME: - dp->hostlist_auto_fail_time = (uint8_t)atoi(optarg); - if (dp->hostlist_auto_fail_time<1) - { - DLOG_ERR("auto hostlist fail time is not valid\n"); - exit_clean(1); - } - break; - case IDX_HOSTLIST_AUTO_DEBUG: - { - FILE *F = fopen(optarg,"a+t"); - if (!F) - { - DLOG_ERR("cannot create %s\n", optarg); - exit_clean(1); - } - fclose(F); - strncpy(params.hostlist_auto_debuglog, optarg, sizeof(params.hostlist_auto_debuglog)); - params.hostlist_auto_debuglog[sizeof(params.hostlist_auto_debuglog) - 1] = '\0'; - } - break; - case IDX_PIDFILE: - snprintf(params.pidfile,sizeof(params.pidfile),"%s",optarg); - break; - case IDX_DEBUG: - if (optarg) - { - if (*optarg=='@') - { - strncpy(params.debug_logfile,optarg+1,sizeof(params.debug_logfile)); - params.debug_logfile[sizeof(params.debug_logfile)-1] = 0; - FILE *F = fopen(params.debug_logfile,"wt"); - if (!F) - { - fprintf(stderr, "cannot create %s\n", params.debug_logfile); - exit_clean(1); - } - if (!params.debug) params.debug = 1; - params.debug_target = LOG_TARGET_FILE; - } - else if (!strcmp(optarg,"syslog")) - { - if (!params.debug) params.debug = 1; - params.debug_target = LOG_TARGET_SYSLOG; - openlog(progname,LOG_PID,LOG_USER); - } -#ifdef __ANDROID__ - else if (!strcmp(optarg,"android")) - { - if (!params.debug) params.debug = 1; - params.debug_target = LOG_TARGET_ANDROID; - } -#endif - else if (optarg[0]>='0' && optarg[0]<='2') - { - params.debug = atoi(optarg); - params.debug_target = LOG_TARGET_CONSOLE; - } - else - { - fprintf(stderr, "invalid debug mode : %s\n", optarg); - exit_clean(1); - } - } - else - { - params.debug = 1; - params.debug_target = LOG_TARGET_CONSOLE; - } - break; - case IDX_DEBUG_LEVEL: - params.debug = atoi(optarg); - break; - case IDX_DRY_RUN: - bDry = true; - break; - case IDX_VERSION: - exit_clean(0); - break; - case IDX_COMMENT: - break; - case IDX_LOCAL_RCVBUF: -#ifdef __linux__ - params.local_rcvbuf = atoi(optarg)/2; -#else - params.local_rcvbuf = atoi(optarg); -#endif - break; - case IDX_LOCAL_SNDBUF: -#ifdef __linux__ - params.local_sndbuf = atoi(optarg)/2; -#else - params.local_sndbuf = atoi(optarg); -#endif - break; - case IDX_REMOTE_RCVBUF: -#ifdef __linux__ - params.remote_rcvbuf = atoi(optarg)/2; -#else - params.remote_rcvbuf = atoi(optarg); -#endif - break; - case IDX_REMOTE_SNDBUF: -#ifdef __linux__ - params.remote_sndbuf = atoi(optarg)/2; -#else - params.remote_sndbuf = atoi(optarg); -#endif - break; - case IDX_SOCKS: - params.proxy_type = CONN_TYPE_SOCKS; - break; - case IDX_NO_RESOLVE: - params.no_resolve = true; - break; - case IDX_RESOLVER_THREADS: - params.resolver_threads = atoi(optarg); - if (params.resolver_threads<1 || params.resolver_threads>300) - { - DLOG_ERR("resolver-threads must be within 1..300\n"); - exit_clean(1); - } - break; - case IDX_SKIP_NODELAY: - params.skip_nodelay = true; - break; - case IDX_TAMPER_START: - { - const char *p=optarg; - if (*p=='n') - { - dp->tamper_start_n=true; - p++; - } - else - dp->tamper_start_n=false; - dp->tamper_start = atoi(p); - } - params.tamper_lim = true; - break; - case IDX_TAMPER_CUTOFF: - { - const char *p=optarg; - if (*p=='n') - { - dp->tamper_cutoff_n=true; - p++; - } - else - dp->tamper_cutoff_n=false; - dp->tamper_cutoff = atoi(p); - } - params.tamper_lim = true; - break; - case IDX_CONNECT_BIND_ADDR: - { - char *p = strchr(optarg,'%'); - if (p) *p++=0; - if (inet_pton(AF_INET, optarg, ¶ms.connect_bind4.sin_addr)) - { - params.connect_bind4.sin_family = AF_INET; - } - else if (inet_pton(AF_INET6, optarg, ¶ms.connect_bind6.sin6_addr)) - { - params.connect_bind6.sin6_family = AF_INET6; - if (p && *p) - { - // copy interface name for delayed resolution - strncpy(params.connect_bind6_ifname,p,sizeof(params.connect_bind6_ifname)); - params.connect_bind6_ifname[sizeof(params.connect_bind6_ifname)-1]=0; - } - - } - else - { - DLOG_ERR("bad bind addr : %s\n", optarg); - exit_clean(1); - } - } - break; - - - case IDX_NEW: - if (bSkip) - { - dp_clear(dp); - dp_init(dp); - dp->n = desync_profile_count; - bSkip = false; - } - else - { - if (!(dpl = dp_list_add(¶ms.desync_profiles))) - { - DLOG_ERR("desync_profile_add: out of memory\n"); - exit_clean(1); - } - dp = &dpl->dp; - dp->n = ++desync_profile_count; - } - anon_hl = anon_hl_exclude = NULL; - anon_ips = anon_ips_exclude = NULL; - break; - case IDX_SKIP: - bSkip = true; - break; - case IDX_FILTER_L3: - if (!wf_make_l3(optarg,&dp->filter_ipv4,&dp->filter_ipv6)) - { - DLOG_ERR("bad value for --filter-l3\n"); - exit_clean(1); - } - break; - case IDX_FILTER_TCP: - if (!parse_pf_list(optarg,&dp->pf_tcp)) - { - DLOG_ERR("Invalid port filter : %s\n",optarg); - exit_clean(1); - } - break; - case IDX_FILTER_L7: - if (!parse_l7_list(optarg,&dp->filter_l7)) - { - DLOG_ERR("Invalid l7 filter : %s\n",optarg); - exit_clean(1); - } - break; - case IDX_IPSET: - if (bSkip) break; - if (!RegisterIpset(dp, false, optarg)) - { - DLOG_ERR("failed to register ipset '%s'\n", optarg); - exit_clean(1); - } - params.tamper = true; - break; - case IDX_IPSET_IP: - if (bSkip) break; - if (!anon_ips && !(anon_ips=RegisterIpset(dp, false, NULL))) - { - DLOG_ERR("failed to register anonymous ipset\n"); - exit_clean(1); - } - if (!parse_ip_list(optarg, &anon_ips->ipset)) - { - DLOG_ERR("failed to add subnets to anonymous ipset\n"); - exit_clean(1); - } - params.tamper = true; - break; - case IDX_IPSET_EXCLUDE: - if (bSkip) break; - if (!RegisterIpset(dp, true, optarg)) - { - DLOG_ERR("failed to register ipset '%s'\n", optarg); - exit_clean(1); - } - params.tamper = true; - break; - case IDX_IPSET_EXCLUDE_IP: - if (bSkip) break; - if (!anon_ips_exclude && !(anon_ips_exclude=RegisterIpset(dp, true, NULL))) - { - DLOG_ERR("failed to register anonymous ipset\n"); - exit_clean(1); - } - if (!parse_ip_list(optarg, &anon_ips_exclude->ipset)) - { - DLOG_ERR("failed to add subnets to anonymous ipset\n"); - exit_clean(1); - } - params.tamper = true; - break; - -#if defined(__FreeBSD__) || defined(__OpenBSD__) - case IDX_ENABLE_PF: - params.pf_enable = true; - break; -#elif defined(__linux__) || defined(__APPLE__) - case IDX_LOCAL_TCP_USER_TIMEOUT: - params.tcp_user_timeout_local = atoi(optarg); - if (params.tcp_user_timeout_local<0 || params.tcp_user_timeout_local>86400) - { - DLOG_ERR("Invalid argument for tcp user timeout. must be 0..86400\n"); - exit_clean(1); - } - break; - case IDX_REMOTE_TCP_USER_TIMEOUT: - params.tcp_user_timeout_remote = atoi(optarg); - if (params.tcp_user_timeout_remote<0 || params.tcp_user_timeout_remote>86400) - { - DLOG_ERR("Invalid argument for tcp user timeout. must be 0..86400\n"); - exit_clean(1); - } - break; -#endif - -#if defined(__linux__) - case IDX_MSS: - // this option does not work in any BSD and MacOS. OS may accept but it changes nothing - dp->mss = atoi(optarg); - if (dp->mss<88 || dp->mss>32767) - { - DLOG_ERR("Invalid value for MSS. Linux accepts MSS 88-32767.\n"); - exit_clean(1); - } - break; - case IDX_FIX_SEG: - if (!params.fix_seg_avail) - { - DLOG_ERR("--fix-seg is supported since kernel 4.6\n"); - exit_clean(1); - } - if (optarg) - { - i = atoi(optarg); - if (i < 0 || i > 1000) - { - DLOG_ERR("fix_seg value must be within 0..1000\n"); - exit_clean(1); - } - params.fix_seg = i; - } - else - params.fix_seg = FIX_SEG_DEFAULT_MAX_WAIT; - break; -#ifdef SPLICE_PRESENT - case IDX_NOSPLICE: - params.nosplice = true; - break; -#endif -#endif - } - } - if (bSkip) - { - LIST_REMOVE(dpl,next); - dp_entry_destroy(dpl); - desync_profile_count--; - } - - if (!params.bind_wait_only && !params.port) - { - DLOG_ERR("Need port number\n"); - exit_clean(1); - } - if (params.binds_last<=0) - { - params.binds_last=0; // default bind to all - } - if (!params.resolver_threads) params.resolver_threads = 5 + params.maxconn/50; - - VPRINT("adding low-priority default empty desync profile\n"); - // add default empty profile - if (!(dpl = dp_list_add(¶ms.desync_profiles))) - { - DLOG_ERR("desync_profile_add: out of memory\n"); - exit_clean(1); - } - - DLOG_CONDUP("we have %d user defined desync profile(s) and default low priority profile 0\n",desync_profile_count); - - save_default_ttl(); - if (params.debug_target == LOG_TARGET_FILE && params.droproot && chown(params.debug_logfile, params.uid, -1)) - fprintf(stderr, "could not chown %s. log file may not be writable after privilege drop\n", params.debug_logfile); - if (params.droproot && *params.hostlist_auto_debuglog && chown(params.hostlist_auto_debuglog, params.uid, -1)) - DLOG_ERR("could not chown %s. auto hostlist debug log may not be writable after privilege drop\n", params.hostlist_auto_debuglog); - -#ifdef __linux__ - bool bHasMSS=false, bHasOOB=false, bHasDisorder=false; -#endif - LIST_FOREACH(dpl, ¶ms.desync_profiles, next) - { - dp = &dpl->dp; - if (params.skip_nodelay && dp->split_count) - { - DLOG_ERR("Cannot split with --skip-nodelay\n"); - exit_clean(1); - } - if (params.droproot && dp->hostlist_auto && chown(dp->hostlist_auto->filename, params.uid, -1)) - DLOG_ERR("could not chown %s. auto hostlist file may not be writable after privilege drop\n", dp->hostlist_auto->filename); -#ifdef __linux__ - if (dp->mss) bHasMSS=true; - if (dp->oob || dp->oob_http || dp->oob_tls) bHasOOB=true; - if (dp->disorder || dp->disorder_http || dp->disorder_tls) bHasDisorder=true; -#endif - } -#ifdef __linux__ - if (is_wsl()==1) - { - if (!params.nosplice) DLOG_CONDUP("WARNING ! WSL1 may have problems with splice. Consider using `--nosplice`.\n"); - if (bHasMSS) DLOG_CONDUP("WARNING ! WSL1 does not support MSS socket option. MSS will likely fail.\n"); - if (bHasOOB) DLOG_CONDUP("WARNING ! WSL1 does not support OOB. OOB will likely fail.\n"); - if (bHasDisorder) DLOG_CONDUP("WARNING ! Windows retransmits whole TCP segment. Disorder will not function properly.\n"); - fflush(stdout); - } -#endif - - if (!test_list_files()) - exit_clean(1); - - if (!LoadAllHostLists()) - { - DLOG_ERR("hostlists load failed\n"); - exit_clean(1); - } - if (!LoadAllIpsets()) - { - DLOG_ERR("ipset load failed\n"); - exit_clean(1); - } - - VPRINT("\nlists summary:\n"); - HostlistsDebug(); - IpsetsDebug(); - - VPRINT("\nsplits summary:\n"); - SplitDebug(); - VPRINT("\n"); - -#if !defined( __OpenBSD__) && !defined(__ANDROID__) - // do not need args from file anymore - cleanup_args(¶ms); -#endif - if (bDry) - { - if (params.droproot) - { - if (!droproot(params.uid,params.user,params.gid,params.gid_count)) - exit_clean(1); -#ifdef __linux__ - if (!dropcaps()) - exit_clean(1); -#endif - print_id(); - if (!test_list_files()) - exit_clean(1); - } - DLOG_CONDUP("command line parameters verified\n"); - exit_clean(0); - } -} - - -static bool find_listen_addr(struct sockaddr_storage *salisten, const char *bindiface, bool bind_if6, enum bindll bindll, int *if_index) -{ - struct ifaddrs *addrs,*a; - bool found=false; - - if (getifaddrs(&addrs)<0) - return false; - - // for ipv6 preference order - // bind-linklocal-1 : link-local,any - // bind-linklocal=0 : private,global,link-local - for(int pass=0;pass<3;pass++) - { - a = addrs; - while (a) - { - if (a->ifa_addr) - { - if (a->ifa_addr->sa_family==AF_INET && - *bindiface && !bind_if6 && !strcmp(a->ifa_name, 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; - goto ex; - } - // ipv6 links locals are fe80::/10 - else if (a->ifa_addr->sa_family==AF_INET6 - && - ((!*bindiface && (bindll==prefer || bindll==force)) || - (*bindiface && bind_if6 && !strcmp(a->ifa_name, bindiface))) - && - ((bindll==force && is_linklocal((struct sockaddr_in6*)a->ifa_addr)) || - (bindll==prefer && ((pass==0 && is_linklocal((struct sockaddr_in6*)a->ifa_addr)) || (pass==1 && is_private6((struct sockaddr_in6*)a->ifa_addr)) || pass==2)) || - (bindll==no && ((pass==0 && is_private6((struct sockaddr_in6*)a->ifa_addr)) || (pass==1 && !is_linklocal((struct sockaddr_in6*)a->ifa_addr)))) || - (bindll==unwanted && ((pass==0 && is_private6((struct sockaddr_in6*)a->ifa_addr)) || (pass==1 && !is_linklocal((struct sockaddr_in6*)a->ifa_addr)) || pass==2))) - ) - { - 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; - goto ex; - } - } - a = a->ifa_next; - } - } -ex: - freeifaddrs(addrs); - return found; -} - -static bool read_system_maxfiles(rlim_t *maxfile) -{ -#ifdef __linux__ - FILE *F; - int n; - uintmax_t um; - if (!(F=fopen("/proc/sys/fs/file-max","r"))) - return false; - n=fscanf(F,"%ju",&um); - fclose(F); - if (n != 1) return false; - *maxfile = (rlim_t)um; - return true; -#elif defined(BSD) - int maxfiles,mib[2]={CTL_KERN, KERN_MAXFILES}; - size_t len = sizeof(maxfiles); - if (sysctl(mib,2,&maxfiles,&len,NULL,0)==-1) - return false; - *maxfile = (rlim_t)maxfiles; - return true; -#else - return false; -#endif -} -static bool write_system_maxfiles(rlim_t maxfile) -{ -#ifdef __linux__ - FILE *F; - int n; - if (!(F=fopen("/proc/sys/fs/file-max","w"))) - return false; - n=fprintf(F,"%ju",(uintmax_t)maxfile); - fclose(F); - return !!n; -#elif defined(BSD) - int maxfiles=(int)maxfile,mib[2]={CTL_KERN, KERN_MAXFILES}; - if (sysctl(mib,2,NULL,0,&maxfiles,sizeof(maxfiles))==-1) - return false; - return true; -#else - return false; -#endif -} - -static bool set_ulimit(void) -{ - rlim_t fdmax,fdmin_system,cur_lim=0; - int n; - - if (!params.maxfiles) - { - // 4 fds per tamper connection (2 pipe + 2 socket), 6 fds for tcp proxy connection (4 pipe + 2 socket), 2 fds (2 socket) for nosplice - // additional 1/2 for unpaired remote legs sending buffers - // 16 for listen_fd, epoll, hostlist, ... -#ifdef SPLICE_PRESENT - fdmax = (rlim_t)(params.nosplice ? 2 : (params.tamper && !params.tamper_lim ? 4 : 6)) * (rlim_t)params.maxconn; -#else - fdmax = 2 * params.maxconn; -#endif - fdmax += fdmax/2 + 16; - } - else - fdmax = params.maxfiles; - fdmin_system = fdmax + 4096; - DBGPRINT("set_ulimit : fdmax=%ju fdmin_system=%ju\n",(uintmax_t)fdmax,(uintmax_t)fdmin_system); - - if (!read_system_maxfiles(&cur_lim)) - return false; - DBGPRINT("set_ulimit : current system file-max=%ju\n",(uintmax_t)cur_lim); - if (cur_lim 0) - { - int sec=0; - if (!is_interface_online(params.binds[i].bindiface)) - { - DLOG_CONDUP("waiting for ifup of %s for up to %d second(s)...\n",params.binds[i].bindiface,params.binds[i].bind_wait_ifup); - do - { - sleep(1); - sec++; - } - while (!is_interface_online(params.binds[i].bindiface) && sec=params.binds[i].bind_wait_ifup) - { - DLOG_CONDUP("wait timed out\n"); - goto exiterr; - } - } - } - if (!(if_index = if_nametoindex(params.binds[i].bindiface)) && params.binds[i].bind_wait_ip<=0) - { - DLOG_CONDUP("bad iface %s\n",params.binds[i].bindiface); - goto exiterr; - } - } - list[i].bind_wait_ip_left = params.binds[i].bind_wait_ip; - if (*params.binds[i].bindaddr) - { - if (inet_pton(AF_INET, params.binds[i].bindaddr, &((struct sockaddr_in*)(&list[i].salisten))->sin_addr)) - { - list[i].salisten.ss_family = AF_INET; - } - else if (inet_pton(AF_INET6, params.binds[i].bindaddr, &((struct sockaddr_in6*)(&list[i].salisten))->sin6_addr)) - { - list[i].salisten.ss_family = AF_INET6; - list[i].ipv6_only = 1; - } - else - { - DLOG_CONDUP("bad bind addr : %s\n", params.binds[i].bindaddr); - goto exiterr; - } - } - else - { - if (*params.binds[i].bindiface || params.binds[i].bindll) - { - bool found; - enum bindll bindll_1; - int sec=0; - - if (params.binds[i].bind_wait_ip > 0) - { - DLOG_CONDUP("waiting for ip on %s for up to %d second(s)...\n", *params.binds[i].bindiface ? params.binds[i].bindiface : "", params.binds[i].bind_wait_ip); - if (params.binds[i].bind_wait_ip_ll>0) - { - if (params.binds[i].bindll==prefer) - DLOG_CONDUP("during the first %d second(s) accepting only link locals...\n", params.binds[i].bind_wait_ip_ll); - else if (params.binds[i].bindll==unwanted) - DLOG_CONDUP("during the first %d second(s) accepting only ipv6 globals...\n", params.binds[i].bind_wait_ip_ll); - } - } - - for(;;) - { - // allow, no, prefer, force - bindll_1 = (params.binds[i].bindll==prefer && sec=params.binds[i].bind_wait_ip) - break; - - sleep(1); - sec++; - } - - if (!found) - { - DLOG_CONDUP("suitable ip address not found\n"); - goto exiterr; - } - list[i].bind_wait_ip_left = params.binds[i].bind_wait_ip - sec; - list[i].ipv6_only=1; - } - else - { - list[i].salisten.ss_family = AF_INET6; - // leave sin6_addr zero - } - } - if (list[i].salisten.ss_family == AF_INET6) - { - list[i].salisten_len = sizeof(struct sockaddr_in6); - ((struct sockaddr_in6*)(&list[i].salisten))->sin6_port = htons(params.port); - if (is_linklocal((struct sockaddr_in6*)(&list[i].salisten))) - ((struct sockaddr_in6*)(&list[i].salisten))->sin6_scope_id = if_index; - } - else - { - list[i].salisten_len = sizeof(struct sockaddr_in); - ((struct sockaddr_in*)(&list[i].salisten))->sin_port = htons(params.port); - } - } - - if (params.bind_wait_only) - { - DLOG_CONDUP("bind wait condition satisfied\n"); - exit_v = 0; - goto exiterr; - } - - if (params.proxy_type==CONN_TYPE_TRANSPARENT && !redir_init()) - { - DLOG_ERR("could not initialize redirector !!!\n"); - goto exiterr; - } - - for(i=0;i<=params.binds_last;i++) - { - if (params.debug) - { - ntop46_port((struct sockaddr *)&list[i].salisten, ip_port, sizeof(ip_port)); - VPRINT("Binding %d to %s\n",i,ip_port); - } - - if ((listen_fd[i] = socket(list[i].salisten.ss_family, SOCK_STREAM, 0)) == -1) { - DLOG_PERROR("socket"); - goto exiterr; - } - -#ifndef __OpenBSD__ -// in OpenBSD always IPV6_ONLY for wildcard sockets - if ((list[i].salisten.ss_family == AF_INET6) && setsockopt(listen_fd[i], IPPROTO_IPV6, IPV6_V6ONLY, &list[i].ipv6_only, sizeof(int)) == -1) - { - DLOG_PERROR("setsockopt (IPV6_ONLY)"); - goto exiterr; - } -#endif - - if (setsockopt(listen_fd[i], SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)) == -1) - { - DLOG_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) - { - #ifdef __linux__ - if (setsockopt(listen_fd[i], SOL_IP, IP_TRANSPARENT, &yes, sizeof(yes)) == -1) - { - DLOG_PERROR("setsockopt (IP_TRANSPARENT)"); - goto exiterr; - } - #elif defined(BSD) && defined(SO_BINDANY) - if (setsockopt(listen_fd[i], SOL_SOCKET, SO_BINDANY, &yes, sizeof(yes)) == -1) - { - DLOG_PERROR("setsockopt (SO_BINDANY)"); - goto exiterr; - } - #endif - } - - if (!set_socket_buffers(listen_fd[i], 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; - socklen_t sz=sizeof(int); - if (!getsockopt(listen_fd[i],SOL_SOCKET,SO_RCVBUF,&v,&sz)) - { - v/=2; - setsockopt(listen_fd[i],SOL_SOCKET,SO_RCVBUF,&v,sizeof(int)); - } - } - bool bBindBug=false; - for(;;) - { - if (bind(listen_fd[i], (struct sockaddr *)&list[i].salisten, list[i].salisten_len) == -1) - { - // in linux strange behaviour was observed - // just after ifup and address assignment there's short window when bind() can't bind to addresses got from getifaddrs() - // it does not happen to transparent sockets because they can bind to any non-existend ip - // also only ipv6 seem to be buggy this way - if (errno==EADDRNOTAVAIL && params.proxy_type!=CONN_TYPE_TRANSPARENT && list[i].bind_wait_ip_left) - { - if (!bBindBug) - { - ntop46_port((struct sockaddr *)&list[i].salisten, ip_port, sizeof(ip_port)); - DLOG_CONDUP("address %s is not available. will retry for %d sec\n",ip_port,list[i].bind_wait_ip_left); - bBindBug=true; - } - sleep(1); - list[i].bind_wait_ip_left--; - continue; - } - DLOG_PERROR("bind"); - goto exiterr; - } - break; - } - if (listen(listen_fd[i], BACKLOG) == -1) - { - DLOG_PERROR("listen"); - goto exiterr; - } - } - - if (params.cache_hostname) VPRINT("ipcache lifetime %us\n", params.ipcache_lifetime); - DLOG_CONDUP(params.proxy_type==CONN_TYPE_SOCKS ? "socks mode\n" : "transparent proxy mode\n"); - if (!params.tamper) DLOG_CONDUP("TCP proxy mode (no tampering)\n"); - - if (*params.pidfile && !(Fpid=fopen(params.pidfile,"w"))) - { - DLOG_PERROR("create pidfile"); - goto exiterr; - } - - set_ulimit(); - if (params.droproot && !droproot(params.uid,params.user,params.gid,params.gid_count)) - goto exiterr; -#ifdef __linux__ - if (!dropcaps()) - goto exiterr; -#endif - print_id(); - if (params.droproot && !test_list_files()) - goto exiterr; - - if (params.daemon) daemonize(); - - sec_harden(); - - if (Fpid) - { - if (fprintf(Fpid, "%d", getpid())<=0) - { - DLOG_PERROR("write pidfile"); - goto exiterr; - } - fclose(Fpid); - Fpid=NULL; - } - - //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) { - DLOG_ERR("Could not block SIGPIPE signal\n"); - goto exiterr; - } - - signal(SIGHUP, onhup); - signal(SIGUSR2, onusr2); - - retval = event_loop(listen_fd,params.binds_last+1); - exit_v = retval < 0 ? EXIT_FAILURE : EXIT_SUCCESS; - DLOG_CONDUP("Exiting\n"); - -exiterr: - if (Fpid) fclose(Fpid); - redir_close(); - for(i=0;i<=params.binds_last;i++) if (listen_fd[i]!=-1) close(listen_fd[i]); - cleanup_params(¶ms); - return exit_v; -} diff --git a/tpws/tpws.h b/tpws/tpws.h deleted file mode 100644 index baa7d60e..00000000 --- a/tpws/tpws.h +++ /dev/null @@ -1,9 +0,0 @@ -#pragma once - -#ifdef __linux__ - #define SPLICE_PRESENT -#endif - -#include - -void ReloadCheck(); diff --git a/tpws/tpws_conn.c b/tpws/tpws_conn.c deleted file mode 100644 index 08a1c504..00000000 --- a/tpws/tpws_conn.c +++ /dev/null @@ -1,1786 +0,0 @@ -#define _GNU_SOURCE -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#ifdef USE_SYSTEMD -#include -#endif - -#include "tpws.h" -#include "tpws_conn.h" -#include "redirect.h" -#include "tamper.h" -#include "socks.h" -#include "helpers.h" -#include "hostlist.h" -#include "linux_compat.h" - -static void notify_ready(void) -{ -#ifdef USE_SYSTEMD - int r = sd_notify(0, "READY=1"); - if (r < 0) - DLOG_ERR("sd_notify: %s\n", strerror(-r)); -#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(void) -{ - VPRINT("Legs : local:%d remote:%d\n", 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 cork(int fd, int enable) -{ -#ifdef __linux__ - int e = errno; - if (setsockopt(fd, SOL_TCP, TCP_CORK, &enable, sizeof(enable))<0) - { - DLOG_PERROR("setsockopt (TCP_CORK)"); - errno = e; - return false; - } - errno = e; -#endif - return true; -} - -ssize_t send_with_ttl(int fd, const void *buf, size_t len, int flags, int ttl) -{ - ssize_t wr; - - if (!params.skip_nodelay) - { - int ttl_apply = ttl ? ttl : params.ttl_default; - DBGPRINT("send_with_ttl %d fd=%d\n",ttl,fd); - if (!set_ttl_hl(fd, ttl_apply)) - //DLOG_ERR("could not set ttl %d to fd=%d\n",ttl,fd); - DLOG_ERR("could not set ttl %d to fd=%d\n",ttl_apply,fd); - cork(fd,true); - } - wr = send(fd, buf, len, flags); - if (!params.skip_nodelay) - cork(fd,false); - return wr; -} - - -static bool send_buffer_create(send_buffer_t *sb, const void *data, size_t len, size_t extra_bytes, int flags, int ttl) -{ - if (sb->data) - { - DLOG_ERR("FATAL : send_buffer_create but buffer is not empty\n"); - exit(1); - } - sb->data = malloc(len + extra_bytes); - if (!sb->data) - { - DBGPRINT("send_buffer_create failed\n"); - return false; - } - if (data) memcpy(sb->data,data,len); - sb->len = len; - sb->pos = 0; - sb->ttl = ttl; - sb->flags = flags; - return true; -} -static bool send_buffer_realloc(send_buffer_t *sb, size_t extra_bytes) -{ - if (sb->data) - { - uint8_t *p = (uint8_t*)realloc(sb->data, sb->len + extra_bytes); - if (p) - { - sb->data = p; - DBGPRINT("reallocated send_buffer from %zd to %zd\n", sb->len, sb->len + extra_bytes); - return true; - } - else - { - DBGPRINT("failed to realloc send_buffer from %zd to %zd\n", sb->len, sb->len + extra_bytes); - } - } - return false; -} - -static void send_buffer_free(send_buffer_t *sb) -{ - 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, sb->flags, sb->ttl); - DBGPRINT("send_buffer_send len=%zu pos=%zu wr=%zd err=%d\n",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,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); - 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 bool conn_shutdown(tproxy_conn_t *conn) -{ - conn->bShutdown = true; - if (shutdown(conn->fd,SHUT_WR)<0) - { - DLOG_PERROR("shutdown"); - return false; - } - return true; -} - -static ssize_t send_or_buffer(send_buffer_t *sb, int fd, const void *buf, size_t len, int flags, int ttl) -{ - ssize_t wr=0; - if (len) - { - wr = send_with_ttl(fd, buf, len, flags, ttl); - if (wr<0 && errno==EAGAIN) wr=0; - if (wr>=0 && wr=2) - { - int v; - socklen_t sz; - sz=sizeof(int); - if (!getsockopt(fd,SOL_SOCKET,SO_RCVBUF,&v,&sz)) - DBGPRINT("fd=%d SO_RCVBUF=%d\n",fd,v); - sz=sizeof(int); - if (!getsockopt(fd,SOL_SOCKET,SO_SNDBUF,&v,&sz)) - DBGPRINT("fd=%d SO_SNDBUF=%d\n",fd,v); - } -} - -bool set_socket_buffers(int fd, int rcvbuf, int sndbuf) -{ - DBGPRINT("set_socket_buffers fd=%d rcvbuf=%d sndbuf=%d\n",fd,rcvbuf,sndbuf); - if (rcvbuf && setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &rcvbuf, sizeof(int)) <0) - { - DLOG_PERROR("setsockopt (SO_RCVBUF)"); - return false; - } - if (sndbuf && setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &sndbuf, sizeof(int)) <0) - { - DLOG_PERROR("setsockopt (SO_SNDBUF)"); - return false; - } - dbgprint_socket_buffers(fd); - return true; -} - - -static bool proxy_remote_conn_ack(tproxy_conn_t *conn, int sock_err) -{ - // if proxy mode acknowledge connection request - // conn = remote. conn->partner = local - if (!conn->remote || !conn_partner_alive(conn)) return false; - bool bres = true; - switch(conn->partner->conn_type) - { - case CONN_TYPE_SOCKS: - if (conn->partner->socks_state==S_WAIT_CONNECTION) - { - conn->partner->socks_state=S_TCP; - bres = socks_send_rep_errno(conn->partner->socks_ver,conn->partner->fd,sock_err); - DBGPRINT("socks connection acknowledgement. bres=%d remote_errn=%d remote_fd=%d local_fd=%d\n",bres,sock_err,conn->fd,conn->partner->fd); - } - break; - } - return bres; -} - -#if defined(__linux__) || defined(__APPLE__) - -static void set_user_timeout(int fd, int timeout) -{ -#ifdef __linux__ - if (timeout>0) - { - int msec = 1000*timeout; - if (setsockopt(fd, IPPROTO_TCP, TCP_USER_TIMEOUT, &msec, sizeof(int)) <0) - DLOG_PERROR("setsockopt (TCP_USER_TIMEOUT)"); - } -#elif defined(__APPLE__) - if (timeout>0 && setsockopt(fd, IPPROTO_TCP, TCP_RXT_CONNDROPTIME, &timeout, sizeof(int)) <0) - DLOG_PERROR("setsockopt (TCP_RXT_CONNDROPTIME)"); -#endif -} - -#else - -#define set_user_timeout(fd,timeout) - -#endif - - -//Createas a socket and initiates the connection to the host specified by -//remote_addr. -//Returns -1 if something fails, >0 on success (socket fd). -static int connect_remote(const struct sockaddr *remote_addr, int mss) -{ - int remote_fd = 0, yes = 1, no = 0; - - - if((remote_fd = socket(remote_addr->sa_family, SOCK_STREAM, 0)) < 0) - { - DLOG_PERROR("socket (connect_remote)"); - return -1; - } - // Use NONBLOCK to avoid slow connects affecting the performance of other connections - // separate fcntl call to comply with macos - if (fcntl(remote_fd, F_SETFL, O_NONBLOCK)<0) - { - DLOG_PERROR("socket set O_NONBLOCK (connect_remote)"); - close(remote_fd); - return -1; - } - if (setsockopt(remote_fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)) < 0) - { - DLOG_PERROR("setsockopt (SO_REUSEADDR, connect_remote)"); - close(remote_fd); - return -1; - } - if (!set_socket_buffers(remote_fd, params.remote_rcvbuf, params.remote_sndbuf)) - { - close(remote_fd); - return -1; - } - if (!set_keepalive(remote_fd)) - { - DLOG_PERROR("set_keepalive"); - close(remote_fd); - return -1; - } - if (setsockopt(remote_fd, IPPROTO_TCP, TCP_NODELAY, params.skip_nodelay ? &no : &yes, sizeof(int)) <0) - { - DLOG_PERROR("setsockopt (TCP_NODELAY, connect_remote)"); - close(remote_fd); - return -1; - } -#ifdef __linux__ - if (mss) - { - VPRINT("Setting MSS %d\n", mss); - if (setsockopt(remote_fd, IPPROTO_TCP, TCP_MAXSEG, &mss, sizeof(int)) <0) - { - DLOG_PERROR("setsockopt (TCP_MAXSEG, connect_remote)"); - close(remote_fd); - return -1; - } - } -#endif - - // if no bind address specified - address family will be 0 in params_connect_bindX - if(remote_addr->sa_family == params.connect_bind4.sin_family) - { - if (bind(remote_fd, (struct sockaddr *)¶ms.connect_bind4, sizeof(struct sockaddr_in)) == -1) - { - DLOG_PERROR("bind on connect"); - close(remote_fd); - return -1; - } - } - else if(remote_addr->sa_family == params.connect_bind6.sin6_family) - { - if (*params.connect_bind6_ifname && !params.connect_bind6.sin6_scope_id) - { - params.connect_bind6.sin6_scope_id=if_nametoindex(params.connect_bind6_ifname); - if (!params.connect_bind6.sin6_scope_id) - { - DLOG_ERR("interface name not found : %s\n", params.connect_bind6_ifname); - close(remote_fd); - return -1; - } - } - - if (bind(remote_fd, (struct sockaddr *)¶ms.connect_bind6, sizeof(struct sockaddr_in6)) == -1) - { - DLOG_PERROR("bind on connect"); - close(remote_fd); - return -1; - } - } - - set_user_timeout(remote_fd, params.tcp_user_timeout_remote); - - if (connect(remote_fd, remote_addr, remote_addr->sa_family == AF_INET ? sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6)) < 0) - { - if(errno != EINPROGRESS) - { - DLOG_PERROR("connect (connect_remote)"); - close(remote_fd); - return -1; - } - } - DBGPRINT("Connecting remote fd=%d\n",remote_fd); - - return remote_fd; -} - -static bool connect_remote_conn(tproxy_conn_t *conn) -{ - int mss=0; - - if (conn->track.hostname) - if (!ipcache_put_hostname(conn->dest.sa_family==AF_INET ? &((struct sockaddr_in*)&conn->dest)->sin_addr : NULL, conn->dest.sa_family==AF_INET6 ? &((struct sockaddr_in6*)&conn->dest)->sin6_addr : NULL , conn->track.hostname, conn->track.hostname_is_ip)) - DLOG_ERR("ipcache_put_hostname: out of memory"); - apply_desync_profile(&conn->track, (struct sockaddr *)&conn->dest); - - if (conn->track.dp && conn->track.dp->mss) - { - mss = conn->track.dp->mss; - if (conn->track.dp->hostlist_auto) - { - if (conn->track.hostname) - { - bool bHostExcluded; - conn->track.b_host_matches = HostlistCheck(conn->track.dp, conn->track.hostname, conn->track.hostname_is_ip, &bHostExcluded, false); - conn->track.b_host_checked = true; - if (!conn->track.b_host_matches) - { - conn->track.b_ah_check = !bHostExcluded; - mss = 0; - } - } - } - } - - return (conn->partner->fd = connect_remote((struct sockaddr *)&conn->dest, mss))>=0; -} - -//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->track.hostname); - if (conn->socks_ri) conn->socks_ri->ptr = NULL; // detach conn - 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*) calloc(1, sizeof(tproxy_conn_t))) == NULL) - { - DLOG_ERR("Could not allocate memory for connection\n"); - return NULL; - } - - conn->state = CONN_UNAVAILABLE; - conn->fd = fd; - conn->remote = remote; - -#ifdef SPLICE_PRESENT - // if dont tamper - both legs are spliced, create 2 pipes - // otherwise create pipe only in local leg - if (!params.nosplice && ( !remote || !params.tamper || params.tamper_lim ) && pipe2(conn->splice_pipe, O_NONBLOCK) != 0) - { - DLOG_ERR("Could not create the splice pipe\n"); - free_conn(conn); - return NULL; - } -#endif - - 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\n",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) - { - DLOG_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\n",conn->fd); - if(epoll_ctl(conn->efd, EPOLL_CTL_DEL, conn->fd, &ev)==-1) - { - DLOG_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 - DBGPRINT("SET FLOW fd=%d to in=%d out=%d state_rdhup=%d\n",conn->fd,conn->bFlowIn,conn->bFlowOut,conn->state==CONN_RDHUP); - uint32_t evtmask = (conn->state==CONN_RDHUP ? 0 : EPOLLRDHUP)|(conn->bFlowIn?EPOLLIN:0)|(conn->bFlowOut?EPOLLOUT:0); - if (!epoll_set(conn, evtmask)) - return false; - 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, const struct sockaddr *accept_sa, uint16_t listen_port, conn_type_t proxy_type) -{ - struct sockaddr_storage orig_dst; - tproxy_conn_t *conn; - - if (proxy_type==CONN_TYPE_TRANSPARENT) - { - if(!get_dest_addr(local_fd, accept_sa, &orig_dst)) - { - DLOG_ERR("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\n"); - close(local_fd); - return NULL; - } - } - - // socket buffers inherited from listen_fd - dbgprint_socket_buffers(local_fd); - - if(!set_keepalive(local_fd)) - { - DLOG_PERROR("set_keepalive"); - close(local_fd); - return 0; - } - - if(!(conn = new_conn(local_fd, false))) - { - 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; - - socklen_t salen=sizeof(conn->client); - getpeername(conn->fd,(struct sockaddr *)&conn->client,&salen); - - if (proxy_type==CONN_TYPE_TRANSPARENT) - { - sa46copy(&conn->dest, (struct sockaddr *)&orig_dst); - - if(!(conn->partner = new_conn(0, true))) - { - free_conn(conn); - return NULL; - } - - conn->partner->partner = conn; - conn->partner->efd = efd; - conn->partner->client = conn->client; - conn->partner->dest = conn->dest; - - if (!connect_remote_conn(conn)) - { - DLOG_ERR("Failed to connect\n"); - free_conn(conn->partner); - free_conn(conn); - return NULL; - } - - //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 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; - } - - // 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) - { - DLOG_PERROR("getsockopt (SO_ERROR)"); - return false; - } - if (!errn) - { - if (params.debug>=1) - { - sockaddr_in46 sa; - socklen_t salen=sizeof(sa); - char ip_port[48]; - - if (getsockname(conn->fd,(struct sockaddr *)&sa,&salen)) - *ip_port=0; - else - ntop46_port((struct sockaddr*)&sa,ip_port,sizeof(ip_port)); - VPRINT("Socket fd=%d (remote) connected from : %s\n", conn->fd, ip_port); - } - if (!epoll_set_flow(conn, true, false) || (conn_partner_alive(conn) && !epoll_set_flow(conn->partner, true, false))) - { - return false; - } - conn->state = CONN_AVAILABLE; - } - proxy_remote_conn_ack(conn,get_so_error(conn->fd)); - return !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\n", - 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)) - return false; - if (conn_partner_alive(conn)) - { - if (!epoll_set_flow(conn->partner, !bHasUnsent && (conn->partner->state != CONN_RDHUP), bHasUnsentPartner)) - return false; - } - return true; -} - -static bool handle_unsent(tproxy_conn_t *conn) -{ - ssize_t wr; - - DBGPRINT("+handle_unsent, fd=%d has_unsent=%d has_unsent_partner=%d\n",conn->fd,conn_has_unsent(conn),conn_partner_alive(conn) ? conn_has_unsent(conn->partner) : false); - -#ifdef SPLICE_PRESENT - if (!params.nosplice && 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\n",conn->wr_unsent,wr,errno); - if (wr<0) - { - if (errno==EAGAIN) wr=0; - else return false; - } - conn->twr += wr; - conn->wr_unsent -= wr; - } -#endif - if (!conn->wr_unsent && conn_buffers_present(conn)) - { - wr=conn_buffers_send(conn); - DBGPRINT("conn_buffers_send wr=%zd\n",wr); - if (wr<0) return false; - } - if (!conn_has_unsent(conn) && conn_partner_alive(conn) && conn->partner->state==CONN_RDHUP) - { - if (!conn->bShutdown) - { - DBGPRINT("fd=%d no more has unsent. partner in RDHUP state. executing delayed shutdown.\n", conn->fd); - if (!conn_shutdown(conn)) - { - DBGPRINT("emergency connection close due to failed shutdown\n"); - return false; - } - } - if (conn->state==CONN_RDHUP && !conn_has_unsent(conn->partner)) - { - DBGPRINT("both partners are in RDHUP state and have no unsent. closing.\n"); - return false; - } - } - - return epoll_set_flow_pair(conn); -} - - -static bool proxy_mode_connect_remote(tproxy_conn_t *conn, struct tailhead *conn_list) -{ - int remote_fd; - - if (params.debug>=1) - { - char ip_port[48]; - ntop46_port((struct sockaddr *)&conn->dest,ip_port,sizeof(ip_port)); - VPRINT("socks target for fd=%d is : %s\n", conn->fd, ip_port); - } - if (check_local_ip((struct sockaddr *)&conn->dest)) - { - VPRINT("Dropping connection to local address for security reasons\n"); - socks_send_rep(conn->socks_ver, conn->fd, S5_REP_NOT_ALLOWED_BY_RULESET); - return false; - } - - if (!(conn->partner = new_conn(remote_fd, true))) - { - close(remote_fd); - DLOG_ERR("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; - conn->partner->client = conn->client; - conn->partner->dest = conn->dest; - - if (!connect_remote_conn(conn)) - { - free_conn(conn->partner); conn->partner = NULL; - DLOG_ERR("socks failed to connect (1) errno=%d\n", errno); - socks_send_rep_errno(conn->socks_ver, conn->fd, errno); - return false; - } - - if (!epoll_set(conn->partner, EPOLLOUT)) - { - DLOG_ERR("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("S_WAIT_CONNECTION\n"); - 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 - - // receive proxy control message - rd=recv(conn->fd, buf, sizeof(buf), MSG_DONTWAIT); - DBGPRINT("handle_proxy_mode rd=%zd\n",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\n"); - if (buf[0] != 5 && buf[0] != 4) return false; // unknown socks version - conn->socks_ver = buf[0]; - DBGPRINT("socks version %u\n", 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\n"); - 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\n"); - ack.method=S5_AUTH_UNACCEPTABLE; - wr=send(conn->fd,&ack,sizeof(ack),MSG_DONTWAIT); - return false; - } - DBGPRINT("socks5 recv valid handshake\n"); - 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\n",wr,errno); - return false; - } - DBGPRINT("socks5 send handshake ack OK\n"); - 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\n"); - return false; - } - if (m->cmd!=S4_CMD_CONNECT) - { - // BIND is not supported - DBGPRINT("socks4 unsupported command %02X\n", m->cmd); - socks4_send_rep(conn->fd, S4_REP_FAILED); - return false; - } - if (!S4_REQ_CONNECT_VALID(m, rd)) - { - DBGPRINT("socks4 connect request invalid\n"); - socks4_send_rep(conn->fd, S4_REP_FAILED); - return false; - } - if (!m->port) - { - DBGPRINT("socks4 zero port\n"); - socks4_send_rep(conn->fd, S4_REP_FAILED); - return false; - } - if (m->ip==htonl(1)) // special ip 0.0.0.1 - { - VPRINT("socks4a protocol not supported\n"); - socks4_send_rep(conn->fd, S4_REP_FAILED); - return false; - } - ((struct sockaddr_in*)&conn->dest)->sin_family = AF_INET; - ((struct sockaddr_in*)&conn->dest)->sin_port = m->port; - ((struct sockaddr_in*)&conn->dest)->sin_addr.s_addr = m->ip; - return proxy_mode_connect_remote(conn, conn_list); - } - break; - case S_WAIT_REQUEST: - DBGPRINT("S_WAIT_REQUEST\n"); - { - s5_req *m = (s5_req*)buf; - - if (!S5_REQ_HEADER_VALID(m,rd)) - { - DBGPRINT("socks5 request invalid\n"); - return false; - } - if (m->cmd!=S5_CMD_CONNECT) - { - // BIND and UDP are not supported - DBGPRINT("socks5 unsupported command %02X\n", 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\n"); - return false; - } - DBGPRINT("socks5 recv valid connect request\n"); - switch(m->atyp) - { - case S5_ATYP_IP4: - ((struct sockaddr_in*)&conn->dest)->sin_family = AF_INET; - ((struct sockaddr_in*)&conn->dest)->sin_port = m->d4.port; - ((struct sockaddr_in*)&conn->dest)->sin_addr = m->d4.addr; - break; - case S5_ATYP_IP6: - ((struct sockaddr_in6*)&conn->dest)->sin6_family = AF_INET6; - ((struct sockaddr_in6*)&conn->dest)->sin6_port = m->d6.port; - ((struct sockaddr_in6*)&conn->dest)->sin6_addr = m->d6.addr; - ((struct sockaddr_in6*)&conn->dest)->sin6_flowinfo = 0; - ((struct sockaddr_in6*)&conn->dest)->sin6_scope_id = 0; - break; - case S5_ATYP_DOM: - { - uint16_t port; - - if (params.no_resolve) - { - VPRINT("socks5 hostname resolving disabled\n"); - socks5_send_rep(conn->fd,S5_REP_NOT_ALLOWED_BY_RULESET); - return false; - } - port=S5_PORT_FROM_DD(m,rd); - if (!port) - { - VPRINT("socks5 no port is given\n"); - socks5_send_rep(conn->fd,S5_REP_HOST_UNREACHABLE); - return false; - } - m->dd.domport[m->dd.len] = 0; - DBGPRINT("socks5 queue resolve hostname '%s' port '%u'\n",m->dd.domport,port); - conn->socks_ri = resolver_queue(m->dd.domport,port,conn); - if (!conn->socks_ri) - { - VPRINT("socks5 could not queue resolve item\n"); - socks5_send_rep(conn->fd,S5_REP_GENERAL_FAILURE); - return false; - } - conn->socks_state=S_WAIT_RESOLVE; - DBGPRINT("S_WAIT_RESOLVE\n"); - return true; - } - break; - default: - return false; // should not be here. S5_REQ_CONNECT_VALID checks for valid atyp - - } - return proxy_mode_connect_remote(conn,conn_list); - } - break; - case S_WAIT_RESOLVE: - DBGPRINT("socks received message while in S_WAIT_RESOLVE. hanging up\n"); - break; - case S_WAIT_CONNECTION: - DBGPRINT("socks received message while in S_WAIT_CONNECTION. hanging up\n"); - break; - default: - DBGPRINT("socks received message while in an unexpected connection state\n"); - break; - } - break; - } - return false; -} - -static bool resolve_complete(struct resolve_item *ri, struct tailhead *conn_list) -{ - tproxy_conn_t *conn = (tproxy_conn_t *)ri->ptr; - - if (conn && (conn->state != CONN_CLOSED)) - { - if (conn->socks_state==S_WAIT_RESOLVE) - { - DBGPRINT("resolve_complete %s. getaddrinfo result %d\n", ri->dom, ri->ga_res); - if (ri->ga_res) - { - socks5_send_rep(conn->fd,S5_REP_HOST_UNREACHABLE); - return false;; - } - else - { - if (!conn->track.hostname) - { - DBGPRINT("resolve_complete put hostname : %s\n", ri->dom); - if (!(conn->track.hostname = strdup(ri->dom))) - DLOG_ERR("dup hostname: out of memory\n"); - } - sa46copy(&conn->dest, (struct sockaddr *)&ri->ss); - return proxy_mode_connect_remote(conn,conn_list); - } - } - else - DLOG_ERR("resolve_complete: conn in wrong socks_state !!! (%s)\n", ri->dom); - } - else - DBGPRINT("resolve_complete: orphaned resolve for %s\n", ri->dom); - - return true; -} - - -static bool in_tamper_out_range(tproxy_conn_t *conn) -{ - if (!conn->track.dp) return true; - bool in_range = \ - ((conn->track.dp->tamper_start_n ? (conn->tnrd+1) : conn->trd) >= conn->track.dp->tamper_start && - (!conn->track.dp->tamper_cutoff || (conn->track.dp->tamper_cutoff_n ? (conn->tnrd+1) : conn->trd) < conn->track.dp->tamper_cutoff)); - DBGPRINT("tamper_out range check. stream pos %" PRIu64 "(n%" PRIu64 "). tamper range %s%u-%s%u (%s)\n", - conn->trd, conn->tnrd+1, - conn->track.dp ? conn->track.dp->tamper_start_n ? "n" : "" : "?" , conn->track.dp ? conn->track.dp->tamper_start : 0, - conn->track.dp ? conn->track.dp->tamper_cutoff_n ? "n" : "" : "?" , conn->track.dp ? conn->track.dp->tamper_cutoff : 0, - in_range ? "IN RANGE" : "OUT OF RANGE"); - return in_range; - -} - -static void tamper(tproxy_conn_t *conn, uint8_t *segment, size_t segment_buffer_size, size_t *segment_size, size_t *multisplit_pos, int *multisplit_count, uint8_t *split_flags) -{ - if (multisplit_count) *multisplit_count=0; - if (params.tamper) - { - if (conn->remote) - { - if (conn_partner_alive(conn) && !conn->partner->track.bTamperInCutoff) - tamper_in(&conn->partner->track,(struct sockaddr*)&conn->partner->client,segment,segment_buffer_size,segment_size); - } - else - { - if (in_tamper_out_range(conn)) - tamper_out(&conn->track,(struct sockaddr*)&conn->dest,segment,segment_buffer_size,segment_size,multisplit_pos,multisplit_count,split_flags); - } - } -} - -// buffer must have at least one extra byte for OOB -static ssize_t send_oob(int fd, uint8_t *buf, size_t len, int ttl, bool oob, uint8_t oob_byte) -{ - ssize_t wr; - if (oob) - { - uint8_t oob_save; - oob_save = buf[len]; - buf[len] = oob_byte; - wr = send_with_ttl(fd, buf, len+1, MSG_OOB, ttl); - buf[len] = oob_save; - if (wr<0 && errno==EAGAIN) wr=0; - } - else - wr = send_with_ttl(fd, buf, len, 0, ttl); - return wr; -} - - -static unsigned int segfail_count=0; -static time_t segfail_report_time=0; -static void report_segfail(void) -{ - time_t now = time(NULL); - segfail_count++; - if (now==segfail_report_time) - VPRINT("WARNING ! segmentation failed. total fails : %u\n", segfail_count); - else - { - DLOG_ERR("WARNING ! segmentation failed. total fails : %u\n", segfail_count); - segfail_report_time = now; - } -} - -#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\n"); - - 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 - uint8_t waste[65070]; - uint64_t trd=0; - - while((rd=recv(conn->fd, waste, sizeof(waste), MSG_DONTWAIT))>0 && trdtrd+=rd; - } - DBGPRINT("wasted recv=%zd all_rd=%" PRIu64 " err=%d\n",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\n",numbytes); - if (numbytes>0) - { - DBGPRINT("%s leg fd=%d stream pos : %" PRIu64 "(n%" PRIu64 ")/%" PRIu64 "\n", conn->remote ? "remote" : "local", conn->fd, conn->trd,conn->tnrd+1,conn->twr); -#ifdef SPLICE_PRESENT - if (!params.nosplice && (!params.tamper || (conn->remote && conn->partner->track.bTamperInCutoff) || (!conn->remote && !in_tamper_out_range(conn)))) - { - // 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\n",conn->fd,conn->remote,SPLICE_LEN,rd,errno); - if (rd<0 && errno==EAGAIN) rd=0; - if (rd>0) - { - conn->tnrd++; - 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\n",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 -#endif - { - // incoming data from local leg - uint8_t buf[RD_BLOCK_SIZE + 6]; - - rd = recv(conn->fd, buf, RD_BLOCK_SIZE, MSG_DONTWAIT); - DBGPRINT("recv fd=%d rd=%zd err=%d\n",conn->fd, rd,errno); - if (rd<0 && errno==EAGAIN) rd=0; - if (rd>0) - { - size_t multisplit_pos[MAX_SPLITS]; - int multisplit_count; - - uint8_t split_flags; - - bs = rd; - - // tamper needs to know stream position of the block start - tamper(conn, buf, sizeof(buf), &bs, multisplit_pos, &multisplit_count, &split_flags); - // increase after tamper - conn->tnrd++; - conn->trd+=rd; - - if (multisplit_count) - { - ssize_t from,to,len; - int i; - bool bApplyDisorder, bApplyOOB; - for (i=0,from=0;i<=multisplit_count;i++) - { - to = i==multisplit_count ? bs : multisplit_pos[i]; - - bApplyDisorder = !(i & 1) && ipartner->fd, params.fix_seg, &wasted); - if (wasted) - VPRINT("WARNING ! wasted %u ms to fix segmenation\n", wasted); - if (!bWaitOK) - report_segfail(); - } - else - { - if (socket_has_notsent(conn->partner->fd)) - report_segfail(); - } - } -#endif - VPRINT("Sending multisplit part %d %zd-%zd (len %zd)%s%s : ", i+1, from, to, len, bApplyDisorder ? " with disorder" : "", bApplyOOB ? " with OOB" : ""); - packet_debug(buf+from,len); - wr = send_oob(conn->partner->fd, buf+from, len, bApplyDisorder, bApplyOOB, conn->track.dp ? conn->track.dp->oob_byte : 0); - if (wr<0) break; - conn->partner->twr += wr; - if (wrpartner->wr_buf, conn->partner->fd, buf+from, bs-from, 0, 0); - if (wr>0) conn->partner->twr += wr; - break; - } - from = to; - } - } - else - { - wr = send_or_buffer(conn->partner->wr_buf, conn->partner->fd, buf, bs, 0, 0); - DBGPRINT("send_or_buffer(3) fd=%d wr=%zd err=%d\n",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\n",rd,wr); - if (oom) DBGPRINT("handle_epoll: OUT_OF_MEMORY\n"); - - // 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); - - epoll_del(conn); - VPRINT("Socket fd=%d (partner_fd=%d, remote=%d) closed, connection removed. total_read=%" PRIu64 " total_write=%" PRIu64 " event_count=%u\n", - 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\n",buffer_number,numbytes); - if (numbytes>0) - { - if (send_buffer_create(conn->partner->wr_buf+buffer_number, NULL, numbytes, 6, 0, 0)) - { - 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; - - tamper(conn, conn->partner->wr_buf[buffer_number].data, numbytes+6, &conn->partner->wr_buf[buffer_number].len, NULL, NULL, NULL); - - 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\n"); - - 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\n",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); - } -} - - -static bool handle_resolve_pipe(tproxy_conn_t **conn, struct tailhead *conn_list, int fd) -{ - ssize_t rd; - struct resolve_item *ri; - bool b; - - rd = read(fd,&ri,sizeof(void*)); - if (rd<0) - { - DLOG_PERROR("resolve_pipe read"); - return false; - } - else if (rd!=sizeof(void*)) - { - // partial pointer read is FATAL. in any case it will cause pointer corruption and coredump - DLOG_ERR("resolve_pipe not full read %zd\n",rd); - exit(1000); - } - b = resolve_complete(ri, conn_list); - *conn = (tproxy_conn_t *)ri->ptr; - if (*conn) (*conn)->socks_ri = NULL; - free(ri); - return b; -} - -int event_loop(const int *listen_fd, size_t listen_fd_ct) -{ - 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=0, i; - struct epoll_event ev, events[MAX_EPOLL_EVENTS]; - struct tailhead conn_list, close_list; - time_t tm,last_timeout_check=0; - tproxy_conn_t *listen_conn = NULL; - size_t sct; - struct sockaddr_storage accept_sa; - socklen_t accept_salen; - int resolve_pipe[2]; - - if (!listen_fd_ct) return -1; - - resolve_pipe[0]=resolve_pipe[1]=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) { - DLOG_PERROR("epoll_create"); - return -1; - } - - if (!(listen_conn=calloc(listen_fd_ct,sizeof(*listen_conn)))) - { - DLOG_PERROR("calloc listen_conn"); - return -1; - } - - //Start monitoring listen sockets - memset(&ev, 0, sizeof(ev)); - ev.events = EPOLLIN; - for(sct=0;sctevent_count++; - if (conn->listener) - { - DBGPRINT("\nEVENT mask %08X fd=%d accept\n",events[i].events,conn->fd); - - accept_salen = sizeof(accept_sa); - //Accept new connection -#if defined (__APPLE__) - // macos does not have accept4() - tmp_fd = accept(conn->fd, (struct sockaddr*)&accept_sa, &accept_salen); -#else - tmp_fd = accept4(conn->fd, (struct sockaddr*)&accept_sa, &accept_salen, SOCK_NONBLOCK); -#endif - if (tmp_fd < 0) - { - DLOG_PERROR("Failed to accept connection"); - } - else if (legs_local >= params.maxconn) // each connection has 2 legs - local and remote - { - close(tmp_fd); - VPRINT("Too many local legs : %d\n", legs_local); - } -#if defined (__APPLE__) - // separate fcntl call to comply with macos - else if (fcntl(tmp_fd, F_SETFL, O_NONBLOCK) < 0) - { - DLOG_PERROR("socket set O_NONBLOCK (accept)"); - close(tmp_fd); - } -#endif - else if (!(conn=add_tcp_connection(efd, &conn_list, tmp_fd, (struct sockaddr*)&accept_sa, params.port, params.proxy_type))) - { - // add_tcp_connection closes fd in case of failure - VPRINT("Failed to add connection\n"); - } - else - { - print_legs(); - - if (params.debug>=1) - { - char ip_port[48]; - ntop46_port((struct sockaddr*)&conn->client,ip_port,sizeof(ip_port)); - VPRINT("Socket fd=%d (local) connected from %s\n", conn->fd, ip_port); - } - set_user_timeout(conn->fd, params.tcp_user_timeout_local); - } - } - else - { - DBGPRINT("\nEVENT mask %08X fd=%d remote=%d fd_partner=%d\n",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)) - { - int errn = get_so_error(conn->fd); - const char *se; - switch (events[i].events & (EPOLLHUP|EPOLLERR)) - { - case EPOLLERR: se="EPOLLERR"; break; - case EPOLLHUP: se="EPOLLHUP"; break; - case EPOLLHUP|EPOLLERR: se="EPOLLERR EPOLLHUP"; break; - default: se=NULL; - } - VPRINT("Socket fd=%d (partner_fd=%d, remote=%d) %s so_error=%d (%s)\n",conn->fd,conn->partner ? conn->partner->fd : 0,conn->remote,se,errn,strerror(errn)); - proxy_remote_conn_ack(conn,errn); - read_all_and_buffer(conn,3); - if (errn==ECONNRESET && conn_partner_alive(conn)) - { - if (conn->remote && params.tamper) rst_in(&conn->partner->track,(struct sockaddr*)&conn->partner->client); - - struct linger lin; - lin.l_onoff=1; - lin.l_linger=0; - DBGPRINT("setting LINGER=0 to partner to force mirrored RST close\n"); - if (setsockopt(conn->partner->fd,SOL_SOCKET,SO_LINGER,&lin,sizeof(lin))<0) - DLOG_PERROR("setsockopt (SO_LINGER)"); - } - 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\n", conn->fd); - conn_close_both(&conn_list,&close_list,conn); - continue; - } - } - if (events[i].events & EPOLLRDHUP) - { - DBGPRINT("EPOLLRDHUP\n"); - read_all_and_buffer(conn,2); - if (!conn->remote && params.tamper) hup_out(&conn->track,(struct sockaddr*)&conn->client); - - conn->state = CONN_RDHUP; // only writes. do not receive RDHUP anymore - if (conn_has_unsent(conn)) - { - DBGPRINT("conn fd=%d has unsent\n", conn->fd); - epoll_set_flow(conn,false,true); - } - else - { - DBGPRINT("conn fd=%d has no unsent\n", conn->fd); - conn->bFlowIn = false; - epoll_update_flow(conn); - if (conn_partner_alive(conn)) - { - if (conn_has_unsent(conn->partner)) - DBGPRINT("partner has unset. partner shutdown delayed.\n"); - else - { - DBGPRINT("partner has no unsent. shutting down partner.\n"); - if (!conn_shutdown(conn->partner)) - { - DBGPRINT("emergency connection close due to failed shutdown\n"); - conn_close_with_partner_check(&conn_list,&close_list,conn); - } - if (conn->partner->state==CONN_RDHUP) - { - DBGPRINT("both partners are in RDHUP state and have no unsent. closing.\n"); - conn_close_with_partner_check(&conn_list,&close_list,conn); - } - } - } - else - { - DBGPRINT("partner is absent or not alive. closing.\n"); - close_tcp_conn(&conn_list,&close_list,conn); - } - } - continue; - } - - if (events[i].events & (EPOLLIN|EPOLLOUT)) - { - const char *se; - switch (events[i].events & (EPOLLIN|EPOLLOUT)) - { - case EPOLLIN: se="EPOLLIN"; break; - case EPOLLOUT: se="EPOLLOUT"; break; - case EPOLLIN|EPOLLOUT: se="EPOLLIN EPOLLOUT"; break; - default: se=NULL; - } - if (se) DBGPRINT("%s\n",se); - // will not receive this until successful check_connection_attempt() - if (!handle_epoll(conn, &conn_list, events[i].events)) - { - DBGPRINT("handle_epoll false\n"); - conn_close_with_partner_check(&conn_list,&close_list,conn); - continue; - } - if ((conn->state == CONN_RDHUP) && conn_partner_alive(conn) && !conn->partner->bShutdown && !conn_has_unsent(conn)) - { - DBGPRINT("conn fd=%d has no unsent. shutting down partner.\n", conn->fd); - if (!conn_shutdown(conn->partner)) - { - DBGPRINT("emergency connection close due to failed shutdown\n"); - 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(); - } - } - -ex: - if (efd) close(efd); - free(listen_conn); - resolver_deinit(); - if (resolve_pipe[0]) close(resolve_pipe[0]); - if (resolve_pipe[1]) close(resolve_pipe[1]); - return retval; -} diff --git a/tpws/tpws_conn.h b/tpws/tpws_conn.h deleted file mode 100644 index 9c81550c..00000000 --- a/tpws/tpws_conn.h +++ /dev/null @@ -1,108 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include "tamper.h" -#include "params.h" -#include "resolver.h" - -#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 -#define DEFAULT_TCP_USER_TIMEOUT_LOCAL 10 -#define DEFAULT_TCP_USER_TIMEOUT_REMOTE 20 - -int event_loop(const int *listen_fd, size_t listen_fd_ct); - -//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 -{ - uint8_t *data; - size_t len,pos; - int ttl, flags; -}; -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 listener; // true - listening socket. false = connecion socket - 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; - sockaddr_in46 client, dest; // ip:port of client, ip:port of target - - struct tproxy_conn *partner; // other leg - time_t orphan_since; - - // socks5 state machine - enum { - S_WAIT_HANDSHAKE=0, - S_WAIT_REQUEST, - S_WAIT_RESOLVE, - S_WAIT_CONNECTION, - S_TCP - } socks_state; - uint8_t socks_ver; - struct resolve_item *socks_ri; - - // 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, bShutdown, bFlowInPrev,bFlowOutPrev, bPrevRdhup; - - // total read,write - uint64_t trd,twr, tnrd; - // 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]; - - t_ctrack track; - - //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 deleted file mode 100644 index 9a396b61..00000000 --- a/tpws/uthash.h +++ /dev/null @@ -1,1136 +0,0 @@ -/* -Copyright (c) 2003-2021, Troy D. Hanson http://troydhanson.github.io/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.3.0 - -#include /* memcmp, memset, strlen */ -#include /* ptrdiff_t */ -#include /* exit */ - -#if defined(HASH_DEFINE_OWN_STDINT) && HASH_DEFINE_OWN_STDINT -/* This codepath is provided for backward compatibility, but I plan to remove it. */ -#warning "HASH_DEFINE_OWN_STDINT is deprecated; please use HASH_NO_STDINT instead" -typedef unsigned int uint32_t; -typedef unsigned char uint8_t; -#elif defined(HASH_NO_STDINT) && HASH_NO_STDINT -#else -#include /* uint8_t, uint32_t */ -#endif - -/* 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 - -#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_strlen -#define uthash_strlen(s) strlen(s) -#endif - -#ifndef HASH_FUNCTION -#define HASH_FUNCTION(keyptr,keylen,hashv) HASH_JEN(keyptr, keylen, hashv) -#endif - -#ifndef HASH_KEYCMP -#define HASH_KEYCMP(a,b,n) memcmp(a,b,n) -#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*)(void*)(((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_FUNCTION(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 { \ - (out) = NULL; \ - if (head) { \ - 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 = (const void*) (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 -#include /* fprintf, stderr */ -#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 - -/* 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]; /* FALLTHROUGH */ \ - default: ; \ - } \ - 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; \ - break; \ - default: ; \ - } \ - \ - /* 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) - -/* 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 (HASH_KEYCMP((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( \ - sizeof(struct UT_hash_bucket) * (tbl)->num_buckets * 2U); \ - if (!_he_new_buckets) { \ - HASH_RECORD_OOM(oomed); \ - } else { \ - uthash_bzero(_he_new_buckets, \ - sizeof(struct UT_hash_bucket) * (tbl)->num_buckets * 2U); \ - (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++; \ - if (_he_newbkt->count > _he_newbkt->expand_mult * (tbl)->ideal_chain_maxlen) { \ - _he_newbkt->expand_mult++; \ - } \ - } \ - _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*)(void*)(((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 */ - const 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 */