Skip to content

Use Crash and Iptables to implement transparent proxy in Ubuntu22

In the process of developing software on the Ubuntu system, it is often necessary to use the system’s command line tools. Many of these tools ignore system proxy settings and connect directly to the Internet, resulting in poor network quality. This article introduces the use of iptables to make all traffic go through the proxy.

Create a clash system account

sudo adduser --shell /usr/sbin/nologin clash

Configure crash system service

Creat a service at /etc/systemd/system/clash.service, add following settings.

[Unit]
Description=Clash daemon, A rule-based proxy in Go.
After=network.target

[Service]
Type=simple
Restart=always
User=clash
Group=clash
ExecStart=/path_to_your_clash_folder/clash-linux-amd64 -d /etc/clash

[Install]
WantedBy=multi-user.target

[Unit]
Description=Clash daemon, A rule-based proxy in Go.
After=network.target

[Service]
Type=simple
Restart=always
User=clash
Group=clash
CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_BIND_SERVICE CAP_NET_RAW
AmbientCapabilities=CAP_NET_ADMIN CAP_NET_BIND_SERVICE CAP_NET_RAW
ExecStart=/path_to_your_clash_folder/clash-linux-amd64 -d /etc/clash

[Install]
WantedBy=multi-user.target

Configure the ExecStart above to your real clash excutable path and configuration folder.

Configure iptables

Create a bash script named iptables-tproxy.sh to enable and disable transparent proxy.

#!/bin/bash

remove_iptables_proxy_setting(){
    # clear old settings
    sudo ip rule del fwmark 666 table 666 2>/dev/null
    sudo ip route del local 0.0.0.0/0 dev lo table 666 2>/dev/null
    sudo iptables -t nat -F
    sudo iptables -t nat -X
    sudo iptables -t mangle -F 
    sudo iptables -t mangle -X  
}

add_iptables_proxy_setting(){
    # ENABLE ipv4 forward
    sudo sysctl -w net.ipv4.ip_forward=1 > /dev/null

    # ROUTE RULES
    sudo ip rule add fwmark 666 lookup 666
    sudo ip route add local 0.0.0.0/0 dev lo table 666


    # create clash chain for traffic forwarding
    sudo iptables -t mangle -N clash

    # skip LAN addresses
    sudo iptables -t mangle -A clash -d 0.0.0.0/8 -j RETURN
    sudo iptables -t mangle -A clash -d 127.0.0.0/8 -j RETURN
    sudo iptables -t mangle -A clash -d 10.0.0.0/8 -j RETURN
    sudo iptables -t mangle -A clash -d 172.16.0.0/12 -j RETURN
    sudo iptables -t mangle -A clash -d 192.168.0.0/16 -j RETURN
    sudo iptables -t mangle -A clash -d 169.254.0.0/16 -j RETURN
    sudo iptables -t mangle -A clash -d 224.0.0.0/4 -j RETURN
    sudo iptables -t mangle -A clash -d 240.0.0.0/4 -j RETURN

    # all other traffic is redirected to port 7893 and marked with 666
    sudo iptables -t mangle -A clash -p tcp -j TPROXY --on-port 7893 --tproxy-mark 666
    sudo iptables -t mangle -A clash -p udp -j TPROXY --on-port 7893 --tproxy-mark 666

    # forward all DNS queries to port 1053
    sudo iptables -t nat -I PREROUTING -p udp --dport 53 -j REDIRECT --to 1053

    # finally, let all traffic be processed through the clash chain
    sudo iptables -t mangle -A PREROUTING -j clash


    # create clash_local chain, this chian is responsible for processing traffic sent by the gateway itself
    sudo iptables -t mangle -N clash_local

    # skip LAN addresses
    sudo iptables -t mangle -A clash_local -d 0.0.0.0/8 -j RETURN
    sudo iptables -t mangle -A clash_local -d 127.0.0.0/8 -j RETURN
    sudo iptables -t mangle -A clash_local -d 10.0.0.0/8 -j RETURN
    sudo iptables -t mangle -A clash_local -d 172.16.0.0/12 -j RETURN
    sudo iptables -t mangle -A clash_local -d 192.168.0.0/16 -j RETURN
    sudo iptables -t mangle -A clash_local -d 169.254.0.0/16 -j RETURN
    sudo iptables -t mangle -A clash_local -d 224.0.0.0/4 -j RETURN
    sudo iptables -t mangle -A clash_local -d 240.0.0.0/4 -j RETURN

    # mark all tranfic sent by localhost with 666
    sudo iptables -t mangle -A clash_local -p tcp -j MARK --set-mark 666
    sudo iptables -t mangle -A clash_local -p udp -j MARK --set-mark 666

    # skip the traffic sent by the clash program itself to prevent an infinite loop (the clash program needs to be started by the "clash" user)
    sudo iptables -t mangle -A OUTPUT -p tcp -m owner --uid-owner clash -j RETURN
    sudo iptables -t mangle -A OUTPUT -p udp -m owner --uid-owner clash -j RETURN

    # let the traffic from the local machine jump to clash_local
    # the clash_local chain marked the traffic sent by localhost, and the marked traffic will return to PREROUTING
    sudo iptables -t mangle -A OUTPUT -j clash_local

    # forward all localhost DNS queries to port 1053
    sudo iptables -t nat -A OUTPUT -p udp -m owner ! --uid-owner clash --dport 53 -j REDIRECT --to 1053

    # fix ICMP PING(optional)
    # this does not guarantee the validity of the ping result (clash doesn't support ICMP forwarding), it just makes it return a result
    # you can uncomment following 2 lines and set --to-destination with a reachable address
    # sysctl -w net.ipv4.conf.all.route_localnet=1
    # sudo iptables -t nat -A PREROUTING -p icmp -d 198.18.0.0/16 -j DNAT --to-destination 127.0.0.1
}


# Check for the argument and call the appropriate function
if [[ $1 == "on" ]]; then
    remove_iptables_proxy_setting
    add_iptables_proxy_setting
elif [[ $1 == "off" ]]; then
    remove_iptables_proxy_setting
else
    echo "Usage: ./iptables-tproxy.sh <on|off>"
fi

Excute iptables-tproxy.sh on to enable transparent proxy and iptables-tproxy.sh off to disable transparent proxy.

Leave a Reply