About Wireguard, routes and rules

Here be dragons.

Several users have reported issues with wireguard in client mode, in particular when trying to route all traffic through a wg interface. While it doesn’t appear to be a clear pattern about the causes sometimes forcing a static route with a high enough priority was enough to cope with the issue.
See here for an example where a command fails because the syntax is in error:

ip route add 195...* dev wg0 metric

A minimally intrusive solution to get rid of the host route when routing all traffic is below.

The easy part:
/usr/bin/wireguard_watchdog implements a monitoring of the wg tunnel, if the latest_handshake value exceeds a predetermined value the script tries to force a resolution of the IP address of the server using:
wg set ${iface} peer ${public_key} endpoint "${endpoint_host}:${endpoint_port}"
as it may have changed since the tunnel was first established.
Unfortunately there is a pitfall there, the endpoint_host resolution will fail if the DNS is restricted to the wg interface and the tunnel becomes stuck.
The solution is straightforward, shutdown and rebuild the tunnel instead:

  ubus call network.interface.${iface} down
  ubus call network.interface.${iface} up

And while at it increase the timeout value by 10 seconds, this is to cope with very slow networks and avoid intempestive shutdowns / rebuilds:
[ ${idle_seconds} -lt 160 ] && return 0;
The modified wireguard_watchdog is here.

Now the core of the subject, /lib/netifd/proto/wireguard.sh.
This file is a derivative of the original from Openwrt, with additions.

  • the script creates a /tmp/wireguard/default-status file to store the peer config name, the interface name and a command to delete the route to the wireguard server, one field per line.
    The idea is to have a way to remove the route when the tunnel is shutdown. This is at best fragile, and will obviously fail if there is more than one tunnel the contents will be corrupted.
    A safer solution is to extract the endpoints IP address from the output of wg show $ifname endpoints:
    wg show ${config} endpoints | ...
    plus some scripting and use the results to format the route delete command. Of course this works even if the original endpoint-host has been modified by wireguard_watchdog.
  • about DEFAULT_ROUTE. It is set if route_allowed_ips is also set, and tested to see if a route to the wg server should be added. This is not correct, adding a route or not to the wg server via the physical interface has nothing to do with routing IP ranges via the wg interface itself. Openwrt has an explicit flag for this purpose it is called “nohostroute” it is not managed by default in /etc/config/network but it still appears in the script …
  • the surest way to avoid route dependencies is to not have routes at all. The solution comes straight from the mouth of the horse section “Improved Rule-based Routing”. Restarting from the Openwrt’s version of wireguard.sh I have just implemented the rules / routes additions. This works very well for routing all traffic through a wg interface ( + + ::/1 + 8000::/1)
  • As it is now irrelevant, /etc/hotplug.d/iface/18-wireguard can be safely removed.

Configuration: add fwmark and nohostroute to the interface section, and set route_allowed_ips to 0 as this wg interface will become the default. Allowed Ips are still required by the kernel module.

 config interface 'wgxxx'
        option proto 'wireguard'
        list dns 'dns reachable via the tunnel'
        option mtu '1280'
        option metric '9'
        list addresses 'ipv4 address of the interface'
        option private_key '(hidden)'
        option fwmark '10359'
        option nohostroute '1'
	option disabled '0'
 config wireguard_wgxxx 'yyy'
        option endpoint_port '51820'
        list allowed_ips ''
        list allowed_ips ''
        list allowed_ips '::/1'
        list allowed_ips '8000::/1'
        option preshared_key '(hidden)'
        option public_key '(hidden)'
        option persistent_keepalive '25'
        option route_allowed_ips '0'
        option endpoint_host 'zzz'

Of course building tunnels with a higher priority in order to access private networks is still possible, just don’t set fwmark and nohostroute, set route_allowed_ips and use a lower metric than the default’s one.

The modified script is here.

@teltonika1love: could you test this to see if it solves your issue ?


1 Like

At the end I have caught an occurrence of the failure to add a default route. /tmp/wireguard/default-status contains:

cat /tmp/default-status 
ip route del x.y.z dev qmimux0 metric 3
ip route del x.y.z dev qmimux0 metric 
ip route del x.y.z dev qmimux0 metric 3
ip route del x.y.z dev qmimux0 metric 

The contents doesn’t make sense, and the metric value is not always present. The original code is:

 ip route add "$ip" ${gw:+via "$gw"} dev "$dev" metric "$metric"
 echo "ip route del "$ip" dev "$dev" metric $metric" >> "$DEFAULT_STATUS"

The metric value is missing in the del command, it must also be missing in the add one and the ip route add fails.

This topic was automatically closed after 15 days. New replies are no longer allowed.