Hello, Habr!
Today we'll look at how to install the network packet modification utility Zapret on Keenetic routers. Unlike using it on specific devices, installing it on a router allows you to process traffic from all devices connected to your home local network (PCs, smartphones, and smart TVs).
This utility offers two main modes of operation: TPWS or NFQWS. When used in TPWS mode, there are drawbacks, namely that this mode does not modify the QUIC (HTTP/3) protocol, and the YouTube app on smart TVs with WebOS or Android TV stops launching altogether.
Information about YouTube protocols and problems that arise during use
First, let's try to determine which protocols YouTube uses on different devices:
In PC browsers, both the new QUIC protocol (traffic goes over UDP to port 443) and the classic HTTPS (traffic goes over TCP to port 443).
In the YouTube app on Android, on the other hand, the QUIC protocol is used from the start. This became clear immediately after launching TPWS – everything worked fine in PC browsers, but on Android devices, it was as if the utility wasn't being used at all;
With the YouTube app on WebOS, it's even more complicated: the interface loads over HTTPS (TCP port 443), and the video streams over QUIC (UDP port 443). However, if a QUIC connection is not available, the apps on both Android and WebOS devices switch to the HTTPS protocol. So, to prevent YouTube from lagging on your TV and phone when using TPWS, you need to block UDP traffic on port 443 to force the apps to connect via HTTPS. But this won't solve the problem of the app interface failing to load on WebOS (Android TV) when using TPWS.
As for solutions to this problem when using the utility in TPWS mode, I see three possible options:
Adjust the launch parameters for TPWS (there aren't many of them) so they don't break the YouTube app's loading, while everything else works as it should. I was never able to achieve this.
Identify the domain addresses from which the YouTube app loads its interface and add them to the exclude list of TPWS. This should work if these domains are not blocked;
And the most incorrect method is to stop TPWS during the initial loading of the app's interface, and then start it again to watch videos.
I took a different, more flexible and correct path, using the zapret utility in NFQWS mode. This mode has several advantages over TPWS mode – more parameters for modifying network packets, as well as the ability to modify traffic over the QUIC protocol.
Instructions for installing NFQWS on a Keenetic router
These instructions will be demonstrated using a Keenetic Ultra (kn-1810) router as an example.
To install the zapret utility, you must first install the following components in the router's firmware via the web interface: "IPv6 protocol" and "Netfilter subsystem kernel modules" (this only appears after selecting the "IPv6 protocol" component).


Next, you need to prepare an external USB drive and install the Entware repository package system on it according to the instructions - Installing-the-Entware-repository-package-system-on-a-USB-drive.
After deploying Entware on the router, create an SSH connection using a terminal program like PuTTY, specifying the router's IP address (default is 192.168.1.1) and port 222. Then, log in with the username: root and password: keenetic.
Next, you can proceed directly to installing the NFQWS utility:
First, let's find out the name of the external network interface (WAN) on the router. You can find it by using the command
ifconfig, which will list all network interfaces in the system. Simply find the interface that has your external IP address. In my case, it'seth3.Install the packages using the command:
opkg updateopkg install coreutils-sort curl grep gzip ipset iptables kmod_ndms xtables-addons_legacyDownload the archive with the latest tar.gz release of Zapret from GitHub to your computer and upload this archive to the tmp folder on the router's USB drive (you can do this using an SMB (FTP) server or through the USB drive file manager in the web interface).
Navigate to the tmp folder on the router:
cd /opt/tmpUnpack the downloaded Zapret archive using the command:
tar xvzf zapret-v71.4.tar.gzWhere
zapret-v71.4.tar.gzis replaced with the name of your downloaded archive (it changes with each release).After the archive is unpacked, navigate to the extracted folder:
cd ./zapret-v71.4Where
zapret-v71.4is replaced with the name of your folder extracted from the archive (it changes with each release).Run the installation script:
./install_easy.shAt the beginning of the installation, this script will automatically copy all files from the
zapret-v71.4folder to/opt/zapret/.Next, answer all the questions as shown in the screenshot:

Executing the install_easy.sh script Important: choose the traffic filtering mode (none, ipset, hostlist, autohostlist).
none- filtering is disabled, all traffic is processed by the utility. The simplest option. I recommend using it if you don't want to bother with configuring address lists and just want everything to work quickly and easily.ipset- traffic filtering usingipset. A complex mode, read about how it works in the instructions.hostlist- filtering by a list of hosts from files:/opt/zapret/ipset/zapret-hosts-user.txt- specify your domains that need to be processed, or in the file/opt/zapret/ipset/zapret-hosts-user-exclude.txt- domains that should be excluded from processing. Write one domain name or IP address per line. Delete the initial content of the files. I recommend this mode if you want to process only specific addresses or, conversely, if you want to exclude some addresses from processing.autohostlist- hostlist mode + block detection and automatic list maintenance.The filter mode can also be changed later via the
MODE_FILTERparameter in/opt/zapret/config.Next, make your choice:

Finishing the install_easy.sh script Edit the script named zapret in the command line using your preferred text editor (for example, vi) or you can access the USB drive from your computer over the network (if it's shared via an SMB server) and edit it in a text editor on your computer:
vi /opt/zapret/init.d/sysv/zapretAdd the code to load the Linux kernel modules (xt_multiport.ko, xt_connbytes.ko, xt_NFQUEUE.ko) to the
do_start()function, as they are not loaded by default, and without them,iptableswill produce errors when trying to apply the necessary rules.The final
zapretscript looks like this:#!/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() { if lsmod | grep "xt_multiport " &> /dev/null ; then echo "xt_multiport.ko is already loaded" else if insmod /lib/modules/$(uname -r)/xt_multiport.ko &> /dev/null; then echo "iptable_raw.ko loaded" else echo "Cannot find xt_multiport.ko kernel module, aborting" exit 1 fi fi if lsmod | grep "xt_connbytes " &> /dev/null ; then echo "xt_connbytes.ko is already loaded" else if insmod /lib/modules/$(uname -r)/xt_connbytes.ko &> /dev/null; then echo "xt_connbytes.ko loaded" else echo "Cannot find xt_connbytes.ko kernel module, aborting" exit 1 fi fi if lsmod | grep "xt_NFQUEUE " &> /dev/null ; then echo "xt_NFQUEUE.ko is already loaded" else if insmod /lib/modules/$(uname -r)/xt_NFQUEUE.ko &> /dev/null; then echo "xt_NFQUEUE.ko loaded" else echo "Cannot find xt_NFQUEUE.ko kernel module, aborting" exit 1 fi fi 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 ;; *) N=/etc/init.d/$NAME echo "Usage: $N {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 0This completes the editing of the
zapretscript. Save and exit the text editor.Copy the 10-keenetic-udp-fix script for Keenetic to counter the lack of masquerading without ndmmark using the command:
cp -a /opt/zapret/init.d/custom.d.examples.linux/10-keenetic-udp-fix /opt/zapret/init.d/sysv/custom.d/10-keenetic-udp-fixAfter saving the zapret file, create a link to the
zapretscript in the startup directory:ln -fs /opt/zapret/init.d/sysv/zapret /opt/etc/init.d/S90-zapretNext, you need to create a script so that the
netfilterfirewall doesn't forget the rules configured above (otherwise, the system will quickly overwrite our rules). Do this also in the command line using a text editor:vi /opt/etc/ndm/netfilter.d/000-zapret.shPaste the following code into the file:
#!/bin/sh [ "$table" != "mangle" ] && [ "$table" != "nat" ] && exit 0 /opt/zapret/init.d/sysv/zapret restart-fw exit 0And grant execute permission to the
000-zapret.shscript using the command:chmod +x /opt/etc/ndm/netfilter.d/000-zapret.shThis script will call our previously edited zapret script with the restart-fw, parameter, where we defined the iptables rules.
Similarly, create a script to disable packet checksum verification:
vi /opt/etc/init.d/S00fixPaste the following code into the file:
#!/bin/sh start() { sysctl -w net.netfilter.nf_conntrack_checksum=0 &> /dev/null } stop() { sysctl -w net.netfilter.nf_conntrack_checksum=1 &> /dev/null } case "$1" in 'start') start ;; 'stop') stop ;; *) stop start ;; esac exit 0Also, don't forget to grant execute permissions to this script:
chmod +x /opt/etc/init.d/S00fixAnd now, finally, let's configure the NFQWS config, located at /opt/zapret/config.
In the command line - using a text editor, or better yet, edit it from a computer over the network:
vi /opt/zapret/configYou need to make the following changes:
Uncomment the line
WS_USER=nobodyNext, you need to specify the parameters for launching the NFQWS process. Here you need to find the parameters that give you the desired result.
In my case, for the HTTP protocol:
--filter-tcp=80 --dpi-desync=fake,multisplit --dpi-desync-ttl=0 --dpi-desync-fooling=badsum,badseq --dpi-desync-fake-http=/opt/zapret/files/fake/zero_256.bin <HOSTLIST> --newFor the HTTPS protocol:
--filter-tcp=443 --dpi-desync=fake,multidisorder --dpi-desync-split-pos=method+2,midsld,5 --dpi-desync-ttl=0 --dpi-desync-fooling=badsum,badseq --dpi-desync-repeats=1 --dpi-desync-any-protocol --dpi-desync-cutoff=d4 --dpi-desync-fake-tls=/opt/zapret/files/fake/zero_256.bin -dpi-desync-fake-tls-mod=none --dpi-desync-fake-tls=/opt/zapret/files/fake/tls_clienthello_www_google_com.bin --dpi-desync-fake-tls-mod=sni=www.google.com,rnd,dupsid <HOSTLIST> --newFor the QUIC protocol:
--filter-udp=443 --dpi-desync=fake --dpi-desync-repeats=1 --dpi-desync-ttl=1 --dpi-desync-any-protocol --dpi-desync-cutoff=d4 --dpi-desync-fooling=badsum,badseq --dpi-desync-fake-quic=/opt/zapret/files/fake/zero_256.bin <HOSTLIST> --newAnd for the disk to work, copy the 50-discord-media script:
cp -a /opt/zapret/init.d/custom.d.examples.linux/50-discord-media /opt/zapret/init.d/sysv/custom.d/50-discord-mediaFor WhatsApp/Telegram voice traffic to work, copy the 50-stun4all script:
cp -a /opt/zapret/init.d/custom.d.examples.linux/50-stun4all /opt/zapret/init.d/sysv/custom.d/50-stun4allAlso in the config, change the name of the external interface (WAN) in
IFACE_WAN. You can also specify several used WAN interfaces separated by a space, for example -IFACE_WAN="eth3 eth2".Traffic is redirected to nfqws always after routing, so only WAN filtering is applicable to it, making LAN irrelevant in this mode of operation.
My ready-made config (provided for informational purposes only, your config is generated by the
install_easy.shscript according to your parameters, and you only need to change the fields mentioned above in it):# this file is included from init scripts # change values here # can help in case /tmp has not enough space #TMPDIR=/opt/zapret/tmp # redefine user for zapret daemons. required on Keenetic WS_USER=nobody # override firewall type : iptables,nftables,ipfw FWTYPE=iptables # nftables only : set this to 0 to use pre-nat mode. default is post-nat. # pre-nat mode disables some bypass techniques for forwarded traffic but allows to see client IP addresses in debug log #POSTNAT=0 # options for ipsets # maximum number of elements in sets. also used for nft sets SET_MAXELEM=522288 # too low hashsize can cause memory allocation errors on low RAM systems , even if RAM is enough # too large hashsize will waste lots of RAM IPSET_OPT="hashsize 262144 maxelem $SET_MAXELEM" # dynamically generate additional ip. $1 = ipset/nfset/table name #IPSET_HOOK="/etc/zapret.ipset.hook" # options for ip2net. "-4" or "-6" auto added by ipset create script IP2NET_OPT4="--prefix-length=22-30 --v4-threshold=3/4" IP2NET_OPT6="--prefix-length=56-64 --v6-threshold=5" # options for auto hostlist AUTOHOSTLIST_RETRANS_THRESHOLD=3 AUTOHOSTLIST_FAIL_THRESHOLD=3 AUTOHOSTLIST_FAIL_TIME=60 # 1 = debug autohostlist positives to ipset/zapret-hosts-auto-debug.log AUTOHOSTLIST_DEBUGLOG=0 # number of parallel threads for domain list resolves MDIG_THREADS=30 # ipset/*.sh can compress large lists GZIP_LISTS=1 # command to reload ip/host lists after update # comment or leave empty for auto backend selection : ipset or ipfw if present # on BSD systems with PF no auto reloading happens. you must provide your own command # set to "-" to disable reload #LISTS_RELOAD="pfctl -f /etc/pf.conf" # mark bit used by nfqws to prevent loop DESYNC_MARK=0x40000000 DESYNC_MARK_POSTNAT=0x20000000 TPWS_SOCKS_ENABLE=0 # tpws socks listens on this port on localhost and LAN interfaces TPPORT_SOCKS=987 # use <HOSTLIST> and <HOSTLIST_NOAUTO> placeholders to engage standard hostlists and autohostlist in ipset dir # hostlist markers are replaced to empty string if MODE_FILTER does not satisfy # <HOSTLIST_NOAUTO> appends ipset/zapret-hosts-auto.txt as normal list TPWS_SOCKS_OPT=" --filter-tcp=80 --methodeol <HOSTLIST> --new --filter-tcp=443 --split-pos=1,midsld --disorder <HOSTLIST> " TPWS_ENABLE=0 TPWS_PORTS=80,443 # use <HOSTLIST> and <HOSTLIST_NOAUTO> placeholders to engage standard hostlists and autohostlist in ipset dir # hostlist markers are replaced to empty string if MODE_FILTER does not satisfy # <HOSTLIST_NOAUTO> appends ipset/zapret-hosts-auto.txt as normal list TPWS_OPT=" --filter-tcp=80 --methodeol <HOSTLIST> --new --filter-tcp=443 --split-pos=1,midsld --disorder <HOSTLIST> " NFQWS_ENABLE=1 # redirect outgoing traffic with connbytes limiter applied in both directions. NFQWS_PORTS_TCP=80,443 NFQWS_PORTS_UDP=443 # PKT_OUT means connbytes dir original # PKT_IN means connbytes dir reply # this is --dpi-desync-cutoff=nX kernel mode implementation for linux. it saves a lot of CPU. 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 # redirect outgoing traffic without connbytes limiter and incoming with connbytes limiter # normally it's needed only for stateless DPI that matches every packet in a single TCP session # typical example are plain HTTP keep alives # this mode can be very CPU consuming. enable with care ! #NFQWS_PORTS_TCP_KEEPALIVE=80 #NFQWS_PORTS_UDP_KEEPALIVE= # use <HOSTLIST> and <HOSTLIST_NOAUTO> placeholders to engage standard hostlists and autohostlist in ipset dir # hostlist markers are replaced to empty string if MODE_FILTER does not satisfy # <HOSTLIST_NOAUTO> appends ipset/zapret-hosts-auto.txt as normal list NFQWS_OPT=" --filter-tcp=80 --dpi-desync=fake,multisplit --dpi-desync-ttl=0 --dpi-desync-fooling=badsum,badseq --dpi-desync-fake-http=/opt/zapret/files/fake/zero_256.bin <HOSTLIST> --new --filter-tcp=443 --dpi-desync=fake,multidisorder --dpi-desync-split-pos=method+2,midsld,5 --dpi-desync-ttl=0 --dpi-desync-fooling=badsum,badseq --dpi-desync-repeats=1 --dpi-desync-any-protocol --dpi-desync-cutoff=d4 --dpi-desync-fake-tls=/opt/zapret/files/fake/zero_256.bin -dpi-desync-fake-tls-mod=none --dpi-desync-fake-tls=/opt/zapret/files/fake/tls_clienthello_www_google_com.bin --dpi-desync-fake-tls-mod=sni=www.google.com,rnd,dupsid <HOSTLIST> --new --filter-udp=443 --dpi-desync=fake --dpi-desync-repeats=1 --dpi-desync-ttl=0 --dpi-desync-any-protocol --dpi-desync-cutoff=d4 --dpi-desync-fooling=badsum,badseq --dpi-desync-fake-quic=/opt/zapret/files/fake/zero_256.bin <HOSTLIST> " # none,ipset,hostlist,autohostlist MODE_FILTER=none # donttouch,none,software,hardware FLOWOFFLOAD=donttouch # openwrt: specify networks to be treated as LAN. default is "lan" #OPENWRT_LAN="lan lan2 lan3" # openwrt: specify networks to be treated as WAN. default wans are interfaces with default route #OPENWRT_WAN4="wan vpn" #OPENWRT_WAN6="wan6 vpn6" # for routers based on desktop linux and macos. has no effect in openwrt. # CHOOSE LAN and optinally WAN/WAN6 NETWORK INTERFACES # or leave them commented if its not router # it's possible to specify multiple interfaces like this : IFACE_LAN="eth0 eth1 eth2" # if IFACE_WAN6 is not defined it take the value of IFACE_WAN IFACE_LAN=br0 IFACE_WAN=eth3 #IFACE_WAN6="ipsec0 wireguard0 he_net" # should start/stop command of init scripts apply firewall rules ? # not applicable to openwrt with firewall3+iptables INIT_APPLY_FW=1 # firewall apply hooks #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" # do not work with ipv4 #DISABLE_IPV4=1 # do not work with ipv6 DISABLE_IPV6=0 # drop icmp time exceeded messages for nfqws tampered connections # in POSTNAT mode this can interfere with default mtr/traceroute in tcp or udp mode. use source port not redirected to nfqws # set to 0 if you are not expecting connection breakage due to icmp in response to TCP SYN or UDP FILTER_TTL_EXPIRED_ICMP=1 # select which init script will be used to get ip or host list # possible values : get_user.sh get_antizapret.sh get_combined.sh get_reestr.sh get_hostlist.sh # comment if not required #GETLIST=This completes the router setup, and you can delete (using the same method you used to upload the archive to the router) the tmp folder from the router's USB drive, as well as the
zapret-v71.4folder and thezapret-v71.4.tar.gzarchive (the specific names depend on the release).Now you can start NFQWS and check its functionality with the command:
/opt/zapret/init.d/sysv/zapret startTo restart NFQWS, use the command:
/opt/zapret/init.d/sysv/zapret restartTo stop NFQWS, use the command:
/opt/zapret/init.d/sysv/zapret stopAlso, to increase security and prevent the possibility of DNS traffic interception, it is recommended to configure DNS over TLS and/or DNS over HTTPS protocols on the router according to the instructions - DNS-over-TLS and DNS-over-HTTPS proxy servers for encrypting DNS queries:

Configuring DNS over TLS and/or DNS over HTTPS In case of problems with accessibility or access speed to certain sites, change the
в --filter-tcp=80and/or--filter-tcp=443and/or--filter-udp=443parameters, restart the NFQWS utility with the command and check again, and repeat this cycle until you get the desired result. If something that used to work breaks, it is recommended to first try changing thedpi-desync-ttlparameters in the config: start from 1 and increase by steps of 1, checking that it gets through unavailable connections but doesn't break working ones. With dpi-desync-ttl=0 (default), the fake packets will have the original TTL, in which case they are guaranteed to pass through limiters, but they might also break connections on some servers if they are not discarded as incorrect, considering thedpi-desync-foolingparameters. If you can't find a solution where everything works and nothing important breaks, use thehostlistfiltering mode.Since Zapret on GitHub is periodically updated by the author and new releases come out, it's a good idea to update your own installation from time to time. To do this, you will have to repeat everything from step 3 to step 16 of the instructions, with the exception of steps 11-13 (they are not necessary).
But first, you need to stop Zapret with the command:
/opt/zapret/init.d/sysv/zapret stopAnd delete the
zapretfolder from the drive with the command:rm -r /opt/zapret/