This article show you the ultimate way to set up a transparent proxy on Linux using clash and iptables to bypass the GFW in China. We use: * [clash](https://github.com/Dreamacro/clash) as the proxy software * [online subconverter](https://github.com/tindy2013/subconverter) and crontab to periodically update config file. * [ACL4SSR](https://github.com/ACL4SSR/ACL4SSR/tree/master) to provide GFW rules. You can go to [github gist](https://gist.github.com/cld4h/9a03ec2f826a25be5ab974fdbc540de4) to download all files mentioned in this article. All files you need: ```txt . โโโ clash-base-config.yaml ---๐ขbase config for clash to work on tproxy and fake-ip mode โโโ clash.service ---๐ขsystemd unit file to start up clash โโโ clean.sh ---๐ขscript to clean iptables โโโ config.ini ---๐ขconfig file for subconverter โโโ iptables.sh ---๐ขiptables config file โโโ update-config.sh ---๐กsubscription update script; "XXXXXXXX" need to be replaced โโโ config.yaml ---โญclash config file, to be generated from update-config.sh โโโ README.md โโโ README.zh-cn.md ๐ข: Doesn't need to change ๐ก: Needs to change โญ: Needs to be generated ``` ## Reference [clash-tproxy](https://www.sobyte.net/post/2022-02/clash-tproxy/) [Transparent proxy demo code](https://git.breakpoint.cc/cgit/fw/tcprdr.git) ## Prerequisite * a `clash` installed. (By default in `/usr/bin/clash`) * iptables * systemd (It's totally fine to use other init programs, but here I'll only show the configuration of systemd, which is the default of most GNU/Linux distros.) * crontab ## Steps ### Create a system user called clash ``` useradd -r -M -s /usr/sbin/nologin clash ``` Since we want to proxy all internet traffics, we need to prevent the traffic from looping. Thus, we create a distinct user called `clash` (the name can be arbitrary) to run the proxy software `clash`. We'll make an iptables rule in the following steps to distinguish traffic coming from the user `clash`. ### Create iptables and routing rules Create a shell script of iptables command called `/etc/clash/iptables.sh`. The file contains 3 main sections: 1. iptables rules to hijack the dns query traffic, 2. config for `clash` chain 3. config for `clash_local` chain iptables: ```txt โโโโโโโโโโโ โโโโโโโโโโโ โ โ PREROUTING INPUT โ โ โ โ โ โ โ โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโ โ โ โ โ โ โ /โโโ\ โ โ โ โ โ โ โ โโโโโ โโโโโโโโโโโ โโโโโโโโ โโโโโ โ / \ โ โโโโโโโโ โโโโโโโโ โ โ โ โ โโโโคโบโrawโโโบโconntrackโโโบโmangleโโโบโnatโโโผโโโโโโโบ<routing>โโโโโโโคโบโmangleโโโบโfilterโโโคโบโ โ โ โ โ โโโโโ โโโโโโโโโโโ โโโโโโโโ โโโโโ โ \ / โ โโโโโโโโ โโโโโโโโ โ โ โ โ โ โ โ \โโฌโ/ โ โ โ โ โ โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ โโโโโโโโโโโโโโโโโโโโโโ โ โ โ โ โ โ โ โ โ โโโโโโโโโโโโ โ โ โ โ โ โ โ โ โ โ โ โ โ โโโโโโโโ โ โ โ โ โ โ โ โmangleโโโผโโโโโโโโโโ โ โ โ Network โ โ โโโโฌโโโโ โ โ Local โ โInterfaceโ โ โ โ FORWARD โ Process โ โ โ โ โโโโผโโโโ โ /โโโ\ โ โ โ โ โโโโโโโโโโโโโโโโโผโโคfilterโ โ / \ โ โ โ โ โ โ โโโโโโโโ โ <routing>โโโค โ โ โ โ โ โ \ / โ โ โ โ โ โโโโโโโโโโโโ \โโฌโ/ โ โ โ โ โ โ โ โ โ โ โโโโโโโโโโโโโโผโโโโโ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโผโโโโโ โ โ โ โ โ โ โ โโโโโ โ โ โ โ โ โ โ โ โโโโโ โโโโโผโโโ โ / \ โ โโโโโโโโ โโโโโ โโโโโโโโ โโโโโโโโโโโ โโผโโโ โ โ โ โ โโโโผโโคnatโโโโคmangleโโโโโโ<reroute>โโโโผโโคfilterโโโโคnatโโโโคmangleโโโโคconntrackโโโโคrawโ โ โ โ โ โ โ โโโโโ โโโโโโโโ โ \check/ โ โโโโโโโโ โโโโโ โโโโโโโโ โโโโโโโโโโโ โโโโโ โ โ โ โ โ โ โ โโโโโ โ โ โ โ โ โ โโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ โ โ โ POSTROUTING OUTPUT โ โ โ โ โ โ โโโโโโโโโโโ โโโโโโโโโโโ ``` `/etc/clash/iptables.sh` ``` #!/usr/bin/env bash # set -ex options: # -e Exit immediately if a command exits with a non-zero status # -x Print commands and their arguments as they are executed. set -ex # ENABLE ipv4 forward sysctl -w net.ipv4.ip_forward=1 # ROUTE RULES ip rule add fwmark 666 table 666 ip route add local 0.0.0.0/0 table 666 dev lo ################################## ## hijack the dns query traffic ## ################################## # Redirect all dns query traffic from LAN to port 1053 # Later clash will return a fake ip in 198.18.0.1/16 iptables -t nat -I PREROUTING -p udp --dport 53 -j REDIRECT --to 1053 # same for ipv6 ip6tables -t nat -I PREROUTING -p udp --dport 53 -j REDIRECT --to 1053 # Redirect dns query traffic from all local processes (except owned by user clash) to port 1053 # Later clash will return a fake ip in 198.18.0.1/16 iptables -t nat -N clash_dns iptables -t nat -A OUTPUT -p udp --dport 53 -j clash_dns iptables -t nat -A clash_dns -m owner --uid-owner clash -j RETURN iptables -t nat -A clash_dns -p udp -j REDIRECT --to-ports 1053 # same for ipv6 ip6tables -t nat -N clash_dns ip6tables -t nat -A OUTPUT -p udp --dport 53 -j clash_dns ip6tables -t nat -A clash_dns -m owner --uid-owner clash -j RETURN ip6tables -t nat -A clash_dns -p udp -j REDIRECT --to-ports 1053 ############################## ## config for `clash` chain ## ############################## # `clash` chain for using tproxy to redirect traffic to the clash listening port 7893 iptables -t mangle -N clash # skip traffic to LAN or reserved address # reference: # https://en.wikipedia.org/wiki/List_of_assigned_/8_IPv4_address_blocks # https://en.wikipedia.org/wiki/Private_network#IPv4 # IANA - Local Identification iptables -t mangle -A clash -d 0.0.0.0/8 -j RETURN # IANA - Loopback iptables -t mangle -A clash -d 127.0.0.0/8 -j RETURN # IANA - Private Use iptables -t mangle -A clash -d 10.0.0.0/8 -j RETURN iptables -t mangle -A clash -d 172.16.0.0/12 -j RETURN iptables -t mangle -A clash -d 192.168.0.0/16 -j RETURN # IPv4 Link-Local Addresses iptables -t mangle -A clash -d 169.254.0.0/16 -j RETURN # Multicast iptables -t mangle -A clash -d 224.0.0.0/4 -j RETURN # Future Use iptables -t mangle -A clash -d 240.0.0.0/4 -j RETURN # Forward all the other traffic to port 7893 and set tproxy mark iptables -t mangle -A clash -p tcp -j TPROXY --on-port 7893 --tproxy-mark 666 iptables -t mangle -A clash -p udp -j TPROXY --on-port 7893 --tproxy-mark 666 # Append the `clash` chain to PREROUTING to enable it iptables -t mangle -A PREROUTING -j clash #################################### ## config for `clash_local` chain ## #################################### # `clash_local` chain to manipulate the traffic from local process: set fwmark 666 on traffic to public address # (which will later reroute to dev lo, then redirect to tproxy port 7893 on the `clash` chain of `PREROUTING`) iptables -t mangle -N clash_local # rerouting traffic from nerdctl container #iptables -t mangle -A clash_local -i nerdctl2 -p udp -j MARK --set-mark 666 #iptables -t mangle -A clash_local -i nerdctl2 -p tcp -j MARK --set-mark 666 # Don't touch traffic to LAN and reserved address iptables -t mangle -A clash_local -d 0.0.0.0/8 -j RETURN iptables -t mangle -A clash_local -d 127.0.0.0/8 -j RETURN iptables -t mangle -A clash_local -d 10.0.0.0/8 -j RETURN iptables -t mangle -A clash_local -d 172.16.0.0/12 -j RETURN iptables -t mangle -A clash_local -d 192.168.0.0/16 -j RETURN iptables -t mangle -A clash_local -d 169.254.0.0/16 -j RETURN iptables -t mangle -A clash_local -d 224.0.0.0/4 -j RETURN iptables -t mangle -A clash_local -d 240.0.0.0/4 -j RETURN # set mark for local traffic # clash_local set mark for the traffic from local process, # the marked traffic will come back to PREROUTING chain. iptables -t mangle -A clash_local -p tcp -j MARK --set-mark 666 iptables -t mangle -A clash_local -p udp -j MARK --set-mark 666 # Don't touch traffic from a serving port # https web server # iptables -t mangle -I OUTPUT -p tcp --sport 443 -j RETURN # transmission # iptables -t mangle -I OUTPUT -p tcp --sport 51413 -j RETURN # Don't touch traffic from the user `clash` to prevent looping iptables -t mangle -A OUTPUT -p tcp -m owner --uid-owner clash -j RETURN iptables -t mangle -A OUTPUT -p udp -m owner --uid-owner clash -j RETURN # Append the `clash_local` chain to OUTPUT to enable it iptables -t mangle -A OUTPUT -j clash_local ####################### ## unimportant staff ## ####################### # fix ICMP(ping) # This step does NOT make the ping result validate # (clash doesn't support forwarding ICMP), it just makes ping have a result. # set --to-destination to a accessable address. #sysctl -w net.ipv4.conf.all.route_localnet=1 #iptables -t nat -A PREROUTING -p icmp -d 198.18.0.0/16 -j DNAT --to-destination 127.0.0.1 #iptables -t nat -A OUTPUT -p icmp -d 198.18.0.0/16 -j DNAT --to-destination 127.0.0.1 ``` After applying the script above, the system iptables should be like this: ```txt โโโโโโโโโโโ โโโโโโโโโโโ โ โ PREROUTING rule1* INPUT โ โ โ โ โ โ โ โ โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโผโโโโ โโโโโโโโโโโโโโโโโโโโโโ โ โ โ โ โ โ โ /โโโ\ โ โ โ โ โ โ โ โโโโโ โโโโโโโโโโโ โโโโโโโโ โโโดโโ โ / \ โ โโโโโโโโ โโโโโโโโ โ โ โ โ โโโโคโบโrawโโโบโconntrackโโโบโmangleโโโบโnatโโโผโโโโโโโบ<routing>โโโโโโโคโบโmangleโโโบโfilterโโโคโบโ โ โ โ โ โโโโโ โโโโโโโโโโโ โโฌโโโโโโ โโโโโ โ \ / โ โโโโโโโโ โโโโโโโโ โ โ โ โ โ โ โ โ \โโฌโ/ โ โ โ โ โ โ โโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโ โ โโโโโโโโโโโโโโโโโโโโโโ โ โ โ โ โ โ โ โ โ โ โโโโดโโโ โโโโโโโโโโโโ โ โ โ โ โ โclashโ โ โ โ โ โ โ โ โโโโโโโ โ โโโโโโโโ โ โ โ โ โ โ โ โmangleโโโผโโโโโโโโโโ โ โ โ Network โ โ โโโโฌโโโโ โ โโโโโโโโโโโโโ โ Local โ โInterfaceโ โ โ โ FORWARD โclash_localโ โ Process โ โ โ โ โโโโผโโโโ โ โโโฌโโโโโโโโโโ /โโโ\ โ โ โ โ โโโโโโโโโโโโโโโโโผโโคfilterโ โ โ / \ โ โ โ โ โ โ โโโโโโโโ โ โโโโrule3* <routing>โโโค โ โ โ โ โ โ โ \ / โ โ โ โ โ โโโโโโโโโโโโ โโโโrule2* \โโฌโ/ โ โ โ โ โ โ โ โ โ โ โ โโโโโโโโโโโโโโผโโโโโ โโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโผโโโโโ โ โ โ โ โ โ โ โโโโโ โ โ โ โ โ โ โ โ โ โโโโโ โโโโโผโโโ โ / \ โ โโโโโโโโ โโโโโ โโโโโดโโโ โโโโโโโโโโโ โโผโโโ โ โ โ โ โโโโผโโคnatโโโโคmangleโโโโโโ<reroute>โโโโผโโคfilterโโโโคnatโโโโคmangleโโโโคconntrackโโโโคrawโ โ โ โ โ โ โ โโโโโ โโโโโโโโ โ \check/ โ โโโโโโโโ โโฌโโโ โโโโโโโโ โโโโโโโโโโโ โโโโโ โ โ โ โ โ โ โ โโโโโ โ โ โ โ โ โ โ โโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ โ โ โ POSTROUTING โ OUTPUT โ โ โ โ โโโโโโโโดโโโ โ โ โโโโโโโโโโโ โclash_dnsโ โโโโโโโโโโโ โโโโโโโโโโโ *rule1: iptables -t nat -I PREROUTING -p udp --dport 53 -j REDIRECT --to 1053 *rule2: iptables -t mangle -A OUTPUT -p tcp -m owner --uid-owner clash -j RETURN *rule3: iptables -t mangle -A OUTPUT -p udp -m owner --uid-owner clash -j RETURN ``` It's also worth to take a look at the route rules in `iptables.sh`: ```sh ip rule add fwmark 666 table 666 ip route add local 0.0.0.0/0 table 666 dev lo ``` `ip rule` manipulates rules in the routing policy database control the route selection algorithm. So here the `ip rule` command make the kernel to lookup table 666 for packets with fwmark 666 [ip rule reference is here.](http://linux-ip.net/html/tools-ip-rule.html) The `ip route` command add a default route to device lo on routing table 666. We can use `ip route show table 666` to see the route added by `ip route add local 0.0.0.0/0 table 666 dev lo ` See the route table value and name mapping: `cat /etc/iproute2/rt_tables` ### Create a cleaning script Create a shell script to clean the iptables. `-D` or `--delete`: Delete one or more rules from the selected chain. There are two versions of this command: the rule can be specified as a number in the chain (starting at 1 for the first rule) or a rule to match. `-F` or `--flush`: Flush the selected chain (all the chains in the table if none is given). This is equivalent to deleting all the rules one by one. `-X` or `--delete-chain`: Delete the optional user-defined chain specified. There must be no references to the chain. If there are, you must delete or replace the referring rules before the chain can be deleted. The chain must be empty, i.e. not contain any rules. If no argument is given, it will attempt to delete every non-builtin chain in the table. ```sh #!/usr/bin/env bash set -ex ip rule del fwmark 666 table 666 || true ip route del local 0.0.0.0/0 table 666 dev lo || true iptables -t nat -D PREROUTING -p udp --dport 53 -j REDIRECT --to 1053 || true ip6tables -t nat -D PREROUTING -p udp --dport 53 -j REDIRECT --to 1053 || true iptables -t nat -D OUTPUT -p udp --dport 53 -j clash_dns || true ip6tables -t nat -D OUTPUT -p udp --dport 53 -j clash_dns || true iptables -t nat -F clash_dns || true iptables -t nat -X clash_dns || true ip6tables -t nat -F clash_dns || true ip6tables -t nat -X clash_dns || true #iptables -t nat -D PREROUTING -p icmp -d 198.18.0.0/16 -j DNAT --to-destination 127.0.0.1 #iptables -t nat -D OUTPUT -p icmp -d 198.18.0.0/16 -j DNAT --to-destination 127.0.0.1 iptables -t mangle -D PREROUTING -j clash || true iptables -t mangle -F clash || true iptables -t mangle -X clash || true iptables -t mangle -D OUTPUT -p tcp -m owner --uid-owner clash -j RETURN || true iptables -t mangle -D OUTPUT -p udp -m owner --uid-owner clash -j RETURN || true iptables -t mangle -D OUTPUT -j clash_local || true iptables -t mangle -F clash_local || true iptables -t mangle -X clash_local || true ``` ### Fixing ping One major drawback of the fake-ip mode is that you can't ping a fake ip. The solution above to redirect all ping request to the localhost is "ๆฉ่ณ็้", it doesn't provide any useful information. Sadly, all devices from LAN can't use ping, but fortunately you can alias ping to `sudo -u clash ping` for this gateway to use ping. ```sh alias ping="sudo -u clash ping" ``` ### Provide a systemd unit file to start clash at a single command This step is optional, but I felt it's extremely convenient to start clash by running a single command. You can also make clash start at boot time. The systemd config file is in the appended files with the name `clash.service` put it under `/usr/lib/systemd/system`, and run `sudo systemctl start clash` to start running clash. To start it at boot time, run `sudo systemctl enable clash`. ### Configuring clash Now let's set clash up to work. Clash use `-d` option to set configuration directory and `-f` option to specify configuration file. The default configuration file name is `config.yaml` when `-f` is omitted. We want to use `crontab` to periodically update the clash config from a subscription url. #### Construct the subscription URL TL;DR. Just replace `XXXXXXXX` with your [encoded](https://www.urlencoder.io/) subscription URL . ``` https://sub.xeton.dev/sub?target=clash&new_name=true&filename=config.yaml&url=XXXXXXXX&config=https%3A%2F%2Fgist.githubusercontent.com%2Fcld4h%2F9a03ec2f826a25be5ab974fdbc540de4%2Fraw%2Fconfig.ini ``` We use [subconverter](https://github.com/tindy2013/subconverter) to generate config file for clash. You need to change a few parameters in the subscription URL to generate a proper config file for clash to work in fake-ip mode. To be specific: 1. the `url` parameter should be the correct subscription URL and [encoded](https://www.urlencoder.io/). 2. Multiple subscription URL should be seperated by `|`, or `%7C` after [URL Encoding](https://www.urlencoder.io/). 3. the `config` parameter should point to [a correct configuration file](https://gist.githubusercontent.com/cld4h/9a03ec2f826a25be5ab974fdbc540de4/raw/config.ini). โ ๏ธ โ ๏ธ NOTICE: the configuration file for subconverter is extremely important! [ACL4SSR project provide a default configuration file for subconverter](https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/config/ACL4SSR_Online.ini ), it's fine but NOT ENOUGH for clash to work on fake-ip mode. To make it work, you need specify `clash_rule_base` by inserting this line to [the configuration file for **subconverter**](https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/config/ACL4SSR_Online.ini) ``` clash_rule_base=https://gist.githubusercontent.com/cld4h/9a03ec2f826a25be5ab974fdbc540de4/raw/clash-base-config.yaml ``` What this line does is that it tells the subconverter to use [the file in the url above](https://gist.githubusercontent.com/cld4h/9a03ec2f826a25be5ab974fdbc540de4/raw/clash-base-config.yaml) instead of the default clash base configuration. Or you can just use [the config.ini here](https://gist.githubusercontent.com/cld4h/9a03ec2f826a25be5ab974fdbc540de4/raw/config.ini) Sample url: ```txt https://sub.xeton.dev/sub? --- backend address target=clash --- client type &new_name=true --- create a new filename &filename=config.yaml --- filename &url= --- Node info ss%3A%2F%2FYmYtY2ZiOnRlc3QvIUAjOkAxOTIuMTY4LjEwMC4xOjg4ODg%23example-server %7C --- the delimiter | to seperate multiple urls ss%3A%2F%2FYmYtY2ZiOnRlc3QvIUAjOkAxOTIuMTY4LjEwMC4xOjg4ODg%23example-server-2 &config= --- config file for subconverter; .ini format https%3A%2F%2Fgist.githubusercontent.com%2Fcld4h%2F9a03ec2f826a25be5ab974fdbc540de4%2Fraw%2Fconfig.ini --- options below is unnecessary and can be omitted &list=false --- ็จไบ่พๅบ Surge Node List ๆ่ Clash Proxy Provider ๆ่ Quantumult (X) ็่็น่ฎข้ ๆ่ ่งฃ็ ๅ็ SIP002 &tfo=false --- ็จไบๅผๅฏ่ฏฅ่ฎข้ ้พๆฅ็ TCP Fast Open๏ผ้ป่ฎคไธบ false &scv=false --- ็จไบๅ ณ้ญ TLS ่็น็่ฏไนฆๆฃๆฅ๏ผ้ป่ฎคไธบ false &fdn=false --- ็จไบ่ฟๆปค็ฎๆ ็ฑปๅไธๆฏๆ็่็น๏ผ้ป่ฎคไธบ true &sort=false --- ็จไบๅฏน่พๅบ็่็นๆ็ญ็ฅ็ปๆ่็นๅ่ฟ่กๅๆฌกๆๅบ๏ผ้ป่ฎคไธบ false ``` #### Set up a cronjob Create a shell script `/etc/clash/update-config.sh` with the following content: ```sh #!/usr/bin/env bash set -ex /usr/bin/curl "https://sub.xeton.dev/sub?target=clash&new_name=true&filename=config.yaml&url=XXXXXXXX&config=https%3A%2F%2Fgist.githubusercontent.com%2Fcld4h%2F9a03ec2f826a25be5ab974fdbc540de4%2Fraw%2Fconfig.ini" -o /etc/clash/config.yaml ``` โ ๏ธ The URL should be replace. Manually executing it to make sure the config file is generated successfully. Make sure you have the following contents in your `config.yaml` file for clash: ```yaml tproxy-port: 7893 dns: enable: true listen: 0.0.0.0:1053 enhanced-mode: fake-ip fake-ip-range: 198.18.0.1/16 ``` Add a [cronjob](https://crontab.guru) by executing `sudo crontab -e` and add this line to executing the script at 10:00 AM every day. ```sh 0 10 * * * /usr/bin/bash /etc/clash/update-config.sh ```