Running Clash on OpenWrt as a transparent proxy

An up-to-date version of the post can be found here: Layer 7 Policy Routing With Clash

As you would have been aware of that I live in China where internet is under strict censorship. I’ve been discovering ways to access the blocked internet resources.

So recently I switched to a x86 mini computer that runs Proxmox VE, which has an OpenWRT VM running as a router. In this blog post, I'm using Clash, a new software that is quite the same to Surge. They both support "rules" mode that routes internet traffic on your will. It’s so convenient that you don’t have to use gfwlist anymore, and they are more precise and customizable, like you can route Google to a Hong Kong proxy, YouTube to United States, Netflix to Japan and so forth. Clash also has a redir mode which can be used with iptables to redirect the TCP packets. We're also gonna utilize Unbound and DNSCrypt-proxy to solve the DNS pollution issue.

Download the tools #

mkdir /etc/clash
cd /etc/clash
tar -xzvf clash-linux-amd64.tar.gz
mv clash-linux-amd64 clash
mkdir /etc/unbound
opkg update && opkg install unbound luci-app-unbound

Follow this guide to install DNSCrypt-proxy:

Make them into services #

Put the following script to /etc/init.d/clash:

#!/bin/sh /etc/rc.common



start_service() {
procd_set_param command /etc/clash/clash -d /etc/clash
procd_set_param respawn 300 0 5 # threshold, timeout, retry
procd_set_param file /etc/clash/config.yml
procd_set_param stdout 1
procd_set_param stderr 1
procd_set_param pidfile /var/run/

Then run the following to make it executable:

chmod +x /etc/init.d/clash

This init.d script also immediately restarts clash when it exits for whatever reason. If it crashes 5 times in 5 minutes, it won’t be restarted anymore.

The logs are in /var/log/messages.

Write Clash Configuration #

I'm not covering how to write Clash configuration in this blog post, but these options must be set as follows:

redir-port: 9090
allow-lan: true
enable: true
ipv6: false
enhanced-mode: redir-host

Let’s tear it down. 9090 is the redir port, allow-lan allows other devices in LAN to access the proxy and external-controller is the API that we’re gonna use later to control Clash.

Clash will now forward DNS requests from :53 to unbound (:5353), which forwards DNS requests to DNSCrypt-proxy (:5678). DNSCrypt-proxy will then securely get the correct DNS responses using DoH.

Configure dnsmasq #

# Disable dnsmasq DNS server
$ uci set 'dhcp.@dnsmasq[0].port=0'

# Configure dnsmasq to send a DNS Server DHCP option with its LAN IP
# since it does not do this by default when port is configured.
$ lan_address=$(uci get network.lan.ipaddr)
$ uci add_list "dhcp.lan.dhcp_option=option:dns-server,$lan_address"

$ uci commit
$ service dnsmasq restart

Configure DNSCrypt-proxy #

Use the example config with these options changed:

server_names = ['cloudflare', 'google']
listen_addresses = ['']
fallback_resolver = ''
ignore_system_dns = true
forwarding_rules = 'forwarding-rules.txt'

Configure Unbound #

First download named.cache from InterNIC:

wget ftp://FTP.INTERNIC.NET/domain/named.cache -O/etc/unbound/root.hints

Then enable manual config so we can configure Unbound directly using it’s config file:

uci set 'unbound.@unbound[0].manual_conf=1'

To make China domains solve through instead of foreign DNSCrypt-proxy, we’re using

git clone
cd dnsmasq-china-list
make SERVER= unbound
mkdir /etc/unbound/unbound.conf.d
cp accelerated-domains.china.unbound.conf /etc/unbound/unbound.conf.d

Finally, this is the config I’m currently using:

include: "/etc/unbound/unbound.conf.d/accelerated-domains.china.unbound.conf"

verbosity: 1
directory: "/etc/unbound"
num-threads: 2
msg-cache-slabs: 1
rrset-cache-slabs: 1
infra-cache-slabs: 1
key-cache-slabs: 1
access-control: allow
outgoing-num-tcp: 256
incoming-num-tcp: 1024
outgoing-port-permit: "10240-65335"
outgoing-range: 60
num-queries-per-thread: 30
msg-buffer-size: 8192
infra-cache-numhosts: 200
key-cache-size: 100k
neg-cache-size: 10k
target-fetch-policy: "2 1 0 0 0 0"
harden-large-queries: yes
harden-short-bufsize: yes
port: 5353
so-rcvbuf: 4m
so-sndbuf: 4m
so-reuseport: yes
msg-cache-size: 64m
rrset-cache-size: 128m
cache-max-ttl: 3600
do-ip4: yes
do-ip6: yes
do-udp: yes
do-tcp: yes
tcp-upstream: no
use-syslog: yes
log-queries: no
root-hints: "/etc/unbound/root.hints"
hide-identity: yes
hide-version: yes
identity: ""
version: ""
harden-glue: yes
private-address: fd00::/8
private-address: fe80::/10
private-address: ::ffff:0:0/96
unwanted-reply-threshold: 10000000
do-not-query-localhost: no
prefetch: yes
minimal-responses: no
module-config: "iterator"
name: "."

Launch the services #

Run the following to make Clash, DNScrypt-proxy and Unbound launch at system startup:

service clash enable
service dnscrypt-proxy enable
service unbound enable

Apply the changes:

service dnscrypt-proxy restart
service unbound restart
service clash restart

Redirect the traffic to Clash #

Go to https://ROUTER_IP/cgi-bin/luci/admin/network/firewall/custom, append the following to the end of rules. Be aware that you need to change YOUR_SSH_PORT.

iptables -t nat -N clash_lan
iptables -t nat -A clash_lan -d -j RETURN
iptables -t nat -A clash_lan -d -j RETURN
iptables -t nat -A clash_lan -d -j RETURN
iptables -t nat -A clash_lan -d -j RETURN
iptables -t nat -A clash_lan -d -j RETURN
iptables -t nat -A clash_lan -d -j RETURN
iptables -t nat -A clash_lan -d -j RETURN
iptables -t nat -A clash_lan -d -j RETURN

# Disable the proxy for
# iptables -t nat -A clash_lan -s -j RETURN

iptables -t nat -A clash_lan -p tcp --dport YOUR_SSH_PORT -j ACCEPT
iptables -t nat -A clash_lan -p tcp --dport 80 -j REDIRECT --to-ports 9090
iptables -t nat -A clash_lan -p tcp --dport 443 -j REDIRECT --to-ports 9090
iptables -t nat -A clash_lan -p tcp --dport 53 -j REDIRECT --to-ports 53

iptables -t nat -A PREROUTING -p tcp -j clash_lan

# Chromecast
iptables -t nat -A PREROUTING -s IP_CIDR_OF_CHROMECAST_IF_YOU_HAVE_ANY -p udp --dport 53 -j REDIRECT --to-ports 53
iptables -t nat -A PREROUTING -s IP_CIDR_OF_CHROMECAST_IF_YOU_HAVE_ANY -p tcp --dport 53 -j REDIRECT --to-ports 53

You can now open your browser now and go to to see if it works!

Control Clash #

Remember external-controller? We’re gonna make use of it… right now.

There’s a fantastic web interface that does exactly the work: Use your OpenWrt IP address, and port 6170.

Be ware that Clash does not remember your choices of servers between restarts.

Check the logs #

logread -e clash -f

Last words #

I’m also using WireGuard to connect back to my home network when I’m not in house. If you want to know further more how to configure WireGuard to work with this approach (Clash + Unbound), comment down below.

Resources #