RouterOS blatantly ignores pref-src. Can this really be a bug?

I was planning on writing out an example, so just for your reading pleasure: the official “rabbit hole solution” to multi-wan wireguard hosting.

We have two WANS. For this example, the two WAN connections are actually EoIP tunnels to another router. The base configuration is:

Interfaces:

/interface eoip
add name=eoip-wan1 remote-address=[other_router] tunnel-id=10
add name=eoip-wan2 remote-address=[other_router] tunnel-id=20

Addresses:

/ip address
add address=192.168.111.10/24 interface=eoip-wan1
add address=192.168.112.10/24 interface=eoip-wan2

Routes:

/routing table
add fib name=wan2

/ip route
add distance=1 dst-address=0.0.0.0/0 gateway=192.168.111.1
add distance=20 dst-address=0.0.0.0/0 gateway=192.168.112.1
add disabled=no dst-address=0.0.0.0/0 gateway=192.168.112.1 routing-table=wan2

WAN masquerade:

add action=masquerade chain=srcnat out-interface=eoip-wan1
add action=masquerade chain=srcnat out-interface=eoip-wan2

And a wireguard “server” with a single peer: (The other side has a 10s persistent keepalive configured.)

/interface wireguard
add listen-port=13231 mtu=1420 name=wireguard1
/interface wireguard peers
add allowed-address=192.168.50.0/24 interface=wireguard1 name=peer1 public-key="22SMLvYh7cQzj3/sGFeFLn7l0xVePXM59DLms3qnNUY=" responder=yes

And now the interesting bit.

Create a bridge with a /30 address:

/interface bridge
add name=br-wg protocol-mode=none

/ip address
add address=172.16.10.1/30 interface=br-wg

Mark connections according to their origins, add routing for wan2 and nat them:

/ip firewall mangle
add action=mark-connection chain=prerouting connection-mark=no-mark dst-address=192.168.111.10 dst-port=13231 in-interface=eoip-wan1 new-connection-mark=wg-wan1 protocol=udp
add action=mark-connection chain=prerouting connection-mark=no-mark dst-address=192.168.112.10 dst-port=13231 in-interface=eoip-wan2 new-connection-mark=wg-wan2 protocol=udp
add action=mark-routing chain=output connection-mark=wg-wan2 new-routing-mark=wan2

/ip firewall nat
add action=dst-nat chain=dstnat connection-mark=wg-wan1 to-addresses=172.16.10.1
add action=dst-nat chain=dstnat connection-mark=wg-wan2 to-addresses=172.16.10.1
add action=src-nat chain=input connection-mark=wg-wan1 to-addresses=172.16.10.2
add action=src-nat chain=input connection-mark=wg-wan2 to-addresses=172.16.10.2

A packet capture:

(The capture is done on the opposing router where the corresponding interfaces are eoip-test1 and eoip-test2)

  #  TIME      INTERFACE   SRC-ADDRESS           DST-ADDRESS           IP-PROTOCOL  SIZE  CPU
  0  3.367     eoip-test1  192.168.33.1:13231    192.168.111.10:13231  udp           190    0
  1  3.373     eoip-test1  192.168.111.10:13231  192.168.33.1:13231    udp           134    1
  2  3.374     eoip-test1  192.168.33.1:13231    192.168.111.10:13231  udp            74    1
  3  13.542    eoip-test1  192.168.33.1:13231    192.168.111.10:13231  udp            74    1
  4  23.784    eoip-test1  192.168.33.1:13231    192.168.111.10:13231  udp            74    1
  5  47.754    eoip-test2  192.168.33.1:13231    192.168.112.10:13231  udp           190    0
  6  47.761    eoip-test2  192.168.112.10:13231  192.168.33.1:13231    udp           134    1
  7  47.761    eoip-test2  192.168.33.1:13231    192.168.112.10:13231  udp            74    2
  8  58.342    eoip-test2  192.168.33.1:13231    192.168.112.10:13231  udp            74    2

This shows a connection first over wan1, then over wan2. The handshake initiations are the 190 byte packets, size 134 are the handshake responses and the 74 byte ones are keepalives. All is well.

A ping test was then conducted over the wg tunnel. The pings are 3s apart, and during the test, the wan1 connection was severed for about 10s.

  9  63.476    eoip-test2  192.168.33.1:13231    192.168.112.10:13231  udp           138    2
 10  63.476    eoip-test2  192.168.112.10:13231  192.168.33.1:13231    udp           138    1
 11  66.484    eoip-test2  192.168.33.1:13231    192.168.112.10:13231  udp           138    2
 12  66.485    eoip-test2  192.168.112.10:13231  192.168.33.1:13231    udp           138    1
 13  69.479    eoip-test2  192.168.33.1:13231    192.168.112.10:13231  udp           138    2
 14  69.48     eoip-test2  192.168.112.10:13231  192.168.33.1:13231    udp           138    1
 15  72.479    eoip-test2  192.168.33.1:13231    192.168.112.10:13231  udp           138    2
 16  72.48     eoip-test2  192.168.112.10:13231  192.168.33.1:13231    udp           138    1
 17  75.486    eoip-test2  192.168.33.1:13231    192.168.112.10:13231  udp           138    2
 18  75.487    eoip-test2  192.168.112.10:13231  192.168.33.1:13231    udp           138    1
 19  78.486    eoip-test2  192.168.33.1:13231    192.168.112.10:13231  udp           138    2
 20  78.487    eoip-test2  192.168.112.10:13231  192.168.33.1:13231    udp           138    1
 21  81.495    eoip-test2  192.168.33.1:13231    192.168.112.10:13231  udp           138    2
 22  81.496    eoip-test2  192.168.112.10:13231  192.168.33.1:13231    udp           138    1
 23  84.498    eoip-test2  192.168.33.1:13231    192.168.112.10:13231  udp           138    2
 24  84.499    eoip-test2  192.168.112.10:13231  192.168.33.1:13231    udp           138    1
 25  87.502    eoip-test2  192.168.33.1:13231    192.168.112.10:13231  udp           138    2
 26  87.503    eoip-test2  192.168.112.10:13231  192.168.33.1:13231    udp           138    1
 27  90.499    eoip-test2  192.168.33.1:13231    192.168.112.10:13231  udp           138    2
 28  90.5      eoip-test2  192.168.112.10:13231  192.168.33.1:13231    udp           138    1
 29  93.506    eoip-test2  192.168.33.1:13231    192.168.112.10:13231  udp           138    2
 30  93.507    eoip-test2  192.168.112.10:13231  192.168.33.1:13231    udp           138    1
 31  96.512    eoip-test2  192.168.33.1:13231    192.168.112.10:13231  udp           138    2
 32  96.513    eoip-test2  192.168.112.10:13231  192.168.33.1:13231    udp           138    1
 33  99.51     eoip-test2  192.168.33.1:13231    192.168.112.10:13231  udp           138    2
 34  99.511    eoip-test2  192.168.112.10:13231  192.168.33.1:13231    udp           138    1
 35  102.512   eoip-test2  192.168.33.1:13231    192.168.112.10:13231  udp           138    2
 36  102.513   eoip-test2  192.168.112.10:13231  192.168.33.1:13231    udp           138    1
 37  112.742   eoip-test2  192.168.33.1:13231    192.168.112.10:13231  udp            74    2
 38  112.742   eoip-test2  192.168.33.1:13231    192.168.112.10:13231  udp            74    2
 39  122.982   eoip-test2  192.168.33.1:13231    192.168.112.10:13231  udp            74    2
 40  133.222   eoip-test2  192.168.33.1:13231    192.168.112.10:13231  udp            74    2
 41  143.462   eoip-test2  192.168.33.1:13231    192.168.112.10:13231  udp            74    2
 42  153.702   eoip-test2  192.168.33.1:13231    192.168.112.10:13231  udp            74    2
 43  163.942   eoip-test2  192.168.33.1:13231    192.168.112.10:13231  udp            74    2
 44  174.182   eoip-test2  192.168.33.1:13231    192.168.112.10:13231  udp            74    2

Traffic flows without interruption. (This is the problem with dst-natting to the primary wan address, there the traffic is interrupted if the address is lost.)

If you would also want to initiate wg connections from the router side and do it over wan2, even if wan1 is available, this is the additional rule you’ll need: (Traffic may be interrupted for at most keepalive time when wan1 goes down.)

/ip firewall mangle
add action=mark-connection chain=output connection-mark=no-mark new-connection-mark=wg-wan2 protocol=udp src-port=13231