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:

  1. 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.

  2. 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;

  3. 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).

Модули ядра подсистемы Netfilter
Netfilter subsystem kernel modules
Протокол IPv6
IPv6 protocol

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:

  1. 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's eth3.

  2. Install the packages using the command:

    opkg update

    opkg install coreutils-sort curl grep gzip ipset iptables kmod_ndms xtables-addons_legacy

  3. Download 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).

  4. Navigate to the tmp folder on the router:

    cd /opt/tmp

  5. Unpack the downloaded Zapret archive using the command:

    tar xvzf zapret-v71.4.tar.gz

    Where zapret-v71.4.tar.gz is replaced with the name of your downloaded archive (it changes with each release).

  6. After the archive is unpacked, navigate to the extracted folder:

    cd ./zapret-v71.4

    Where zapret-v71.4 is replaced with the name of your folder extracted from the archive (it changes with each release).

  7. Run the installation script:
    ./install_easy.sh

    At the beginning of the installation, this script will automatically copy all files from the zapret-v71.4folder to /opt/zapret/.

  8. Next, answer all the questions as shown in the screenshot:

    Выполнение скрипта install_easy.sh
    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 using ipset. 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_FILTER parameter in /opt/zapret/config.

    Next, make your choice:

    Завершение работы скрипта install_easy.sh
    Finishing the install_easy.sh script
  9. 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/zapret

    Add 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, iptables will produce errors when trying to apply the necessary rules.

    The final zapret script 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 0

    This completes the editing of the zapret script. Save and exit the text editor.

  10. 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-fix

  11. After saving the zapret file, create a link to the zapret script in the startup directory:

    ln -fs /opt/zapret/init.d/sysv/zapret /opt/etc/init.d/S90-zapret

  12. Next, you need to create a script so that the netfilter firewall 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.sh

    Paste the following code into the file:

    #!/bin/sh
    [ "$table" != "mangle" ] && [ "$table" != "nat" ] && exit 0
    /opt/zapret/init.d/sysv/zapret restart-fw
    exit 0

    And grant execute permission to the 000-zapret.sh script using the command:

    chmod +x /opt/etc/ndm/netfilter.d/000-zapret.sh

    This script will call our previously edited zapret script with the restart-fw, parameter, where we defined the iptables rules.

  13. Similarly, create a script to disable packet checksum verification:

    vi /opt/etc/init.d/S00fix

    Paste 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 0

    Also, don't forget to grant execute permissions to this script:

    chmod +x /opt/etc/init.d/S00fix

  14. And 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/config

  15. You need to make the following changes:

    Uncomment the line

    WS_USER=nobody

    Next, 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> --new

    For 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> --new

    For 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> --new

    And 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-media

    For 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-stun4all

    Also in the config, change the name of the external interface (WAN) inIFACE_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.sh script 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.4 folder and the zapret-v71.4.tar.gz archive (the specific names depend on the release).

  16. Now you can start NFQWS and check its functionality with the command:

    /opt/zapret/init.d/sysv/zapret start

    To restart NFQWS, use the command:

    /opt/zapret/init.d/sysv/zapret restart

    To stop NFQWS, use the command:

    /opt/zapret/init.d/sysv/zapret stop

  17. Also, 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:

    Настройка DNS over TLS и/или DNS over HTTPS
    Configuring DNS over TLS and/or DNS over HTTPS
  18. In case of problems with accessibility or access speed to certain sites, change the в --filter-tcp=80 and/or --filter-tcp=443 and/or --filter-udp=443 parameters, 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 the dpi-desync-ttl parameters 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 the dpi-desync-fooling parameters. If you can't find a solution where everything works and nothing important breaks, use the hostlist filtering mode.

  19. 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 stop

    And delete the zapret folder from the drive with the command:
    rm -r /opt/zapret/