Transparent proxy on OpenWrt - docker-geph/get-started GitHub Wiki

Introduction

The following guide is based on official OpenWrt 23.05.0 with a working opkg and SSH connection.

The core idea of this setup is to use redsocks to sit in front of SOCKS5 proxy such as Geph4 to work as a transparent proxy. Then configure dnsmasq to add gfwlist domains into a nftset rule so nftables can forward those packets to our proxy. We will also deal with DNS pollution so dnsmasq can set the right IP address.

Package installation

First we will install some necessary packages.

opkg update
opkg install dnsmasq-full redsocks coreutils-base64 nano luci-app-https-dns-proxy
# Disable global https-dns-proxy
uci set https-dns-proxy.config.force_dns=0
uci set https-dns-proxy.config.dnsmasq_config_update="-"
uci commit
# Need to stop and start to reset dnsmasq settings
service https-dns-proxy stop
service https-dns-proxy start
# Actually replace dnsmasq
cd /tmp
opkg download dnsmasq-full
cd ~
opkg remove dnsmasq
opkg install /tmp/dnsmasq-full*.ipk
rm /tmp/dnsmasq-full*.ipk /etc/config/dhcp-opkg

Set up redsocks

Assuming you have a socks5 based proxy already set up, we can config redsocks as a transparent proxy. Below is the recommended setting for redsocks to work.

ROUTER_IP=192.168.1.1
ROUTER_PORT=20000
PROXY_IP=192.168.1.2
PROXY_PORT=7891
cat << EOF > /etc/redsocks.conf
base {
  daemon = on;
  redirector = iptables;
  rlimit_nofile = 65535;
  redsocks_conn_max = 65535;
}
redsocks {
  local_ip = $ROUTER_IP;
  local_port = $ROUTER_PORT;
  ip = $PROXY_IP;
  port = $PROXY_PORT;
  type = socks5;
  listenq = 512;
}
EOF
service redsocks stop
sleep 1
service redsocks start

You can read more here. Remember to change local_ip to match the interface's IP address that you are intended to have transparent proxy, for example br-lan. You can have multiple redsocks section for each interface you have with different local_ip, or using nftables to forward your packet, which is what we are going to do. Note 127.0.0.1 does not seem to work as a valid local_ip.

Set up firewall

Finally we can configure firewall to forward our requests.

cat << EOF >> /etc/firewall.user
nft "add set inet fw4 gfwlist { type ipv4_addr; flags interval; }"
nft "add set inet fw4 gfwlist6 { type ipv6_addr; flags interval; }"
nft add rule inet fw4 dstnat_lan meta l4proto tcp ip daddr @gfwlist dnat ip to $ROUTER_IP:$ROUTER_PORT
nft "add chain inet fw4 dstnat_output { type nat hook output priority -100; policy accept; }"
nft add rule inet fw4 dstnat_output meta l4proto tcp ip daddr @gfwlist dnat ip to $ROUTER_IP:$ROUTER_PORT
EOF
uci add firewall include
uci set firewall.@include[-1].type=script
uci set firewall.@include[-1].path=/etc/firewall.user
uci set firewall.@include[-1].fw4_compatible=1
uci commit
service firewall restart

Beware we use DNAT instead of REDIRECT like what most other guides are suggesting. This is because request originated from OpenWrt (OUTPUT chain) or from other interfaces (PREROUTING chain) will have different SRC IP, which will be rejected by redsocks if we use REDIRECT.

Bootstrap DNS

First we need to do a little bootstrapping. We will generate our nftset rules using gfwlist2dnsmasq. However, without the generated rule one cannot access GitHub within a censored network. We will create a very basic rule first to download gfwlist2dnsmasq later on.

mkdir /etc/dnsmasq.d
cat << EOF > /etc/dnsmasq.d/github.conf
server=/githubusercontent.com/127.0.0.1#5053
nftset=/githubusercontent.com/4#inet#fw4#gfwlist,6#inet#fw4#gfwlist6
server=/github.com/127.0.0.1#5053
nftset=/github.com/4#inet#fw4#gfwlist,6#inet#fw4#gfwlist6
EOF
uci set dhcp.@dnsmasq[-1].confdir="/etc/dnsmasq.d"
uci set https-dns-proxy.@https-dns-proxy[0].proxy_server="socks5h://$PROXY_IP:$PROXY_PORT"
uci set https-dns-proxy.@https-dns-proxy[0].listen_addr="0.0.0.0"
# If you are in China you might also want to try different DNS provider
uci commit
service dnsmasq restart
service https-dns-proxy restart

You can install drill and run drill @$ROUTER_IP -p 5053 github.com & drill @$ROUTER_IP github.com to make sure DNS is now working. You can try changing the Resolver for the last entry (the one with port 5053) if the default one (Cloudflare) is not working for you.

Set up gfwlist2dnsmasq

Now we have set up our transparent proxy. We will test it by installing gfwlist2dnsmasq.

mkdir -p /usr/local/bin
wget -O /usr/local/bin/gfwlist2dnsmasq.sh https://raw.githubusercontent.com/docker-geph/gfwlist2dnsmasq/master/gfwlist2dnsmasq.sh
# Alternatively: https_proxy=socks5h://$PROXY_IP:$PROXY_PORT curl -L https://raw.githubusercontent.com/docker-geph/gfwlist2dnsmasq/master/gfwlist2dnsmasq.sh > /usr/local/bin/gfwlist2dnsmasq.sh
# However, wget should work if everything is right.
chmod +x /usr/local/bin/gfwlist2dnsmasq.sh
/usr/local/bin/gfwlist2dnsmasq.sh --nftset4 gfwlist --nftset6 gfwlist6 -o /etc/dnsmasq.d/gfwlist.conf
service dnsmasq restart

If everything goes right you have gfwlist installed.

Add cron job for gfwlist2dnsmasq

Also set up a cron job to update the list automatically.

echo "0 0 * * * sh /usr/local/bin/gfwlist2dnsmasq.sh -d 127.0.0.1 -p 5053 --nftset4 gfwlist --nftset6 gfwlist6 --exclude-domain-file /root/whitelist.conf --extra-domain-file /root/gfwlist.conf -o /etc/dnsmasq.d/gfwlist.conf && service dnsmasq restart" >> /etc/crontabs/root

Appendix A: .bash_profile

gfwlist() {
    nano /root/gfwlist.conf
    read c0 c1 c2 c3 c4 cmd <<< "$(grep "gfwlist" /etc/crontabs/root | head -n 1)"
    eval "$cmd"
}

redsocks() {
    nano /etc/redsocks.conf
    # redsocks restart is not very reliable
    service redsocks stop
    sleep 1
    service redsocks start
}

httpsdns() {
    nano /etc/config/https-dns-proxy
    service https-dns-proxy restart
}

isgfwed() {
    grep "$1" /etc/dnsmasq.d/gfwlist.conf | grep server
}

restart_proxy() {
    service redsocks stop
    service https-dns-proxy stop
    service dnsmasq stop
    sleep 1
    service dnsmasq start
    service https-dns-proxy start
    service redsocks start
}

Appendix B: what if I can't put Geph4 on my router?

While docker makes deployment and upgrade easy, it is quite heavy on resource. Our basic setup idles at 239M memory and uses 342M on storage, which may not be available on your device.

There are three ways to workaround this issue:

  1. First is to deploy Geph4 natively. You can download the latest binary from here and configure it yourself. You will need to edit /etc/rc.local to launch Geph4 on boot, or create a service so it can restart on failure.

  2. Next is setting up Extroot if you are only short on storage. Some newer devices like Linksys E8450 / Belkin RT3200 has 512M memory but only 128M flash with a slow USB 2.0 port. Not a great NAS candidate but has everything to get a Extroot environment up and running.

  3. The final way is simply running Geph4 service externally. Many existing OpenWrt capable routers are running on MIPS, which is not only slow, but also unsupported by one of Geph4's dependency, so no binary will be available. You can put Geph4 on a secondary SBC and keep existing network configuration untouched. In this case you need to change redsocks and firewall rules to point at the correct server.

Appendix C: Install Geph4 on OpenWrt

Below is a section included in the earlier revision of this tutorial. Currently installing docker on OpenWrt is not recommended, and the guide below might be broken.

We will use docker-compose to install Geph4, which is the recommended way from docker-geph.

opkg install dockerd docker-compose
# And add the 2 fixes from: https://forum.openwrt.org/t/nftables-vs-dockerd/132588/8
# and https://forum.openwrt.org/t/no-internet-access-from-docker-continers-hosted-on-openwrt/136848/10
service dockerd stop
service dockerd ucidel
service dockerd boot
uci add firewall forwarding
uci set firewall.@forwarding[-1].src="docker"
uci set firewall.@forwarding[-1].dest="wan"
uci set dockerd.firewall.extra_iptables_args="--match conntrack ! --ctstate RELATED,ESTABLISHED"
uci commit
service firewall restart
service dockerd restart
cat << EOF > /root/docker-compose.yml
services:
  geph4:
    image: dockergeph/geph4:v4.5.2-alpha.2
    container_name: geph4
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=Asia/Shanghai
    volumes:
      - /root/geph4:/config
    ports:
      - 9909:9909
      - 9910:9910
      - 5055:5053/udp
    network_mode: bridge
    restart: unless-stopped
EOF
cd /root
docker-compose up -d
# This script needs some work as OpenWrt 22.03.0 is not working very well with docker right now.
# If you run `docker exec -it geph4 ping bing.com` and it is not working,
# your docker container is also not getting the internet connection to connect to your hosted geph4 server.
# Currently the workaround is to reboot the router.

Now the sample config file is copied to /root/geph4/geph4.conf. Open the file with nano /root/geph4/geph4.conf and change it the way you like. However, the following options are recommended.

...
OPTIONS="--dns-listen 0.0.0.0:5053 --socks5-listen 0.0.0.0:9909 --http-listen 0.0.0.0:9910 ...
...

To work around DNS pollution, we will use https-dns-proxy to provide DNS services over Geph4's socks5 connection. However, we can still expose Geph4's builtin proxied DNS for troubleshooting. We also need to expose our socks5 port for redsocks, so --socks5-listen 0.0.0.0:9909 is mandatory. We also expose http proxy for programs without socks5 support to force them using proxy temporarily. If you want to change the port, you need to update /root/docker-compose.yml as well as /etc/dnsmasq.d/*.conf.

You can test your connection within OpenWrt by running https_proxy=socks5h://172.17.0.2:9909 curl https://google.com. Require curl installation since DNS is currently not work, and wget does not support socks5h protocol.

⚠️ **GitHub.com Fallback** ⚠️