Playing around with OpenWrt often involves multiple outbounds, maybe multiple ISPs or VPN outbounds like WireGuard. We always need to configure and route the traffic. Configuring routing can actually be a headache because as the needs become more complex, the difficulty of configuration tends to increase exponentially, also considering potential conflicts. Using ipset is certainly good, but the workload doesn’t diminish, it becomes greater sometimes. To address this need, I chose PBR, aka Policy-Based Routing, simplifying this complex problem with some simple configuration scripts.
PBR (Policy Based Routing)
Before discussing policy routing, it might be useful to first look at the ipset. We need to invoke ipset in the firewall to mark the traffic, then match this mark in the ipv4/ipv6 rules to perform other operations. Isn’t it already giving you a headache? Figuring out how to update these ipset lists is also a problem. The PBR on OpenWrt provides a method called Custom User Files, which conveniently solves the issue of updating and configuring the list together. Furthermore, PBR on OpenWrt works with nft, so there’s not much to worry about performance. Therefore, after considering it, I decided to use PBR.
As for installation, it’s quite simple, we only need the pbr
and luci-app-pbr
packages. Once installed, we can control it through the graphical interface under Services – Policy Routing.
Route Traffic
Assuming we have a WireGuard outbound interface named wg0
on OpenWrt, and our default route dictates that all our traffic goes out through wan
and wan6
, how should we configure it to ensure that all traffic going to Google using this WireGuard interface?
#!/bin/sh
# shellcheck disable=SC2015,SC3003,SC3060
TARGET_URL='https://www.gstatic.com/ipranges/goog.json'
TARGET_DL_FILE='/var/pbr_tmp_google_ip_ranges.gz'
TARGET_TABLE='inet fw4'
TARGET_INTERFACE='wg0'
_ret=1
mkdir -p "${TARGET_DL_FILE%/*}"
[ -s "$TARGET_DL_FILE" ] ||
uclient-fetch -qO- "$TARGET_URL" |
gzip > "$TARGET_DL_FILE"
[ -s "$TARGET_DL_FILE" ] || return 1
params4="$(zcat "$TARGET_DL_FILE" | jsonfilter -e "@.prefixes[*].ipv4Prefix")"
params6="$(zcat "$TARGET_DL_FILE" | jsonfilter -e "@.prefixes[*].ipv6Prefix")"
for ver in 4 6; do
case "$ver" in
4) params="$params4";;
6) params="$params6";;
esac
[ -n "$params" ] && _ret=0 || continue
nftset="pbr_${TARGET_INTERFACE}_${ver}_dst_ip_user"
nft "add element $TARGET_TABLE $nftset { ${params//$'n'/, } }" || _ret=1
done
Here’s a simple rule file, get the list of Google’s CIDR ranges using Google’s API,