Contents
I am running several DStar repeaters using an AMPR Net public IP address. In this article we’ll see how to route only specific (Ham related) traffic through that address.
Why ?
I wanted only ham radio related network traffic to go through the Wireguard tunnel. This traffic is mainly generated by DStarGateway. All other generated traffic should go through the regular ethernet interface. Also, in case the Wireguard tunnel fails I do not want DStarGateway to be offline, I want it to fallback to regular ethernet interface, as a matter of fact binding it to the Wireguard interface was not an interface.
When installing DStarGateway a user dstar is created and the process is run under this user by systemd. The trick is to have all traffic generated by the dstar user to flow through the tunnel. We will create a specific routing table and routing rules for that matter.
Prerequisites
- Working Wireguard 44net interface
- A recent Linux distribution which includes systemd, all the work presented here has been done under Armbian 13 Trixie.
Disable Wireguard default routing
First, we want Wiregaurd to stop inserting a default route into the main routing table. Also, we want response to incoming traffic to also go back through that same tunnel.
For that matter we add the following lines into the interface section of our Wiregaurd configuration (typically /etc/wireguard/wg0.conf)
Make sure to replace 44.xxx.xxx.xxx with your own 44 address.
[Interface]
# handle table stuff ourselves
Table = off
PostUp = ip route replace default dev %i table 51820
PostDown = ip route flush table 51820
# Force incoming traffic through the 44 interface to go back through that same interface
PostUp = ip rule add pref 110 from 44.xxx.xxx.xxx/32 lookup 51820
PostDown = ip rule del pref 110 from 44.xxx.xxx.xxx/32 lookup 5182Code language: PHP (php)
This will create a routing table with id 51820 when the Wireguard tunnel is brought up and remove it when it is brought down.
The second part adds rules to route response traffic through the tunnel, otherwise it will routed through main table and no incoming traffic would be possible. The rules are respectively created and removed as soon as the tunnel goes up and own.
Create the user based routing rules
What we need here is a rule telling the kernel to use the 51820 routing table f the table is present and if the user generating the traffic is the dstar user. To create such a rule we will use the following command:
ip rule add pref 100 uidrange USER-ID-USER-ID lookup 51820
USER-ID needs to be the actual ID of the user whose traffic we want to route through the tunnel. To simplify this process and make the rule persistent, I created a script and a systemd service to do just that.
Copy this script into /usr/local/sbin/user-pbr.sh
#!/usr/bin/env bash
set -euo pipefail
# Usage:
# user-pbr.sh add <username> [table] [pref]
# user-pbr.sh del <username> [table] [pref]
#
# Defaults:
# table=51820
# pref=100
ACTION="${1:-}"
USER_NAME="${2:-}"
TABLE="${3:-51820}"
PREF="${4:-100}"
if [[ -z "${ACTION}" || -z "${USER_NAME}" || ! "${ACTION}" =~ ^(add|del)$ ]]; then
echo "Usage: $0 {add|del} <username> [table] [pref]" >&2
exit 2
fi
# Resolve UID
UID_NUM="$(id -u "${USER_NAME}" 2>/dev/null || true)"
if [[ -z "${UID_NUM}" ]]; then
echo "Error: user '${USER_NAME}' not found." >&2
exit 3
fi
RULE_RE="uidrange ${UID_NUM}-${UID_NUM}.* lookup ${TABLE}"
case "${ACTION}" in
add)
# only add if missing
if /sbin/ip rule show | grep -qE "${RULE_RE}"; then
exit 0
fi
/sbin/ip rule add pref "${PREF}" uidrange "${UID_NUM}-${UID_NUM}" lookup "${TABLE}"
;;
del)
# delete if present, ignore errors
if /sbin/ip rule show | grep -qE "${RULE_RE}"; then
/sbin/ip rule del pref "${PREF}" uidrange "${UID_NUM}-${UID_NUM}" lookup "${TABLE}" || true
fi
;;
esacCode language: PHP (php)
And this systemd unit into /etc/systemd/system/user-pbr@.service
[Unit]
Description=PBR: route user %i via routing table 51820 when available
After=network-online.target
Wants=network-online.target
[Service]
Type=oneshot
RemainAfterExit=yes
# %i = instance name (username)
# Arguments: action user table pref
ExecStart=/usr/local/sbin/user-pbr.sh add %i 51820 100
ExecStop=/usr/local/sbin/user-pbr.sh del %i 51820 100
[Install]
WantedBy=multi-user.targetCode language: PHP (php)
Enable the service for the dstar user
sudo systemctl enable user-pbr@dstarCode language: CSS (css)
To start it all either reboot the system or:
sudo systemctl restart wg-quick@wg0
sudo systemctl start user-pbr@dstarCode language: CSS (css)
To test ist issue following commands:
sudo -u dstar curl -4 api.ipify.org
curl -4 api.ipify.orgCode language: CSS (css)
The first command should display your tunnel public IP and the second command should display your own public IP.
Conclusion
We now have a policy based routing that routes all traffic generated by the dstar user over the Wireguard tunnel. This all kernel based with no iptables or nftables involved. You may add several users by enabling user-pbr@ service for each user.