Merge pull request #81 from awslawsl233/patch-1

Use Markdown for english docs, for better readability
This commit is contained in:
bol-van 2021-12-14 15:24:24 +03:00 committed by GitHub
commit 1d8ab1722e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 432 additions and 269 deletions

View File

@ -1,12 +1,10 @@
Supported versions
------------------
## Supported versions
FreeBSD 11.x+ , OpenBSD 6.x+, partially MacOS Sierra+
Older versions may work or not. pfSense is not supported.
BSD features
------------
## 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.
@ -14,25 +12,30 @@ 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 in FreeBSD use 'make', in OpenBSD - use 'make bsd', in MacOS - use 'make mac'.
Compile all programs : make -C /opt/zapret
Compile all programs with PF support : make -C /opt/zapret CFLAGS=-DUSE_PF
To compile sources in FreeBSD use `make`, in OpenBSD - use `make bsd`, in MacOS - use `make mac`.
Compile all programs : `make -C /opt/zapret`
Compile all programs with PF support : `make -C /opt/zapret CFLAGS=-DUSE_PF`
In FreeBSD enable PF only if you use it. Its undesirable if you don't.
PF is enabled automatically in OpenBSD and MacOS.
Divert sockets are internal type sockets in the BSD kernel. They have no relation to network addresses
or network packet exchange. They are identified by a port number 1..65535. Its like queue number in NFQUEUE.
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.
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.
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.
- 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.
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.
@ -47,47 +50,54 @@ It works for the moment but who knows. Such a usage is not very documented.
mdig and ip2net are fully compatible with BSD.
FreeBSD
-------
## 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 :
-----------
```
Later you will add ipfw commands to `/etc/rc.firewall.my` to be reapplied after reboot.
You can also run zapret daemons from there. Start them with `--daemon` options, for example :
```
pkill ^dvtws$
/opt/zapret/nfq/dvtws --port=989 --daemon --dpi-desync=split2
-----------
To restart firewall and daemons run : /etc/rc.d/ipfw restart
```
To restart firewall and daemons run : `/etc/rc.d/ipfw restart`
Assume LAN='em1', WAN="em0".
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
@ -96,11 +106,14 @@ 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
```
write the line : `0 12 */2 * * /opt/zapret/ipset/get_config.sh`
When using ipfw tpws does not require special permissions for transparent mode.
However without root its not possible to bind to ports <1024 and change UID/GID. Without changing UID tpws
@ -112,15 +125,19 @@ listen to that port and intercept traffic.
dvtws quick start.
For all traffic:
```
ipfw delete 100
ipfw add 100 divert 989 tcp from any to any 80,443 out not diverted not sockarg xmit em0
/opt/zapret/nfq/dvtws --port=989 --dpi-desync=split2
```
Process only table zapret with the exception of table nozapret :
```
ipfw delete 100
ipfw add 100 allow tcp from me to table\(nozapret\) 80,443
ipfw add 100 divert 989 tcp from any to table\(zapret\) 80,443 out not diverted not sockarg xmit em0
/opt/zapret/nfq/dvtws --port=989 --dpi-desync=split2
```
Reinjection loop avoidance.
FreeBSD artificially ignores sockarg for ipv6 in the kernel.
@ -137,18 +154,21 @@ Look for fe80:... address in ifconfig and use it for redirection target.
4) How to set maximum table size : sysctl net.pf.request_maxcount=2000000
5) The word 'divert-packet' is absent in the pfctl binary, divert-packet rules are not working.
'divert-to' is not the same thing. Looks like its not possible to use dvtws with PF in FreeBSD.
/etc/pf.conf
-----------
```
rdr pass on em1 inet6 proto tcp to port {80,443} -> fe80::31c:29ff:dee2:1c4d port 988
rdr pass on em1 inet proto tcp to port {80,443} -> 127.0.0.1 port 988
-----------
```
then
```
/opt/zapret/tpws/tpws --port=988 --bind-addr=127.0.0.1 --bind-iface6=em1 --bind-linklocal=force
```
Its not clear how to do rdr-to outgoing traffic. I could not make route-to scheme work.
OpenBSD
-------
## 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.
@ -156,12 +176,15 @@ Use --bind-addr=0.0.0.0 --bind-addr=:: to achieve the same default bind as in o
tpws for forwarded traffic only :
/etc/pf.conf
------------
```
pass in quick on em1 inet proto tcp to port {80,443} rdr-to 127.0.0.1 port 988
pass in quick on em1 inet6 proto tcp to port {80,443} rdr-to ::1 port 988
------------
```
then
```
pfctl -f /etc/pf.conf
tpws --port=988 --user=daemon --bind-addr=::1 --bind-addr=127.0.0.1
```
Its not clear how to do rdr-to outgoing traffic. I could not make route-to scheme work.
rdr-to support is done using /dev/pf, that's why transparent mode requires root.
@ -169,17 +192,20 @@ rdr-to support is done using /dev/pf, that's why transparent mode requires root.
dvtws for all traffic:
/etc/pf.conf
------------
```
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=split2
```
dwtws only for table zapret with the exception of table nozapret :
/etc/pf.conf
------------
```
set limit table-entries 2000000
table <zapret> file "/opt/zapret/ipset/zapret-ip.txt"
table <zapret-user> file "/opt/zapret/ipset/zapret-ip-user.txt"
@ -197,9 +223,12 @@ pass in quick on em0 inet6 proto tcp from <zapret6> port {80,443} no state
pass out quick on em0 inet6 proto tcp to <zapret6> port {80,443} divert-packet port 989 no state
pass in quick on em0 inet6 proto tcp from <zapret6-user> port {80,443} no state
pass out quick on em0 inet6 proto tcp to <zapret6-user> port {80,443} divert-packet port 989 no state
------------
```
then
```
pfctl -f /etc/pf.conf
./dvtws --port=989 --dpi-desync=split2
```
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.
@ -211,21 +240,30 @@ Looks like pf automatically prevent reinsertion of diverted frames. Loop problem
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 :
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
```
Newer pfctl versions can reload tables only :
```
pfctl -Tl -f /etc/pf.conf
```
But OpenBSD 6.8 pfctl is old enough and does not support that. Newer FreeBSD do.
Don't forget to disable gzip compression :
```
GZIP_LISTS=0
```
If some list files do not exist and have references in pf.conf it leads to error.
You need to exclude those tables from pf.conf and referencing them rules.
After configuration is done you can put ipset script :
```
crontab -e
write the line : 0 12 */2 * * /opt/zapret/ipset/get_config.sh
```
then write the line : `0 12 */2 * * /opt/zapret/ipset/get_config.sh`
MacOS
-----
## 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.
@ -235,14 +273,20 @@ What everyone have updated long ago they keep old like a mammoth. But who cares
MacOS used to have ipfw but it was removed later and replaced by PF.
It looks like divert sockets are internally replaced with raw. Its possible to request a divert socket
but it behaves exactly as raw socket with all its BSD inherited + apple specific bugs and feature.
The fact is that divert-packet in /etc/pf.conf does not work. pfctl binary does not contain the word 'divert'.
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
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.
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.
@ -250,100 +294,130 @@ Because tpws is forced to run as root to avoid loop its necessary to exempt root
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.
Put the string `net.inet6.send.opmode=0` to `/etc/sysctl.conf`. If not present - create it.
Then reboot the system.
If you dont like this solution you can assign an additional static ipv6 address from fd00::/8 range with /128 prefix
If you dont like this solution you can assign an additional static ipv6 address from `fd00::/8` range with `/128` prefix
to your LAN interface and use it as the gateway address.
tpws transparent mode only for outgoing connections.
/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.
Build from source : make -C /opt/zapret mac
### MacOS easy install
ipset/*.sh scripts work.
`install_easy.sh` supports MacOS
MacOS easy install
------------------
install_easy.sh supports MacOS
Shipped precompiled binaries are built for 64-bit MacOS with -mmacosx-version-min=10.8 option.
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.
If no - its easy to build your own. Running `make` automatically installs developer tools.
!! **Internet sharing is not supported** !!
!! 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
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)
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.
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.
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) :
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.
start-fw creates 3 anchor files in /etc/pf.anchors : zapret,zapret-v4,zapret-v6.
Last 2 are referenced by anchor "zapret".
Tables nozapret,nozapret6 belong to anchor "zapret".
Tables zapret,zapret-user belong to anchor "zapret-v4".
Tables zapret6,zapret6-user belong to anchor "zapret-v6".
If an ip version is disabled then corresponding anchor is empty and is not referenced from the anchor "zapret".
- 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.

View File

@ -1,5 +1,4 @@
What is it for
--------------
## 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 protocol discovery.
@ -10,14 +9,16 @@ blocked by Roskomnadzor), but most others are common.
Mainly OpenWRT targeted but also supports traditional Linux, FreeBSD, OpenBSD, partially MacOS.
How it works
------------
## 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.
@ -27,25 +28,25 @@ This project is aimed at preventing the ban rather than eliminating its conseque
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."
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
-------------------------------------------------
## 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.
1. Passive DPI not sending RST to the server. ISP tuned iptables commands can help.
This option is out of the scope of the project. If you do not allow ban trigger to fire, then you wont have to
deal with its consequences.
2) Modification of the TCP connection at the stream level. Implemented through a proxy or transparent proxy.
3) Modification of TCP connection at the packet level. Implemented through the NFQUEUE handler and raw sockets.
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.
@ -53,55 +54,55 @@ You need to run them with the necessary parameters and redirect certain traffic
To redirect a TCP connection to a transparent proxy, the following commands are used:
forwarded traffic :
iptables -t nat -I PREROUTING -i <internal_interface> -p tcp --dport 80 -j DNAT --to 127.0.0.127:988
`iptables -t nat -I PREROUTING -i <internal_interface> -p tcp --dport 80 -j DNAT --to 127.0.0.127:988`
outgoing traffic :
iptables -t nat -I OUTPUT -o <external_interface> -p tcp --dport 80 -m owner ! --uid-owner tpws -j DNAT --to 127.0.0.127:988
`iptables -t nat -I OUTPUT -o <external_interface> -p tcp --dport 80 -m owner ! --uid-owner tpws -j DNAT --to 127.0.0.127:988`
DNAT on localhost works in the OUTPUT chain, but does not work in the PREROUTING chain without enabling the route_localnet parameter:
sysctl -w net.ipv4.conf.<internal_interface>.route_localnet=1
`sysctl -w net.ipv4.conf.<internal_interface>.route_localnet=1`
You can use "-j REDIRECT --to-port 988" instead of DNAT, but in this case the transparent proxy process
You can use `-j REDIRECT --to-port 988` instead of DNAT, but in this case the transparent proxy process
should listen on the ip address of the incoming interface or on all addresses. Listen all - not good
in terms of security. Listening one (local) is possible, but automated scripts will have to recognize it,
then dynamically enter it into the command. In any case, additional efforts are required.
Using route_localnet can also introduce some security risks. You make available from internal_interface everything
bound to 127.0.0.0/8. Services are usually bound to 127.0.0.1. Its possible to deny input to 127.0.0.1 from all interfaces except lo
or bind tpws to any other IP from 127.0.0.0/8 range, for example to 127.0.0.127, and allow incomings only to that IP :
bound to `127.0.0.0/8`. Services are usually bound to `127.0.0.1`. Its possible to deny input to `127.0.0.1` from all interfaces except lo
or bind tpws to any other IP from `127.0.0.0/8` range, for example to `127.0.0.127`, and allow incomings only to that IP :
```
iptables -A INPUT ! -i lo -d 127.0.0.127 -j ACCEPT
iptables -A INPUT ! -i lo -d 127.0.0.0/8 -j DROP
```
Owner filter is necessary to prevent recursive redirection of connections from tpws itself.
tpws must be started under OS user "tpws".
tpws must be started under OS user `tpws`.
NFQUEUE redirection of the outgoing traffic and forwarded traffic going towards the external interface,
can be done with the following commands:
iptables -t mangle -I POSTROUTING -o <external_interface> -p tcp --dport 80 -j NFQUEUE --queue-num 200 --queue-bypass
`iptables -t mangle -I POSTROUTING -o <external_interface> -p tcp --dport 80 -j NFQUEUE --queue-num 200 --queue-bypass`
In order not to touch the traffic to unblocked addresses, you can take a list of blocked hosts, resolve it
into IP addresses and put them to ipset 'zapret', then add a filter to the command:
iptables -t mangle -I POSTROUTING -o <external_interface> -p tcp --dport 80 -m set --match-set zapret dst -j NFQUEUE --queue-num 200 --queue-bypass
`iptables -t mangle -I POSTROUTING -o <external_interface> -p tcp --dport 80 -m set --match-set zapret dst -j NFQUEUE --queue-num 200 --queue-bypass`
Some DPIs catch only the first http request, ignoring subsequent requests in a keep-alive session.
Then we can reduce CPU load, refusing to process unnecessary packets.
iptables -t mangle -I POSTROUTING -o <external_interface> -p tcp --dport 80 -m connbytes --connbytes-dir=original --connbytes-mode=packets --connbytes 1:4 -m mark ! --mark 0x40000000/0x40000000 -m set --match-set zapret dst -j NFQUEUE --queue-num 200 --queue-bypass
`iptables -t mangle -I POSTROUTING -o <external_interface> -p tcp --dport 80 -m connbytes --connbytes-dir=original --connbytes-mode=packets --connbytes 1:4 -m mark ! --mark 0x40000000/0x40000000 -m set --match-set zapret dst -j NFQUEUE --queue-num 200 --queue-bypass`
Mark filter does not allow nfqws-generated packets to enter the queue again.
Its necessary to use this filter when also using "connbytes 1:4". Without it packet ordering can be changed breaking the whole idea.
Its necessary to use this filter when also using `connbytes 1:4`. Without it packet ordering can be changed breaking the whole idea.
ip6tables
---------
## ip6tables
ip6tables work almost exactly the same way as ipv4, but there are a number of important nuances.
In DNAT, you should take the address --to in square brackets. For example :
ip6tables -t nat -I OUTPUT -o <external_interface> -p tcp --dport 80 -m owner ! --uid-owner tpws -j DNAT --to [::1]:988
`ip6tables -t nat -I OUTPUT -o <external_interface> -p tcp --dport 80 -m owner ! --uid-owner tpws -j DNAT --to [::1]:988`
The route_localnet parameter does not exist for ipv6.
DNAT to localhost (:: 1) is possible only in the OUTPUT chain.
@ -109,27 +110,21 @@ In the PREROUTING DNAT chain, it is possible to any global address or to the lin
the packet came from.
NFQUEUE works without changes.
When it will not work
----------------------
## 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.
when blocked domains are queried. If this is the case change DNS to public ones, such as 8.8.8.8 or 1.1.1.1.Sometimes ISP hijacks queries to any DNS server. Dnscrypt or dns-over-tls help.
* If blocking is done by IP.
* If a connection passes through a filter capable of reconstructing a TCP connection, and which
follows all standards. For example, we are routed to squid. Connection goes through the full OS tcpip stack,
fragmentation disappears immediately as a means of circumvention. Squid is correct, it will find everything
as it should, it is useless to deceive him.
BUT. Only small providers can afford using squid, since it is very resource intensive.
Large companies usually use DPI, which is designed for much greater bandwidth.
follows all standards. For example, we are routed to squid. Connection goes through the full OS tcpip stack, fragmentation disappears immediately as a means of circumvention. Squid is correct, it will find everything as it should, it is useless to deceive him. BUT. Only small providers can afford using squid, since it is very resource intensive. Large companies usually use DPI, which is designed for much greater bandwidth.
nfqws
-----
## nfqws
This program is a packet modifier and a NFQUEUE queue handler.
For BSD systems there is dvtws. Its built from the same source and has almost the same parameters (see bsd.eng.txt).
For BSD systems there is dvtws. Its built from the same source and has almost the same parameters (see bsd.eng.md).
nfqws takes the following parameters:
```
--debug=0|1 ; 1=print debug info
--qnum=<nfqueue_number>
--wsize=<winsize>[:<scale_factor>] ; change window size in SYN,ACK packets. default is not to change scale factor (OBSOLETE !)
@ -158,72 +153,77 @@ nfqws takes the following parameters:
--dpi-desync-fake-tls=<filename> ; file containing fake TLS ClientHello (for https). replacement for built-in
--dpi-desync-cutoff=N ; apply dpi desync only to packet numbers less than N
--hostlist=<filename> ; apply fooling only to the listed hosts (one host per line, subdomains auto apply)
```
The manipulation parameters can be combined in any way.
WARNING. --wsize parameter is now not used anymore in scripts. TCP split can be achieved using DPI desync attack.
WARNING. `--wsize` parameter is now not used anymore in scripts. TCP split can be achieved using DPI desync attack.
### DPI desync attack
DPI DESYNC ATTACK
After completion of the tcp 3-way handshake, the first data packet from the client goes.
It usually has "GET / ..." or TLS ClientHello. We drop this packet, replacing with something else.
It usually has `GET / ...` or TLS ClientHello. We drop this packet, replacing with something else.
It can be a fake version with another harmless but valid http or https request (fake), tcp reset packet (rst,rstack),
split into 2 segments original packet with fake segment in the middle (disorder).
In articles these attack have names "TCB desynchronization" and "TCB teardown".
In articles these attack have names **TCB desynchronization** and **TCB teardown**.
Fake packet must reach DPI, but do not reach the destination server.
The following means are available: set a low TTL, send a packet with bad checksum,
add tcp option "MD5 signature". All of them have their own disadvantages :
add tcp option **MD5 signature**. All of them have their own disadvantages :
* md5sig does not work on all servers
* badsum doesn't work if your device is behind NAT which does not pass invalid packets.
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 contrack to verify tcp and udp checksums
The default sysctl configuration `net.netfilter.nf_conntrack_checksum=1` causes contrack 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.
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.
* badsum doesn't work if your device is behind NAT which does not pass invalid packets.
Linux NAT by default does not pass them without special setting "sysctl -w net.netfilter.nf_conntrack_checksum=0"
Linux NAT by default does not pass them without special setting `sysctl -w net.netfilter.nf_conntrack_checksum=0`
Openwrt sets it from the box, other routers in most cases don't, and its not always possible to change it.
If nfqws is on the router, its not necessary to switch of "net.netfilter.nf_conntrack_checksum".
If nfqws is on the router, its not necessary to switch of `net.netfilter.nf_conntrack_checksum`.
Fake packet doesn't go through FORWARD chain, it goes through OUTPUT. But if your router is behind another NAT, for example ISP NAT,
and that NAT does not pass invalid packets, you cant do anything.
* badseq packets will be dropped by server, but DPI also can ignore them
* TTL looks like the best option, but it requires special tuning for 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.
If automatic solution cannot be found then use `zapret-hosts-user-exclude.txt`.
--dpi-desync-fooling takes multiple comma separated values.
`--dpi-desync-fooling` takes multiple comma separated values.
For fake,rst,rstack modes original packet can be sent after the fake one or just dropped.
If its dropped OS will perform first retransmission after 0.2 sec, then the delay increases exponentially.
Delay can help to make sure fake and original packets are properly ordered and processed on DPI.
When dpi-desync-retrans=1 its mandatory to use connbytes in iptables rule. Otherwise loop happens.
When `dpi-desync-retrans=1` its mandatory to use connbytes in iptables rule. Otherwise loop happens.
Disorder mode splits original packet and sends packets in the following order :
1. 2nd segment
2. fake 1st segment, data filled with zeroes
3. 1st segment
4. fake 1st segment, data filled with zeroes (2nd copy)
Original packet is always dropped. --dpi-desync-split-pos sets split position (default 3).
Original packet is always dropped. `--dpi-desync-split-pos` sets split position (default 3).
If position is higher than packet length, pos=1 is used.
This sequence is designed to make reconstruction of critical message as difficult as possible.
Fake segments may not be required to bypass some DPIs, but can potentially help if more sophisticated reconstruction
algorithms are used.
Mode 'disorder2' disables sending of fake segments.
Mode `disorder2` disables sending of fake segments.
Split mode is very similar to disorder but without segment reordering :
1. fake 1st segment, data filled with zeroes
2. 1st segment
3. fake 1st segment, data filled with zeroes (2nd copy)
4. 2nd segment
Mode 'split2' disables sending of fake segments. It can be used as a faster alternative to --wsize.
In disorder2 and split2 modes no fake packets are sent, so ttl and fooling options are not required.
Mode `split2` disables sending of fake segments. It can be used as a faster alternative to --wsize.
In `disorder2` and 'split2` modes no fake packets are sent, so ttl and fooling options are not required.
There are DPIs that analyze responses from the server, particularly the certificate from the ServerHello
that contain domain name(s). The ClientHello delivery confirmation is an ACK packet from the server
@ -243,105 +243,140 @@ Subdomains are applied automatically. gzip lists are supported.
iptables for performing the attack on the first packet :
iptables -t mangle -I POSTROUTING -o <external_interface> -p tcp -m multiport --dports 80,443 -m connbytes --connbytes-dir=original --connbytes-mode=packets --connbytes 1:4 -m mark ! --mark 0x40000000/0x40000000 -j NFQUEUE --queue-num 200 --queue-bypass
`iptables -t mangle -I POSTROUTING -o <external_interface> -p tcp -m multiport --dports 80,443 -m connbytes --connbytes-dir=original --connbytes-mode=packets --connbytes 1:4 -m mark ! --mark 0x40000000/0x40000000 -j NFQUEUE --queue-num 200 --queue-bypass`
This is good if DPI does not track all requests in http keep-alive session.
If it does, then pass all outgoing packets for http and only first data packet for https :
```
iptables -t mangle -I POSTROUTING -o <external_interface> -p tcp --dport 443 -m connbytes --connbytes-dir=original --connbytes-mode=packets --connbytes 1:4 -m mark ! --mark 0x40000000/0x40000000 -j NFQUEUE --queue-num 200 --queue-bypass
iptables -t mangle -I POSTROUTING -o <external_interface> -p tcp --dport 80 -m mark ! --mark 0x40000000/0x40000000 -j NFQUEUE --queue-num 200 --queue-bypass
```
mark is needed to keep away generated packets from NFQUEUE. nfqws sets fwmark when it sends generated packets.
nfqws can internally filter marked packets. but when connbytes filter is used without mark filter
packet ordering can be changed breaking the whole idea of desync attack.
DPI DESYNC COMBOS
### DPI deync combos
dpi-desync parameter takes up to 3 comma separated arguments.
zero phase means tcp connection establishement (before sending data payload). Mode can be "synack".
zero phase means tcp connection establishement (before sending data payload). Mode can be `synack`.
Hostlist filter is not applicable to the zero phase.
Next phases work on packets with data payload.
1st phase mode can be fake,rst,rstack, 2nd phase mode - disorder,disorder2,split,split2.
1st phase mode can be `fake`,`rst`,`rstack`, 2nd phase mode - `disorder`,`disorder2`,`split`,`split2`.
Can be useful for ISPs with more than one DPI.
SYNACK MODE
In geneva docs it's called "TCP turnaround". Attempt to make the DPI believe the roles of client and server are reversed.
### SYNACK mode
In geneva docs it's called **TCP turnaround**. Attempt to make the DPI believe the roles of client and server are reversed.
!!! This mode breaks NAT operation and can be used only if there's no NAT between the attacker's device and the DPI !
In linux it's required to remove standard firewall rule dropping INVALID packets in the OUTPUT chain,
for example : -A OUTPUT -m state --state INVALID -j DROP
for example : `-A OUTPUT -m state --state INVALID -j DROP`
In openwrt it's possible to disable the rule for both FORWARD and OUTPUT chains in /etc/config/firewall :
```
config zone
option name 'wan'
.........
option masq_allow_invalid '1'
```
Unfortunately there's no OUTPUT only switch. It's not desired to remove the rule from the FORWARD chain.
Add the following lines to /etc/firewall.user :
Add the following lines to `/etc/firewall.user` :
```
iptables -D zone_wan_output -m comment --comment '!fw3' -j zone_wan_dest_ACCEPT
ip6tables -D zone_wan_output -m comment --comment '!fw3' -j zone_wan_dest_ACCEPT
```
then /etc/init.d/firewall restart
then `/etc/init.d/firewall restart`
Otherwise raw sending SYN,ACK frame will cause error stopping the further processing.
If you realize you don't need the synack mode it's highly suggested to restore drop INVALID rule.
VIRTUAL MACHINES
### 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.
### CONNTRACK
CONNTRACK
nfqws is equipped with minimalistic connection tracking system (conntrack)
It's enabled if some specific DPI circumvention methods are involved.
Currently these are --wssize and --dpi-desync-cutoff options.
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.
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.
Currently these are `--wssize` and `--dpi-desync-cutoff` options.
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.
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.
If you deal with a non-http(s) protocol you need --wssize-cutoff. It sets the number of the outgoing packet where wssize stops.
If you deal with a non-http(s) protocol you need `--wssize-cutoff`. It sets the number of the outgoing packet where wssize stops.
(numbering starts from 1).
If a http request or TLS ClientHello packet is detected wssize stops immediately ignoring wssize-cutoff option.
If your protocol is prone to long inactivity, you should increase ESTABLISHED phase timeout using --ctrack-timeouts.
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.
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.
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
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.
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.
Hostlist filter does not affect --wssize because it works since the connection initiation when it's not yet possible
Hostlist filter does not affect `--wssize` because it works since the connection initiation when it's not yet possible
to extract the host name.
--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 limit on the number of the outgoing packet, at which it stops
applying dpi-desync. Useful with --dpi-desync-any-protocol=1.
`--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 limit on the number of the outgoing packet, at which it stops
applying dpi-desync. 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.
tpws
-----
## tpws
tpws is transparent proxy.
```
--debug=0|1|2 ; 0(default)=silent 1=verbose 2=debug
--bind-addr=<v4_addr>|<v6_addr>; for v6 link locals append %interface_name : fe80::1%br-lan
--bind-iface4=<interface_name> ; bind to the first ipv4 addr of interface
@ -386,190 +421,233 @@ tpws is transparent proxy.
--pidfile=<filename> ; write pid to file
--user=<username> ; drop root privs
--uid=uid[:gid] ; drop root privs
```
The manipulation parameters can be combined in any way.
split-http-req takes precedence over split-pos for http reqs.
split-pos works by default only on http and TLS ClientHello. use --split-any-protocol to act on any packet
`split-http-req` takes precedence over split-pos for http reqs.
split-pos works by default only on http and TLS ClientHello. use `--split-any-protocol` to act on any packet
tpws can bind to multiple interfaces and IP addresses (up to 32).
Port number is always the same.
Parameters --bind-iface* and --bind-addr create new bind.
Other parameters --bind-* are related to the last bind.
link local ipv6 (fe80::/8) mode selection :
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 fd00::/8, then global address
--bind-iface6 --bind-linklocal=unwanted : first selects private address fd00::/8, then global address, then LL
--bind-iface6 --bind-linklocal=prefer : first selects LL, then private address fd00::/8, 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.
```
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
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
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
connection to local IPs of the system where tpws runs are prohibited
tpws supports remote dns resolving (curl : --socks5-hostname firefox : socks_remote_dns=true) , but does it in blocking mode.
tpws supports remote dns resolving (curl : `--socks5-hostname` firefox : `socks_remote_dns=true`) , but does it in blocking mode.
tpws uses async sockets for all activity but resolving can break this model.
if tpws serves many clients it can cause trouble. also DoS attack is possible against tpws.
if remote resolving causes trouble configure clients to use local name resolution and use
--no-resolve option on tpws side.
Ways to get a list of blocked IP
--------------------------------
## Ways to get a list of blocked IP
1) Enter the blocked domains to ipset/zapret-hosts-user.txt and run ipset/get_user.sh
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
2. `ipset/get_reestr_*.sh`. Russian specific
3) ipset/get_antifilter_*.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.
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.
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.
`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 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.
`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
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
`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.
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.
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
---------------------
## Domain name filtering
An alternative to ipset is to use tpws or nfqws with a list of domains. Only one list is supported.
Enter the blocked domains to ipset/zapret-hosts-users.txt. Remove ipset/zapret-hosts.txt.gz.
Then the init script will run tpws with the zapret-hosts-users.txt list.
Enter the blocked domains to `ipset/zapret-hosts-users.txt`. Remove `ipset/zapret-hosts.txt.gz`.
Then the init script will run tpws with the `zapret-hosts-users.txt` list.
Other option ( Roskomnadzor list - get_hostlist.sh ) is russian specific.
You can write your own replacement for get_hostlist.sh.
Other option ( Roskomnadzor list - `get_hostlist.sh` ) is russian specific.
You can write your own replacement for `get_hostlist.sh`.
When filtering by domain name, daemons should run without filtering by ipset.
When using large regulator lists estimate the amount of RAM on the router !
Choosing parameters
-------------------
## Choosing parameters
The file /opt/zapret/config is used by various components of the system and contains basic settings.
The file `/opt/zapret/config` is used by various components of the system and contains basic settings.
It needs to be viewed and edited if necessary.
Main mode :
```
tpws - tpws transparent mode
tpws-socks - tpws socks mode
binds to localhost and LAN interface (if IFACE_LAN is specified or the system is OpenWRT). port 988
nfqws - nfqws
filter - only fill ipset or load hostlist
custom - use custom script for running daemons and establishing firewall rules
```
MODE=tpws
`MODE=tpws`
Enable http fooling :
MODE_HTTP=1
`MODE_HTTP=1`
Apply fooling to keep alive http sessions. Only applicable to nfqws. Tpws always fool keepalives.
Not enabling this can save CPU time.
MODE_HTTP_KEEPALIVE=0
`MODE_HTTP_KEEPALIVE=0`
Enable https fooling :
MODE_HTTPS=1
`MODE_HTTPS=1`
Host filtering mode :
```
none - apply fooling to all hosts
ipset - limit fooling to hosts from ipset zapret/zapret6
hostlist - limit fooling to hosts from hostlist
```
MODE_FILTER=none
`MODE_FILTER=none`
Its possible to change manipulation options used by tpws :
TPWS_OPT="--hostspell=HOST --split-http-req=method --split-pos=3"
`TPWS_OPT="--hostspell=HOST --split-http-req=method --split-pos=3"`
nfqws options for DPI desync attack:
```
DESYNC_MARK=0x40000000
NFQWS_OPT_DESYNC="--dpi-desync=fake --dpi-desync-ttl=0 --dpi-desync-fooling=badsum --dpi-desync-fwmark=$DESYNC_MARK"
```
Separate nfqws options for http and https and ip protocol versions 4,6:
```
NFQWS_OPT_DESYNC_HTTP="--dpi-desync=split --dpi-desync-ttl=0 --dpi-desync-fooling=badsum"
NFQWS_OPT_DESYNC_HTTPS="--wssize=1:6 --dpi-desync=split --dpi-desync-ttl=0 --dpi-desync-fooling=badsum"
NFQWS_OPT_DESYNC_HTTP6="--dpi-desync=split --dpi-desync-ttl=5 --dpi-desync-fooling=none"
NFQWS_OPT_DESYNC_HTTPS6="--wssize=1:6 --dpi-desync=split --dpi-desync-ttl=5 --dpi-desync-fooling=none"
```
If one of NFQWS_OPT_DESYNC_HTTP/NFQWS_OPT_DESYNC_HTTPS is not defined it takes value of NFQWS_OPT_DESYNC.
If one of NFQWS_OPT_DESYNC_HTTP6/NFQWS_OPT_DESYNC_HTTPS6 is not defined it takes value from
NFQWS_OPT_DESYNC_HTTP/NFQWS_OPT_DESYNC_HTTPS.
It means if only NFQWS_OPT_DESYNC is defined all four take its value.
If one of `NFQWS_OPT_DESYNC_HTTP`/`NFQWS_OPT_DESYNC_HTTPS` is not defined it takes value of NFQWS_OPT_DESYNC.
If one of `NFQWS_OPT_DESYNC_HTTP6`/`NFQWS_OPT_DESYNC_HTTPS6` is not defined it takes value from
`NFQWS_OPT_DESYNC_HTTP`/`NFQWS_OPT_DESYNC_HTTPS`.
It means if only `NFQWS_OPT_DESYNC` is defined all four take its value.
If a variable is not defined, the value NFQWS_OPT_DESYNC is taken.
If a variable is not defined, the value `NFQWS_OPT_DESYNC` is taken.
flow offloading control (openwrt only)
flow offloading control (OpenWRT only)
```
donttouch : disable system flow offloading setting if selected mode is incompatible with it, dont touch it otherwise and dont configure selective flow offloading
none : always disable system flow offloading setting and dont configure selective flow offloading
software : always disable system flow offloading setting and configure selective software flow offloading
hardware : always disable system flow offloading setting and configure selective hardware flow offloading
```
FLOWOFFLOAD=donttouch
`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).
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
`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.
@ -577,7 +655,7 @@ TMPDIR=/opt/zapret/tmp
ipset options :
IPSET_OPT="hashsize 262144 maxelem 2097152"
`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.
@ -585,11 +663,15 @@ On low RAM systems it can cause errors.
Do not use too high hashsize. This way you waste your RAM. And dont use too low hashsize to avoid reallocs.
ip2net options. separate for ipv4 and ipv6.
```
IP2NET_OPT4="--prefix-length=22-30 --v4-threshold=3/4"
IP2NET_OPT6="--prefix-length=56-64 --v6-threshold=5"
```
Enable gzip compression for large lists. Used by ipset/*.sh scripts.
GZIP_LISTS=1
`GZIP_LISTS=1`
Command to reload ip/host lists after update.
Comment or leave empty for auto backend selection : ipset or ipfw if present.
@ -600,24 +682,25 @@ 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"
`OPENWRT_LAN="lan lan2 lan3"`
The following settings are not relevant for openwrt :
If your system works as a router, then you need to enter the names of the internal and external interfaces:
```
IFACE_LAN = eth0
IFACE_WAN = eth1
```
IMPORTANT: configuring routing, masquerade, etc. not a zapret task.
Only modes that intercept transit traffic are enabled.
It's possible to specify multiple interfaces like this : IFACE_LAN="eth0 eth1 eth2"
It's possible to specify multiple interfaces like this : `IFACE_LAN="eth0 eth1 eth2"`
The INIT_APPLY_FW=1 parameter enables the init script to independently apply iptables rules.
The `INIT_APPLY_FW=1` parameter enables the init script to independently apply iptables rules.
With other values or if the parameter is commented out, the rules will not be applied.
This is useful if you have a firewall management system, in the settings of which you should tie the rules.
Screwing to the firewall control system or your launch system
-------------------------------------------------------------
## 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.
@ -625,25 +708,28 @@ In this case, the rules for iptables should be screwed to your firewall separate
The following calls allow you to apply or remove iptables rules separately:
```
/opt/zapret/init.d/sysv/zapret start-fw
/opt/zapret/init.d/sysv/zapret stop-fw
```
And you can start or stop the demons separately from the firewall:
```
/opt/zapret/init.d/sysv/zapret start-daemons
/opt/zapret/init.d/sysv/zapret stop-daemons
```
## Installation
Simple install to desktop linux system
--------------------------------------
### 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.
Run `install_easy.sh` and answer its questions.
Simple install to openwrt
-------------------------
### OpenWRT
install_easy.sh works on openwrt but there're additional challenges.
`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.
@ -651,14 +737,12 @@ Another challenge would be to bring zapret to the router. You can download zip f
Do not repack zip contents in Windows, because this way you break chmod and links.
Install openssh-sftp-server and unzip to openwrt and use sftp to transfer the file.
The best way to start is to put zapret dir to /tmp and run /tmp/zapret/install_easy.sh from there.
After installation remove /tmp/zapret to free RAM.
The best way to start is to put zapret dir to `/tmp` and run `/tmp/zapret/install_easy.sh` from there.
After installation remove `/tmp/zapret` to free RAM.
The absolute minimum for openwrt is 64/8 system, 64/16 is comfortable, 128/extroot is recommended.
Android
-------
### Android
Its not possible to use nfqws and tpws in transparent proxy mode without root privileges.
Without root tpws can run in --socks mode.
@ -670,60 +754,65 @@ There's no ipset support unless you run custom kernel. In common case task of br
on android is ranging from "not easy" to "almost impossible", unless you find working kernel
image for your device.
Android does not use /etc/passwd, tpws --user won't work. There's replacement.
Use numeric uids in --uid option.
Android does not use /etc/passwd, `tpws --user` won't work. There's replacement.
Use numeric uids in `--uid` option.
Its recommended to use gid 3003 (AID_INET), otherwise tpws will not have inet access.
Example : --uid 1:3003
In iptables use : "! --uid-owner 1" instead of "! --uid-owner tpws".
Example : `--uid 1:3003`
In iptables use : `! --uid-owner 1` instead of `! --uid-owner tpws`.
Write your own shell script with iptables and tpws, run it using your root manager.
Autorun scripts are here :
magisk : /data/adb/service.d
supersu : /system/su.d
magisk : `/data/adb/service.d`
supersu : `/system/su.d`
I haven't checked whether android can kill iptable rules at its own will during wifi connection/disconnection,
mobile data on/off, ...
How to run tpws on root-less android.
You can't write to /system, /data, can't run from sd card.
Selinux prevents running executables in /data/local/tmp from apps.
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.
```
Now its possible to run `/data/local/tmp/zapret/tpws` from any app such as tasker.
FreeBSD, OpenBSD, MacOS
-----------------------
### FreeBSD, OpenBSD, MacOS
see docs/bsd.eng.txt
see docs/bsd.eng.md
Windows (WSL)
-------------
### Windows (WSL)
Using WSL (Windows subsystem for Linux) it's possible to run tpws in socks mode under rather new builds of
windows 10 and windows server.
Its not required to install any linux distributions as suggested in most articles.
tpws is static binary. It doesn't need a distribution.
Install WSL : dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all
Install WSL : `dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all`
Copy binaries/x86_64/tpws_wsl.tgz to the target system.
Run : wsl --import tpws "%USERPROFILE%\tpws" tpws_wsl.tgz
Run tpws : wsl --exec /tpws --uid=1 --no-resolve --socks --bind-addr=127.0.0.1 --port=1080 <fooling_options>
Run : `wsl --import tpws "%USERPROFILE%\tpws" tpws_wsl.tgz`
Run tpws : `wsl --exec /tpws --uid=1 --no-resolve --socks --bind-addr=127.0.0.1 --port=1080 <fooling_options>`
Configure socks as 127.0.0.1:1080 in a browser or another program.
Cleanup : wsl --unregister tpws
Cleanup : `wsl --unregister tpws`
Tested in windows 10 build 19041 (20.04).
NOTICE. There is native windows solution GoodByeDPI. It works on packet level like nfqws.
Other devices
-------------
### 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.
@ -738,10 +827,10 @@ You will need :
* tpws can be run almost anywhere but nfqws require kernel support for NFQUEUE. Its missing in most firmwares.
* too old 2.6 kernels are unsupported and can cause errors
If binaries crash with segfault (rare but happens on some kernels) try to unpack upx like this : upx -d tpws.
First manually debug your scenario. Run iptables + daemon and check if its what you want.
Write your own script with iptables magic and run required daemon from there. Put it to startup.
Dont ask me how to do it. Its different for all firmwares and requires studying.
Find manual or reverse engineer yourself.
Check for race conditions. Firmware can clear or modify iptables after your startup script.
If this is the case then run another script in background and add some delay there.