Manual setup to bypass Wireguard VPN for UDP traffic with specific source port

Hello,

I’m trying to accomplish 2 things at once:

  • learn a bit about how policy based routing works in Linux
  • have all outbound UDP traffic with source port 41641 (aka outbound Tailscale traffic), from lan and from the router itself, bypass the always-on Wireguard VPN which otherwise handles all internet traffic

I’m aware of the pbr plugin but I’m wondering if I can accomplish this myself on the command line by using ip commands, so I gain better understanding of how the system works under the covers.

I’m attempting to do this on a device running vanilla OpenWrt 24.10, but I first managed to set it up on a device running GL.iNet’s flavor which also has an always-on Wireguard VPN. GL.iNet’s firmware is already set up to bypass the VPN if fwmark 0x8000/0xc000 is set on packets. I’d like to do something similar on my vanilla OpenWrt device, but I’ve gotten to the point where I’m going round and round with the documentation and I’m not sure how to proceed.

Most notably, I think there is a difference between how GL.iNet routes traffic thru Wireguard, and how vanilla OpenWrt with the luci-proto-wireguard package does it.

On the GL.iNet device, if I run ip route show table all, near the top there is a line

default dev wgclient1 table 1001 proto static scope link 

if I then run ip -d route show table 1001 I see the following:

unicast default dev wgclient1 proto static scope link
blackhole default proto static scope global metric 254
unicast 10.99.101.0/24 dev wgclient1 proto static scope link

I’m very new at this, and maybe I’m misunderstanding what the complete output of ip route show table all is telling me, but this seems to me to mean that the Wireguard interface wgclient1 is set as the default way for traffic to go, by sending it (?) via table 1001.

Then when I run ip rule on the GLi device, I see these two rules among others:

6000:   from all fwmark 0x8000/0xf000 lookup main
6000:   from all fwmark 0x1000/0xf000 lookup 1001

Thus I can see how packets with fwmark of 0x8000 would be sent by main instead of the Wireguard interface. (I’m not sure where fwmark 0x1000 is set, if it is).

Now, on my vanilla OpenWrt device using luci-proto-wireguard to set up a Wireguard VPN for all internet traffic, it’s done differently.

When I run ip route show table all, near the top I see

default dev wg1 proto static scope link 

And when I run ip -d route show table main, I see

unicast default dev wg1 proto static scope link
unicast 10.180.99.0/24 dev wg1 proto kernel scope link src 10.180.99.3
unicast x.x.x.x via 10.0.0.1 dev eth2 proto static scope global
unicast 10.0.0.0/24 dev eth2 proto kernel scope link src 10.0.0.2
unicast 192.168.1.0/24 dev br-lan proto kernel scope link src 192.168.1.1

where x.x.x.x is the public IP of my VPN exit point.

I’m not really sure how to interpret this actually. I’m still too much of a noob to really understand everything these commands are telling me. But what I do believe is that when luci-proto-wireguard sets up a Wireguard interface to handle internet traffic by default, it doesn’t do so by creating a table for Wireguard and sending traffic to that. Instead it’s altering the main table (and maybe doing some other things) to accomplish this.

What I’d like to do is set things up similar to how GLi’s firmware does it, but compatible with the setup created by luci-proto-wireguard. I think what I need to do is create a table specifically for the traffic I want to bypass the VPN, then use a Traffic Rule (Luci terminology) to set a fwmark on the relevant packets, and then create a rule to send marked packets to the table I created which route them out the router’s wan interface instead of by wg1.

My issues with this are:

  • I don’t know if this is a correct approach
  • I don’t know if this is possible with built-in commands or if a plugin such as pbr is required
  • I’m not sure where to begin as far as commands to accomplish this

I’d love to hear any suggestions anyone has, or links to tutorials / guides / posts about doing what I’m trying to do. I’ve run dozens of searches at this point, but everything I find seems different enough to what I’m trying to accomplish that it doesn’t really apply.

  1. Create a firewall rule that marks the traffic you want to bypass the wireguard interface.
nft insert rule inet fw4 mangle_output oifname eth2 udp sport 41641 counter meta mark set 0x5

or

#/etc/config/firewall

config rule
        option dest 'wan'
        option name 'UDP_41641'
        list proto 'udp'
        option target 'MARK'
        option set_mark '5'
        option src_port '41641'
  1. Create an ip rule that forwards the marked traffic to a custom routing table.
ip rule add fwmark 0x5 lookup 5 priority 5

or

#/etc/config/network

config rule
        option mark '0x5'
        option lookup '5'
        option priority '5'
  1. Set a default route via the wan interface in that custom routing table.
ip route add 0.0.0.0/0 via 10.0.0.1 table 5

or

#/etc/config/network

config route
        option interface 'wan'
        option target '0.0.0.0/0'
        option table '5'
        option gateway '10.0.0.1'

That should be all.

2 Likes

See my notes which are using netifd to do manual PBR:
OpenWRT Policy Based Routing (PBR)

I case you need help with Setting up WireGuard:
WireGuard Client Setup Guide

1 Like