Setup Clash transparent proxy on a Linux router
Tags: linux, iptables, homelab, clash
Published: 2022-05-24 17:41:51

I bought a very small fanless PC in April 2019, intended to install a Linux then replace my main router in my home, and set up a transparent proxy on it, Finally, I finished it in November 2019.

## Hardware and OS

The router using J1900 CPU, installed 2G memory and 128G SSD, and I install an Ubuntu on it. Because Ubuntu is my most used Linux distributions.

Many people believe router that using specially designed hardware may have higher performance. But I think that a Linux on a universal PC hardware will have almost the same performance with special hardware.

## Overview

I installed or use the following software on my router:

- **dnsmasq** for providing DHCP service and DNS cache.
- **Clash** for providing redir proxy and DNS service.
- **iptables** for providing NAT and firewall, redirects TCP connections.

I install them by Ansible — a configuration management tool, the source code is available on my [GitHub](https://github.com/jysperm/playbooks/blob/master/homelab.yml). You can’t use it directly, but you can learn more details about my setup, or modify your own version based on it.

## Turn into a router

My router has four RJ45 ports, Let’s assume that the first port (`enp1s0`) connected to the Internet — we call it WAN, and the rest ports connect to used by my devices — we call it LAN.

Edit `/etc/sysctl.conf`, set the following instructions to enable forwarding for IPv4:

```
net.ipv4.ip_forward=1
```

Create `/etc/netplan/bridges.yaml`, use NetPlan — Ubuntu’s new network configuration utility to make a bridge interface(called `brlan`) between our LAN interfaces, includes `enp2s0`, `enp3s0` and `enp4s0`, then assign `10.0.0.1/24` to it:

```
network:
  version: 2
  renderer: networkd
  ethernets:
    enp1s0:
      dhcp4: true
    enp2s0:
      optional: true
    enp3s0:
      optional: true
    enp4s0:
      optional: true
bridges:
    brlan:
      addresses:
        - 10.0.0.1/24
      interfaces:
        - enp2s0
        - enp3s0
        - enp4s0
```

You will need a reboot to make `sysctl.conf` and NetPlan take effects.

Run the following commands in shell to setup iptables NAT rules:

```
iptables -t nat -A POSTROUTING -o enp1s0 -j MASQUERADE
```

We need to set some of basic firewall rules, we deny all income connections by default unless explicitly permit.

```
iptables -A INPUT -i enp1s0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
iptables -A INPUT -i enp1s0 -p icmp -j ACCEPT
iptables -A INPUT -i enp1s0 -j REJECT --reject-with icmp-port-unreachable
```

Then install `netfilter-persistent` to persistent the rules after reboot:

```
apt-get install netfilter-persistent
netfilter-persistent save
```

Now your Linux has become a NAT router.

## DHCP

To be a real router, we also need DHCP service, we use dnsmasq to do that:

```
apt install dnsmasq
```

Modify the following lines in `/etc/dnsmasq.conf`:

```
interface=brlan
dhcp-range=brlan,10.0.0.1,10.0.0.255,12h
dhcp-option=6,10.0.0.1
```

## Clash

Download release from [GitHub](https://github.com/Dreamacro/clash), unarchive and move to `/usr/bin/clash`, add executable permissions:

```
curl -o clash.gz https://github.com/Dreamacro/clash/releases/download/v1.1.0/clash-linux-amd64-v1.1.0.gz
gzip -dk clash.gz
mv clash /usr/bin/clash
chmod +x /usr/bin/clash
```

Create `/etc/systemd/system/clash.service`:

```
[Unit]
Description=clash daemon
[Service]
Type=simple
LimitNOFILE=49152
ExecStart=/usr/bin/clash -d /etc/clash
[Install]
WantedBy=multi-user.target
```

Prepare a clash config in `/etc/clash/config.yml`, the following just showing some key instructions, not the complete configuration, you need to modify by yourself:

```
redir-port: 7892
mode: rule

proxies:
    - { name: 'My Upstream Proxy', type: 'http', server: 'example.com', port: 443}

proxy-groups:
    - { name: "Proxy", type: select, proxies: ['My Upstream Proxy']}

rules:

# Domains don't want to be proxy
- DOMAIN-SUFFIX,alipay.com,DIRECT
- DOMAIN-SUFFIX,baidu.com,DIRECT
- DOMAIN-SUFFIX,bilibili.com,DIRECT
- DOMAIN-SUFFIX,douban.com,DIRECT
- DOMAIN-SUFFIX,iqiyi.com,DIRECT
- DOMAIN-SUFFIX,jd.com,DIRECT
- DOMAIN-SUFFIX,qq.com,DIRECT
- DOMAIN-SUFFIX,taobao.com,DIRECT
- DOMAIN-SUFFIX,tmall.com,DIRECT
- DOMAIN-SUFFIX,weibo.com,DIRECT
- DOMAIN-SUFFIX,zhihu.com,DIRECT

# Domain keywords to be proxy
- DOMAIN-KEYWORD,amazon,Proxy
- DOMAIN-KEYWORD,google,Proxy
- DOMAIN-KEYWORD,gmail,Proxy
- DOMAIN-KEYWORD,youtube,Proxy
- DOMAIN-KEYWORD,facebook,Proxy
- DOMAIN-KEYWORD,twitter,Proxy
- DOMAIN-KEYWORD,instagram,Proxy
- DOMAIN-KEYWORD,dropbox,Proxy

# Local address
- IP-CIDR,127.0.0.0/8,DIRECT
- IP-CIDR,172.16.0.0/12,DIRECT
- IP-CIDR,192.168.0.0/16,DIRECT
- IP-CIDR,10.0.0.0/8,DIRECT
- IP-CIDR,17.0.0.0/8,DIRECT
- IP-CIDR,100.64.0.0/10,DIRECT

# Finally rules
- GEOIP,CN,DIRECT
- MATCH,Proxy
```

## Redir proxy
We will use iptables to redirect TCP connections to our clash redir port.

First, create a chain for Clash:

```
iptables -t nat -N CLASH
```

Ignore connections to local private address:

```
iptables -t nat -A CLASH -p tcp -d 0.0.0.0/8 -j RETURN
iptables -t nat -A CLASH -p tcp -d 10.0.0.0/8 -j RETURN
iptables -t nat -A CLASH -p tcp -d 127.0.0.0/8 -j RETURN
iptables -t nat -A CLASH -p tcp -d 172.16.0.0/12 -j RETURN
iptables -t nat -A CLASH -p tcp -d 192.168.0.0/16 -j RETURN
```

Ignore connections to redir port to avoid cyclic redirect:

```
iptables -t nat -A CLASH -p tcp -m tcp --dport 7892 -j RETURN
```

We also need ignore connections to your upstream proxy to avoid cyclic redirect:

```
# if you upstream proxy has a stable address
iptables -t nat -A CLASH -p tcp -d <your upstream proxy address> -j RETURN
# if you upstream proxy has a stable proxy
iptables -t nat -A CLASH -p tcp -m tcp --dport <your upstream proxy port> -j RETURN
```

Then, redirect to clash:

```
iptables -t nat -A CLASH -p tcp -j REDIRECT --to-ports 7892
```

Send traffics from LAN to the CLASH chain:

```
iptables -t nat -A PREROUTING -s 10.0.0.0/24 -p tcp -j CLASH
```

Now, all of your outgoing connections will be redirect to Clash.

## Troubles about DNS

Unlike a system proxy, iptables redirect traffic on the IP layer, it uses IP instead of a domain. So the domain-related rules in Clash don’t work.

The DNS query is sent by UDP, which can’t redirect by the previous rules, and may produce an incorrect result. We are going to use Clash’s DNS to replace `systemd-resolved` — the default DNS resolver of Ubuntu.

Disable `systemd-resolved`:

```
systemctl disable systemd-resolved
systemctl stop systemd-resolved
```

Modify Clash config:

```
dns:
  enable: true
  ipv6: false
  listen: 127.0.0.53:53
  enhanced-mode: redir-host
  nameserver:
    - 119.29.29.29
    - 223.5.5.5
  fallback:
    - 'tls://1.1.1.1:853'
    - 'tls://8.8.8.8:853'
    - 'tls://9.9.9.9:853'
```

Clash will resolve domains by servers in nameserver section and fallback section in the same time.

Servers in nameserver section are fast but unreliable, if it returns an address which GEOIP country is `CN`, we use that; otherwise, we wait for the result form servers in `fallback` section.

In this process, Clash will remember the mapping from domain to IP, so Clash will know the domain of a redirected connection, and apply domain-related rules.
Now, Clash will provide correct DNS resolve on `127.0.0.53:53`. Then we need set dnsmasq as a DNS cache server, Modify the following lines in `/etc/dnsmasq.conf`:

```
port=53
no-resolv
server=127.0.0.53
cache-size=1000
```

Modify `/etc/resolv.conf` to use dnsmasq as local resolver:

```
nameserver 127.0.0.1
```

## Other components in my case

- **pppd** for providing PPPoE connection.
- **netdata** for monitoring dashboard.