Source NAT just won't back-translate and I've tried everything

After spending hours over multiple evenings I'm just at my wits end once again and hoping someone knows what the heck is going on.

Background: A wireguard server on port 51820 should be accessible from multiple VRFs (at same address+port!). Sounds simple, right? Not with RouterOS. It's putting every conceivable stone in your way...

Before moving forward, three constraints:

  1. I do have existing route leaks between the VRFs which are working and they should remain untouched!
  2. The wireguard server should be accessible over the same IP address/port from multiple VRFs
  3. The responses from the wireguard server should have a defined, predictable source address

Based on this topic I can easily make port 51820 accessible from multiple VRFs but frustratingly wireguard generates response packets with source address 0.0.0.0 which means the source address comes from the local IP (or pref-src) of the first route lookup. Too bad this is a connected route where I couldn't even set a pref-src if I wanted, but in any case, the source address of the response packet is not necessarily the same as the destination address of the request packet. So, this doesn't satisfy #3.

So here's the outline of my solution, again loosely based on @CGGXANNX 's creative solution:

  1. Assign the wireguard connection to one of the VRFs (vrf-as). At this point, wireguard is perfectly accessible via vrf-as but not vrf-main:

    /interface wireguard add listen-port=51820 mtu=1360 name=wg-mobile vrf=vrf-as

  2. Create a bridge with two macvlan interfaces to interconnect the two VRFs, vrf-as and vrf-main:

    /interface bridge add name=br-xvrf protocol-mode=none
    /interface macvlan add comment="XVRF Hack 2: Wireguard" interface=br-xvrf mac-address=C2:0E:AD:A6:FF:A4 name=xvrf-as
    /interface macvlan add comment="XVRF Hack 2: Wireguard" interface=br-xvrf mac-address=42:AF:7F:C1:F3:F7 name=xvrf-main2
    /ip vrf add interfaces=lo,vlan3,vrrp3,xvrf-main2 name=vrf-main
    /ip vrf add interfaces=xvrf-as name=vrf-as
    /ip address add address=172.18.1.20/24 interface=xvrf-as network=172.18.1.0
    /ip address add address=172.18.1.21/24 interface=xvrf-main2 network=172.18.1.0

  3. Add a route from vrf-main to vrf-as via the macvlan bridge (per constraint #1 above, this route must not interfere with existing routes for anything in 10.227.79.0/24):

    /ip route add disabled=no distance=1 dst-address=192.0.2.222/32 gateway=172.18.1.20@vrf-main routing-table=vrf-main scope=30 target-scope
    =10

  4. Add a route for the return packet in vrf-as. Critically, Use pref-src to make sure that the return packets will have the correct source address (192.0.2.222):

    /ip route add distance=1 dst-address=172.18.1.21/32 gateway=xvrf-as@vrf-as pref-src=192.0.2.222 routing-table=vrf-as scope=30 target-scope=10

  5. Lastly, I must use SNAT so that the return packet goes through my macvlan tunnel (again, per constraint #1, only wireguard server should be captured):

    /ip firewall nat add action=src-nat chain=srcnat dst-address=192.0.2.222 dst-port=51820 in-interface-list=LAN protocol=udp src-address=10.227.79.0/24 to-addresses=172.18.1.21

Now here is the packet sniffer output:

/tool/sniffer/quick
Columns: INTERFACE, TIME, NUM, DIR, SRC-MAC, DST-MAC, VLAN, SRC-ADDRESS, DST-ADDRESS, PROTOCOL, SIZE, CPU
INTERFACE TIME NUM DIR SRC-MAC DST-MAC VLAN SRC-ADDRESS DST-ADDRESS PROTOCOL SIZE CPU
ether1 4.253 1 <- 70:8C:F2:C8:1A:F9 00:00:5E:00:01:03 3 10.227.79.104:61276 192.0.2.222:51820 ip:udp 194 0
br-main 4.253 2 <- 70:8C:F2:C8:1A:F9 00:00:5E:00:01:03 3 10.227.79.104:61276 192.0.2.222:51820 ip:udp 194 0
vlan3 4.253 3 <- 70:8C:F2:C8:1A:F9 00:00:5E:00:01:03 10.227.79.104:61276 192.0.2.222:51820 ip:udp 190 0
vrrp3 4.253 4 <- 70:8C:F2:C8:1A:F9 00:00:5E:00:01:03 10.227.79.104:61276 192.0.2.222:51820 ip:udp 190 0
xvrf-main2 4.253 5 -> 42:AF:7F:C1:F3:F7 C2:0E:AD:A6:FF:A4 172.18.1.21:61276 192.0.2.222:51820 ip:udp 190 0
br-xvrf 4.253 6 <- 42:AF:7F:C1:F3:F7 C2:0E:AD:A6:FF:A4 172.18.1.21:61276 192.0.2.222:51820 ip:udp 190 0
xvrf-as 4.253 7 <- 42:AF:7F:C1:F3:F7 C2:0E:AD:A6:FF:A4 172.18.1.21:61276 192.0.2.222:51820 ip:udp 190 0
vrf-as 4.253 8 -> BA:B9:5F:2D:46:9F BA:B9:5F:2D:46:9F 192.0.2.222:51820 172.18.1.21:61276 ip:udp 134 1
ether1 9.513 9 <- 70:8C:F2:C8:1A:F9 00:00:5E:00:01:03 3 10.227.79.104:61276 192.0.2.222:51820 ip:udp 194 0
br-main 9.513 10 <- 70:8C:F2:C8:1A:F9 00:00:5E:00:01:03 3 10.227.79.104:61276 192.0.2.222:51820 ip:udp 194 0
vlan3 9.513 11 <- 70:8C:F2:C8:1A:F9 00:00:5E:00:01:03 10.227.79.104:61276 192.0.2.222:51820 ip:udp 190 0
vrrp3 9.513 12 <- 70:8C:F2:C8:1A:F9 00:00:5E:00:01:03 10.227.79.104:61276 192.0.2.222:51820 ip:udp 190 0
xvrf-main2 9.513 13 -> 42:AF:7F:C1:F3:F7 C2:0E:AD:A6:FF:A4 172.18.1.21:61276 192.0.2.222:51820 ip:udp 190 0
br-xvrf 9.513 14 <- 42:AF:7F:C1:F3:F7 C2:0E:AD:A6:FF:A4 172.18.1.21:61276 192.0.2.222:51820 ip:udp 190 0
xvrf-as 9.513 15 <- 42:AF:7F:C1:F3:F7 C2:0E:AD:A6:FF:A4 172.18.1.21:61276 192.0.2.222:51820 ip:udp 190 0
vrf-as 9.514 16 -> BA:B9:5F:2D:46:9F BA:B9:5F:2D:46:9F 192.0.2.222:51820 172.18.1.21:61276 ip:udp 134 2

Everything makes sense: Host 10.227.79.104 (from vlan3 in vrf-main) is establishing a connection, it enters via ether1/br-main/vlan3/vrrp3. Then SNAT happens, as expected, the source address is translated to 172.18.1.21 and packet forwarded to xvrf-main2. It goes through the br-xvrf and eventually arrives in vrf-as via xvrf-as ... exactly as expected! Wireguard generates a response, again exactly as expected, it uses source address 192.0.2.222, again exactly as expected (due to out pref-src in the route). Packet is sent to 172.18.1.21 via vrf-as.

But then? Silence. Nothing else!

At the same time, I am not sure if it's related and expected but there are two conntrack entries:

/ip/firewall/connection/print where dst-port=51820
Flags: S - SEEN-REPLY; A - ASSURED; C - CONFIRMED; s - SRCNAT
Columns: PROTOCOL, SRC-ADDRESS, SRC-PORT, DST-ADDRESS, DST-PORT, TIMEOUT, ORIG-RATE, REPL-RATE, ORIG-PACKETS, REPL-PACKETS, ORIG-BYTES, REPL-BYTES

PROTOCOL  SRC-ADDRESS    SRC-PORT  DST-ADDRESS   DST-PORT  TIMEOUT  ORIG-RATE  REPL-RATE  ORIG-PACKETS  REPL-PACKETS  ORIG-BYTES  REPL-BYTES

0 SACs udp       172.18.1.21       61276  192.0.2.222     51820  2m17s    0bps       0bps                  5             5         880         600
1 SACs udp       10.227.79.104     61276  192.0.2.222     51820  2m17s    0bps       0bps                  5             5         880         600

I have exhausted my wildest imaginations: I have tried using DNAT hacks, mangle/route-mark hacks, different ports, using raw with "notrack" targets to avoid one of the conntrack entries and I even tried SNAT-ing to a non-local IP with the same trick from here.

Why is RouterOS trying sooo hard to make nothing with VRFs work as expected? :weary_face: :weary_face: :weary_face:

Why on earth is SNAT not working here?

That is a really interesting case. VRFs, WireGuard sockets, conntrack, and NAT together can create some very unexpected behavior on RouterOS.

One thing I would look at is separating the service endpoint from the VRF routing domain for example using a dedicated loopback/service address and controlled route leaking rather than trying to make the same WireGuard listener behave across multiple VRFs. Locally generated WireGuard replies can behave differently from normal forwarded traffic, which makes these scenarios tricky.

This type of problem is actually one of the reasons we built TunGuard. Not because WireGuard itself is difficult, but because once you add real-world requirements like multiple sites, NAT, CGNAT, provisioning, remote access, and migrations, the operational side becomes the hard part.

TunGuard takes a different approach: userspace WireGuard engine, automated peer management, MikroTik provisioning, and an optional SSH jump host for remote access.

If you are interested, the project is here:

https://mikrotik-provision.vercel.app

Would also be interested to hear how you ended up solving this VRF design.

First, like I already wrote in the other thread(s), the tunnel workaround with MACVLAN is only really needed when you need to communicate between one side being a router's service running in main, and the other in some other VRF(s). In your test setup, the communication is between two real VRFs (not main) so normally, the usual route leaking mechanism as documented by MikroTik can be use without the dummy bridge and the extra MACVLAN.

However, WireGuard is special with regards to the source address selection, you should read this post from @lurker888 RouterOS blatantly ignores pref-src. Can this really be a bug? - #15 by lurker888 as well as the discussion in that thread for more details. The solution to work-around this involves several NAT rules as you can read from here RouterOS blatantly ignores pref-src. Can this really be a bug? - #72 by lurker888 as well as the rest of that thread.

But wait! You knew all that because you were the one who started that thread! :smiley:


Having said that, if you still want to use / abuse the MACVLAN "tunnel" solution then what follow is a "solution" to be integrated with that MACVLAN stuff.

First I will start from the easiest, that requires almost no modification to the configuration with MACVLAN, but with a big constraint. The constraint is that your WG client don't need to use that specific 192.0.2.222 WG endpoint address, and has the ability to specify the endpoint address as a private (non-public) IP address, in short, the constraint is that the WG clients are in the network managed by your router, and not coming from the outside internet for example.

I just did a test configuration and nothing much to be done for it to work. In fact, starting from the test configuration of this post: Accessing router-hosted services (DNS, HTTP, etc) from a VRF - #9 by CGGXANNX a single additional SRCNAT rule was required. And I only linked to that post for the definition of the VRFs, the MACVLANs, and the addresses. The NAT or mangle rules from that post is not required (they are needed for accessing services in main only).

From the screenshot you can see that the Windows VM which has 192.168.88.254 as address is in the VRF tertiary. The WireGuard is set up to listen in the secondary interface. You can see that there aren't even any VRF route leaking between the two VRFs required. No routes from secondary to tertiary and vice versa.

WG client on Windows is working, and Windows is able to ping the address of the router inside WG (10.5.25.1).

Now if we apply that, starting from your configuration:

This #3 route above is not needed at all.

Same with this #4 route. It can be removed.

As for this SRCNAT rule at #5:

It's the only additional thing that is needed (as I wrote above), however the SRCNAT rule should be like this instead:

/ip firewall nat
add action=masquerade chain=srcnat dst-address=172.18.1.20 dst-port=51820 \
    protocol=udp out-interface=xvrf-main2

This is the only additional thing needed (and it replaces your #5)!

Now is the very important part: For the devices in vrf-main (those connected to the interfaces vlan3, vrrp3, etc... When they specify the remote endpoint of the WG server, they should use 172.18.1.20:51820, not 192.0.2.222:51820! And of course, it's preferable that you turn on the "Responder" checkbox in the setting of the peers of the wg-mobile interface.

That was the big constraint. The devices use the internal address inside the MACVLAN "network" as endpoint address. If the router acts as default gateway for those clients, then this work fine.


The reason why this work is:

  • The incoming UDP packet that tries to establish the handshake has destination 172.18.1.20:51820, it arrive at the router in the xvrf-main VRF. In this VRF, the best route for destination 172.18.1.0/24 is the connected route specifying xvrf-main2 as outgoing interface.

  • The packet, before going out through this interface, is handled by SRCNAT (the masquerade rule above), so its source address is changed to 172.18.1.21, the address attached to xvrf-main2.

  • It goes into the dummy bridges and come out at the side of the xvrf-as interface, because this interface has 172.18.1.20 as assigned address. This interface is also in vrf-as, so it's accepted and handled by WireGuard.

  • WireGuard will create the response packet, if you read the other thread, you'll know that this packet at the beginning has the destination address 172.18.1.21, but the source address is not yet defined.

  • Luckily for us, in vrf-as the best route for destination 172.18.1.21 is the connected route with xvrf-as specified as outgoing gateway. There is no ambiguity. Also, if this route is used and the packet has no source address yet, then there is only one best address to be used as source, which is 172.18.1.20 attached to xvrf-as.

  • As a result, the response packet will have source 172.18.1.20:51820 and destination address 172.18.1.21 and it will be put into the dummy bridge.

  • The packet will then arrive at the interface xvrf-main2, because this interface as 172.18.1.21 assigned. Here connection tracking will be able to find the matching pair 172.18.1.20:51820 and 172.18.1.21:xxxx previously marked as SRCNAT-ed. It will be able to undo the NAT-ing and restore the original address and port of the WG client in place of 172.18.1.21:xxxx. The packet will be routed back to the client.


So this pretty straightforward. We don't even need any VRF route leaking, because the routing between VRF is through the tunnel.

The reason we needed the SRCNAT masquerade rule is because it helps selecting the correct route for the answer packet at the vrf-as side. If you don't like that SRCNAT rule, you can remove it, however, in the routing table of vrf-as you should assign routes for destination address ranges of the WG client (the addresses in xvrf-main) with gateway=172.18.1.21@vrf-as, so that the response packet can be sent through the MACVLAN tunnel.


In the next post I will outline the few modifications needed, so that 192.0.2.222:51820 can be used to specify the endpoint address in the WG client, instead of 172.18.1.20:51820.

Now, what if the clients can't use 172.18.1.20 as endpoint, and absolutely have to use 192.0.2.222 and we want to stick to the MACVLAN tunnel workaround?

A first thought would be to simply add a DSTNAT rule, that transforms the destination 192.0.2.222:51820 to 172.18.1.20:51820. So the packets will have both DSTNAT and SRCNAT applied. Having both is normally not a problem, and it works like that elsewhere too. However not in this configuration. Probably because both of the two ends of the MACVLAN tunnel share the same connection tracking table that apparently has no separation between VRFs, having a connection with both DSTNAT and SRCNAT coming to the other end of the table, where it should be tracked again as another connection, somehow confuse conntrack and you get 3 conntrack entries appearing at the sametime, with some permutation of the source/destnation/reply source/reply destination, where two will quickly timeout and the remaining entry is a wrong one. As a result conntrack is unable to reverse the NAT-ing, and it doesn't work.

Which means that we can unfortunately not use the trivial solution of DSTNAT. To be able to use 192.0.2.222 as endpoint destination address, the other end must answer for that destination, as well as use that address as source for the return packet. And from other thread, we new that using pref-src is not effective.

So, the solution is to assign 192.0.2.222 as the main address of xvrf-as. There is no problem with assigning the same 192.0.2.222 address to multiple interfaces in the same VRF, so the other interface that currently has 192.0.2.222 assigned needs no modification. What we have to make sure, however, is that the two connected routes generated by the two /ip address assignments do not have the same dst-address, otherwise we'll create an ECMP group and that is problematic.

And we want WG to really use 192.0.2.222 as source address, and not sometime that, sometime 172.18.1.20. Which means the 192.0.2.222 address must really replace 172.18.1.20 in the /ip address assignment for xvrf-as, not existing side-by-side.

But we also don't want to have to change the other places that currently specify 172.18.1.20 as gateway address of the static routes. Which means we should proceed in the following order:

  • First, we don't want to lose the current connected route with dst-address=172.18.1.0/24 gateway=xvrf-as@vrf-as, this route normally disappears when we remove the 172.18.1.20/24 /ip address assignment. So we'll first add this static connected route:

    /ip route
    add dst-address=172.18.1.0/24 gateway=xvrf-as@vrf-as routing-table=vrf-as
    
  • We also don't want to have to modify the routes in other tables that currently use something like gateway=172.18.1.20@vrf-xxxxx, so we'll make sure the all the xvrf-xxxxx interfaces will reach xvrf-as for the destination 172.18.1.20. For that we'll add a static ARP entry for each of the other xvrf-xxxxx interfaces:

    /ip arp
    add address=172.18.1.20 interface=xvrf-main mac-address=<MAC-Address-of-xvrf-as>
    add address=172.18.1.20 interface=xvrf-main2 mac-address=<MAC-Address-of-xvrf-as>
    # ...
    
  • We can now add the 192.0.2.222 address to xvrf-as, but we'll add that as a /32 address, so that the connected route it produces does not interfere with the routes created by the assignment of the real original interface that also has 192.0.2.222:

    /ip address
    add address=192.0.2.222 interface=xvrf-as network=192.0.2.222
    
  • After this step, we can safely remove this old IP address assignment:

    The address 172.18.1.20 is no longer assigned to xvrf-as and everything should still work.

  • Next, we need a route in vrf-main that directs packets for destination 192.0.2.222 through the tunnel towards the other VRF:

    /ip route
    add dst-address=192.0.2.222/32 gateway=xvrf-main2@vrf-main routing-table=vrf-main
    
  • Of course, now we have to modify the masquerade rule from the previous post, so that it now uses the condition dst-address=192.0.2.222 instead of dst-address=172.18.1.20:

    /ip firewall nat
    add action=masquerade chain=srcnat dst-address=192.0.2.222 dst-port=51820 \
        protocol=udp out-interface=xvrf-main2
    

That's all the modifications needed. Devices in vrf-main should be able to specify 192.0.2.222:51820 as endpoint for the WG peer setting.

Similar to what was said in the previous post. If you don't like to apply SRCNAT (the masquerade rule) and want to keep the source addresses (real addresses of the clients) then you can remove the masquerade rule if you add routes for all the needed destinations with gateway=172.18.1.21@vrf-as routing-table=vrf-as.

Once again, thank you so much for taking so much time and the thorough response.

Let me just first clarify the background: I would like the VPN (wireguard) server to be available from everywhere, no matter if internal or external networks, internet under 192.0.2.222:51820. To add a bit complication, the actual IP of the router is actually 192.0.2.210. However, there is/will be a backup router 192.0.2.211 which operates with VRRP on 192.0.2.222.

Indeed I studied the old thread again and this is how I got things working before I started with all of this VRF mess.

Having said that, it's not that I want to use/abuse the MACVLAN solution for this, but I was thinking it's the only way it can work.

I actually got something very similar to your first suggestion working already, even without SNAT but the issue is that it's just not the public IP then.

Unfortunately with your second solution, I am still not able to make it work. In fact, I run into the same issue from the original post In this thread: I see the response packet going out of vrf-as but nothing more:

/tool/sniffer/quick
Columns: INTERFACE, TIME, NUM, DIR, SRC-MAC, DST-MAC, VLAN, SRC-ADDRESS, DST-ADDRESS, PROTOCOL, SIZE, CPU
INTERFACE TIME NUM DIR SRC-MAC DST-MAC VLAN SRC-ADDRESS DST-ADDRESS PROTOCOL SIZE CPU
ether1 5.514 1 <- 70:8C:F2:C8:1A:F9 00:00:5E:00:01:03 3 10.227.79.104:62090 192.0.2.222:51820 ip:udp 194 2
br-main 5.514 2 <- 70:8C:F2:C8:1A:F9 00:00:5E:00:01:03 3 10.227.79.104:62090 192.0.2.222:51820 ip:udp 194 2
vlan3 5.514 3 <- 70:8C:F2:C8:1A:F9 00:00:5E:00:01:03 10.227.79.104:62090 192.0.2.222:51820 ip:udp 190 2
vrrp3 5.514 4 <- 70:8C:F2:C8:1A:F9 00:00:5E:00:01:03 10.227.79.104:62090 192.0.2.222:51820 ip:udp 190 2
xvrf-main2 5.514 5 -> 42:AF:7F:C1:F3:F7 C2:0E:AD:A6:FF:A4 172.18.1.21:62090 192.0.2.222:51820 ip:udp 190 2
br-xvrf 5.514 6 <- 42:AF:7F:C1:F3:F7 C2:0E:AD:A6:FF:A4 172.18.1.21:62090 192.0.2.222:51820 ip:udp 190 2
xvrf-as 5.514 7 <- 42:AF:7F:C1:F3:F7 C2:0E:AD:A6:FF:A4 172.18.1.21:62090 192.0.2.222:51820 ip:udp 190 2
vrf-as 5.515 8 -> BA:B9:5F:2D:46:9F BA:B9:5F:2D:46:9F 192.0.2.222:51820 172.18.1.21:62090 ip:udp 134 3

The logs show that wireguard responds:

And this is the conntrack table:

/ip/firewall/connection/print where dst-port=51820
Flags: S - SEEN-REPLY; A - ASSURED; C - CONFIRMED; s - SRCNAT
Columns: PROTOCOL, SRC-ADDRESS, SRC-PORT, DST-ADDRESS, DST-PORT, TIMEOUT, ORIG-RATE, REPL-RATE, ORIG-PACKETS, REPL-PACKETS, ORIG-BYTES, REPL-BYTES

PROTOCOL  SRC-ADDRESS    SRC-PORT  DST-ADDRESS   DST-PORT  TIMEOUT  ORIG-RATE  REPL-RATE  ORIG-PACKETS  REPL-PACKETS  ORIG-BYTES  REPL-BYTES

0 SACs udp       172.18.1.21       62090  192.0.2.222      51820  2m57s    0bps       0bps                 15            15       2 640       1 800
1 SACs udp       10.227.79.104     62090  192.0.2.222      51820  2m57s    0bps       0bps                 15            15       2 640       1 800

I am aware that with this setup I have the ECMP route that you mentioned to avoid because 192.0.2.222 is also /32 on the VRRP interface. However, I removed it and it made no change. I don't think it's related to this.

So I think there must be something else wrong.

Now, if I think about it, I believe your second proposal is pretty much the same as the proposal in my initial post. The only difference is that your solution avoids the pref-src issue.

I am not sure which other thread you are referring to (this one?) but I think this doesnt apply here: As explained in that linked thread, there are two route lookups, an initial one (which sets pref-src) and a route adjustment step. The latter one is used for policy routing. In my original question I had the issue that the route was picked at the route adjustment stage (due to complex routing scenarios) but at this point the source address was already selected.

Now, #4 in original post of this thread does generate a route which is indeed consulted in the very initial routing step which is used to set the source address. You can see that it works, in original post wireguard is indeed answering from 192.0.2.222, as expected.

I tried removing that route and/or pref-src and it stopped working, so I'm pretty sure that's doing its job.

The issue that's left in both our cases is the same: Wireguard's response packet from 192.0.2.222 to 172.18.1.21 is stuck somewhere and does not proceed beyond vrf-as in both cases.

Have you tried out your proposal and did it work? If so, there must still be something wrong with my config.

If not, I am afraid hitting yet another RouterOS bug, possibly what you were saying here already:

To avoid unnecessary guesswork, I am sharing attached my full config for reference.

# 2026-08-02 18:03:59 by RouterOS 7.23.2
#
/interface bridge add name=br-main vlan-filtering=yes
/interface bridge add comment="XVRF Hack 1: Mikrotik" name=br-xvrf protocol-mode=none
/interface ethernet set [ find default-name=ether1 ] disable-running-check=no
/interface vlan add comment=FTTH interface=br-main name=vlan2 vlan-id=2
/interface vlan add comment=LAN interface=br-main name=vlan3 vlan-id=3
/interface vlan add comment=SRV interface=br-main name=vlan4 vlan-id=4
/interface vlan add comment=WWAN interface=br-main name=vlan33 vlan-id=33
/interface vlan add comment=DMZ44 interface=br-main name=vlan44 vlan-id=44
/interface vlan add comment=ADM interface=br-main name=vlan99 vlan-id=99
/interface macvlan add comment="XVRF Hack 2: Wireguard" interface=br-xvrf mac-address=C2:0E:AD:A6:FF:A4 name=xvrf-as
/interface macvlan add comment="XVRF Hack 1: Mikrotik" interface=br-xvrf mac-address=42:1F:9F:D0:56:08 name=xvrf-fiber
/interface macvlan add comment="XVRF Hack 1: Mikrotik" interface=br-xvrf mac-address=72:F1:22:8D:C8:A1 name=xvrf-main
/interface macvlan add comment="XVRF Hack 2: Wireguard" interface=br-xvrf mac-address=42:AF:7F:C1:F3:F7 name=xvrf-main2
/interface vrrp add interface=vlan3 name=vrrp3 sync-connection-tracking=yes vrid=3
/interface vrrp add interface=vlan4 name=vrrp4 vrid=4
/interface vrrp add interface=vlan44 name=vrrp44 vrid=44
/interface vrrp add interface=vlan99 name=vrrp99 vrid=99
/interface list add name=LAN
/interface list add comment="Admin Interfaces" name=ADM
/interface list add name=DMZ
/interface list add name=SRV
/interface list add name=WAN
/interface list add include=ADM,DMZ,LAN,SRV name=trusted
/interface list add name=routing_peers
/interface list add name=WANAS
/interface list add name=ISP
/interface list add comment="Interfaces with lower MTU, to fix MSS" name=mss_fix
/interface list add name=VLAN_33
/interface list add name=MOBILE
/interface list add comment="All interfaces that have 192.0.2 subnets" include=DMZ,WANAS name=MY_PREFIX
/interface list add include=LAN,SRV name=mdns_interfaces
/ip vrf add interfaces=vlan2,xvrf-fiber name=vrf-fiber
/ip vrf add interfaces=vlan33 name=vrf-lte
/interface wireguard add listen-port=51820 mtu=1360 name=wg-mobile vrf=vrf-as
/interface bridge port add bridge=br-main interface=ether1
/ip firewall connection tracking set enabled=yes
/ip neighbor discovery-settings set discover-interface-list=none lldp-med-net-policy-vlan=1
/interface bridge vlan add bridge=br-main tagged=br-main,ether1 vlan-ids=2
/interface bridge vlan add bridge=br-main tagged=br-main,ether1 vlan-ids=3
/interface bridge vlan add bridge=br-main tagged=br-main,ether1 vlan-ids=4
/interface bridge vlan add bridge=br-main tagged=br-main,ether1 vlan-ids=44
/interface bridge vlan add bridge=br-main tagged=br-main,ether1 vlan-ids=33
/interface bridge vlan add bridge=br-main tagged=br-main,ether1 vlan-ids=99
/interface detect-internet set internet-interface-list=static lan-interface-list=static wan-interface-list=static
/interface list member add interface=vlan3 list=LAN
/interface list member add interface=vlan99 list=ADM
/interface list member add interface=vlan2 list=WAN
/interface list member add interface=vlan44 list=DMZ
/interface list member add interface=vrrp3 list=LAN
/interface list member add interface=vlan4 list=SRV
/interface list member add interface=vlan2 list=ISP
/interface list member add interface=vlan33 list=ISP
/interface list member add interface=vrrp44 list=DMZ
/interface list member add interface=vrrp99 list=ADM
/interface list member add interface=vrrp4 list=SRV
/interface list member add interface=wg-mobile list=mss_fix
/interface list member add interface=wg-mobile list=MOBILE
/interface wireguard peers add allowed-address=10.227.33.10/32 client-address=10.227.33.10/24 client-allowed-address=::/0 interface=wg-mobile name=client1 public-key="Z1HVcPkTEdfVoDdgQmiBVzCqHY1+qZowPuRWeZr8gQQ=" responder=yes
/ip address add address=10.227.1.2/24 interface=vlan99 network=10.227.1.0
/ip address add address=10.227.4.2/24 interface=vlan4 network=10.227.4.0
/ip address add address=10.227.79.2/24 interface=vlan3 network=10.227.79.0
/ip address add address=192.0.2.210/28 interface=vlan44 network=192.0.2.208
/ip address add address=10.227.79.254 interface=vrrp3 network=10.227.79.254
/ip address add address=10.227.33.1/24 interface=wg-mobile network=10.227.33.0
/ip address add address=10.227.1.254 interface=vrrp99 network=10.227.1.254
/ip address add address=10.227.4.254 interface=vrrp4 network=10.227.4.254
/ip address add address=192.168.5.102/24 interface=vlan33 network=192.168.5.0
/ip address add address=192.0.2.222 interface=vrrp44 network=192.0.2.222
/ip address add address=172.18.0.20/24 comment="XVRF Hack 1: Mikrotik" interface=xvrf-main network=172.18.0.0
/ip address add address=172.18.0.21/24 comment="XVRF Hack 1: Mikrotik" interface=xvrf-fiber network=172.18.0.0
/ip address add address=192.0.2.222 interface=xvrf-as network=192.0.2.222
/ip address add address=172.18.1.21/24 interface=xvrf-main2 network=172.18.1.0
/ip arp add address=172.18.0.22 comment="VRF crossing: update" interface=xvrf-fiber mac-address=72:F1:22:8D:C8:A1
/ip arp add address=172.18.1.20 comment="#Hack Wireguard" interface=xvrf-main2 mac-address=C2:0E:AD:A6:FF:A4
/ip dhcp-client add check-gateway=ping default-route-tables=vrf-lte !dhcp-options interface=vlan33 name=client-lte use-peer-dns=no use-peer-ntp=no
/ip dhcp-client add check-gateway=ping default-route-tables=vrf-fiber interface=vlan2 name=client-fiber use-peer-ntp=no
/ip firewall address-list add address=10.227.0.0/16 list=own_hosts
/ip firewall address-list add address=192.0.2.0/24 list=own_hosts
/ip firewall address-list add address=0.0.0.0/8 comment="defconf: RFC6890" list=no_forward_ipv4
/ip firewall address-list add address=169.254.0.0/16 comment="defconf: RFC6890" list=no_forward_ipv4
/ip firewall address-list add address=224.0.0.0/4 comment="defconf: multicast" list=no_forward_ipv4
/ip firewall address-list add address=255.255.255.255 comment="defconf: RFC6890" list=no_forward_ipv4
/ip firewall address-list add address=127.0.0.0/8 comment="defconf: RFC6890" list=bad_ipv4
/ip firewall address-list add address=192.0.0.0/24 comment="defconf: RFC6890" list=bad_ipv4
/ip firewall address-list add address=192.0.2.0/24 comment="defconf: RFC6890 documentation" list=bad_ipv4
/ip firewall address-list add address=198.51.100.0/24 comment="defconf: RFC6890 documentation" list=bad_ipv4
/ip firewall address-list add address=203.0.113.0/24 comment="defconf: RFC6890 documentation" list=bad_ipv4
/ip firewall address-list add address=240.0.0.0/4 comment="defconf: RFC6890 reserved" list=bad_ipv4
/ip firewall address-list add address=0.0.0.0/8 comment="defconf: RFC6890" list=not_global_ipv4
/ip firewall address-list add address=10.0.0.0/8 comment="defconf: RFC6890" list=not_global_ipv4
/ip firewall address-list add address=100.64.0.0/10 comment="defconf: RFC6890" list=not_global_ipv4
/ip firewall address-list add address=169.254.0.0/16 comment="defconf: RFC6890" list=not_global_ipv4
/ip firewall address-list add address=172.16.0.0/12 comment="defconf: RFC6890" list=not_global_ipv4
/ip firewall address-list add address=192.0.0.0/29 comment="defconf: RFC6890" list=not_global_ipv4
/ip firewall address-list add address=192.168.0.0/16 comment="defconf: RFC6890" list=not_global_ipv4
/ip firewall address-list add address=198.18.0.0/15 comment="defconf: RFC6890 benchmark" list=not_global_ipv4
/ip firewall address-list add address=255.255.255.255 comment="defconf: RFC6890" list=not_global_ipv4
/ip firewall address-list add address=224.0.0.0/4 comment="defconf: multicast" list=bad_src_ipv4
/ip firewall address-list add address=255.255.255.255 comment="defconf: RFC6890" list=bad_src_ipv4
/ip firewall address-list add address=0.0.0.0/8 comment="defconf: RFC6890" list=bad_dst_ipv4
/ip firewall address-list add address=224.0.0.0/4 comment="defconf: RFC6890" list=bad_dst_ipv4
/ip firewall address-list add address=10.227.0.0/16 list=private_lans
/ip firewall address-list add address=192.168.0.0/16 list=private_lans
/ip firewall address-list add address=10.227.4.10 list=hairpin_dst
/ip firewall address-list add address=10.227.4.20 list=hairpin_dst
/ip firewall address-list add address=10.227.0.0/16 list=hairpin_src
/ip firewall address-list add address=192.0.2.210 list=localhost_as
/ip firewall address-list add address=192.0.2.222 list=localhost_as
/ip firewall filter add action=accept chain=input comment="defconf: accept ICMP after RAW" protocol=icmp
/ip firewall filter add action=accept chain=input comment="defconf: accept established,related,untracked" connection-state=established,related,untracked
/ip firewall filter add action=accept chain=input comment="SSH access from everywhere" dst-port=1983 protocol=tcp
/ip firewall filter add action=accept chain=input comment="Wireguard server" dst-port=51820 protocol=udp
/ip firewall filter add action=accept chain=input comment=Winbox dst-port=8291 protocol=tcp src-address-list=private_lans
/ip firewall filter add action=accept chain=input comment="DNS server for own hosts" dst-port=53 protocol=udp src-address-list=own_hosts
/ip firewall filter add action=accept chain=input dst-port=53 protocol=tcp src-address-list=own_hosts
/ip firewall filter add action=accept chain=input comment="API (checkmk)" dst-port=8728 in-interface-list=SRV protocol=tcp src-address=10.227.4.12
/ip firewall filter add action=accept chain=input comment="SNMP (checkmk)" dst-port=161,162 in-interface-list=SRV protocol=udp src-address=10.227.4.12
/ip firewall filter add action=reject chain=input comment="defconf: drop all not coming from trusted" in-interface-list=!trusted reject-with=icmp-port-unreachable
/ip firewall filter add action=drop chain=input comment="Log & block everything else" log=yes log-prefix="[BLOCKED-IN]"
/ip firewall filter add action=accept chain=forward comment="XVRF: Allow ssh access from vrf-as" dst-port=1983 out-interface=vrf-main protocol=tcp src-address=192.0.2.0/24
/ip firewall filter add action=accept chain=forward comment="XVRF: Allow dns access from vrf-as" dst-port=53 out-interface=vrf-main protocol=udp src-address=192.0.2.0/24
/ip firewall filter add action=accept chain=forward comment="XVRF: Allow wireguard access from vrf-main. TODO: restrict again" dst-port=51820 protocol=udp
/ip firewall filter add action=accept chain=forward comment="XVRF: Allow wireguard access from vrf-main. TODO: restrict again" protocol=udp src-port=51820
/ip firewall filter add action=accept chain=forward comment="XVRF Hack 1: Mikrotik" dst-address=159.148.147.0/24
/ip firewall filter add action=accept chain=forward comment="defconf: accept established,related, untracked" connection-state=established,related,untracked
/ip firewall filter add action=jump chain=forward comment=traffic_zones connection-state=new jump-target=traffic_zones
/ip firewall filter add action=drop chain=forward comment="defconf: drop invalid" connection-state=invalid log=yes log-prefix=INVALID
/ip firewall filter add action=drop chain=forward comment="defconf:  drop all from WAN not DSTNATed" connection-nat-state=!dstnat connection-state=new in-interface-list=WAN
/ip firewall filter add action=drop chain=forward comment="defconf: drop bad forward IPs" src-address-list=no_forward_ipv4
/ip firewall filter add action=drop chain=forward comment="defconf: drop bad forward IPs" dst-address-list=no_forward_ipv4
/ip firewall filter add action=accept chain=forward comment="Check if counters >0. If not, DROP here" log=yes log-prefix="[ACPT]"
/ip firewall filter add action=accept chain=traffic_zones comment=LAN->VPN in-interface-list=LAN out-interface-list=MOBILE
/ip firewall filter add action=accept chain=traffic_zones comment=LAN->SRV in-interface-list=LAN out-interface-list=SRV
/ip firewall filter add action=accept chain=traffic_zones comment="LAN->ADM (ToBeRestricted!!)" in-interface-list=LAN out-interface-list=ADM
/ip firewall filter add action=accept chain=traffic_zones comment=VPN->LAN in-interface-list=MOBILE out-interface-list=LAN
/ip firewall filter add action=accept chain=traffic_zones comment=VPN->SRV in-interface-list=MOBILE out-interface-list=SRV
/ip firewall filter add action=accept chain=traffic_zones comment="VPN->ADM (ToBeRestricted!!)" in-interface-list=MOBILE out-interface-list=ADM
/ip firewall mangle add action=mark-routing chain=output comment="XVRF PortFW: ssh" new-routing-mark=vrf-as out-interface=vrf-as protocol=tcp src-port=1983
/ip firewall mangle add action=mark-routing chain=output comment="XVRF PortFW: dns" new-routing-mark=vrf-as out-interface=vrf-as protocol=udp src-port=53
/ip firewall mangle add action=change-mss chain=forward comment="MSS/MTU fix for wg" new-mss=1380 out-interface-list=mss_fix protocol=tcp tcp-flags=syn tcp-mss=1381-65535
/ip firewall nat add action=dst-nat chain=dstnat comment="XVRF: Access ssh from vrf-as as 192.0.2.210" dst-address=192.0.2.222 dst-port=1983 in-interface-list=MY_PREFIX protocol=tcp src-address=192.0.2.0/24 to-addresses=10.227.4.254
/ip firewall nat add action=dst-nat chain=dstnat comment="XVRF: Access dns from vrf-as as 192.0.2.210" dst-address=192.0.2.222 dst-port=53 in-interface-list=MY_PREFIX protocol=udp src-address=192.0.2.0/24 to-addresses=10.227.4.254
/ip firewall nat add action=src-nat chain=srcnat comment="XVRF Hack 1: main VRF to fiber" out-interface=xvrf-main to-addresses=172.18.0.22
/ip firewall nat add action=masquerade chain=srcnat comment="XVRF Hack 1: main VRF to fiber" src-address=172.18.0.22
/ip firewall nat add action=masquerade chain=srcnat comment="Hairpin all PortFWs" dst-address-list=hairpin_dst src-address-list=hairpin_src
/ip firewall nat add action=masquerade chain=srcnat comment="defconf: masquerade WAN interface" out-interface=vlan2
/ip firewall nat add action=masquerade chain=srcnat comment="defconf: masquerade WAN interface" out-interface=vlan33
/ip firewall nat add action=src-nat chain=srcnat comment="srcnat WAN interface" out-interface-list=WANAS src-address=!192.0.2.0/24 to-addresses=192.0.2.210
/ip firewall raw add action=accept chain=prerouting comment="defconf: enable for transparent firewall"
/ip firewall raw add action=accept chain=prerouting comment="defconf: accept DHCP discover" dst-address=255.255.255.255 dst-port=67 in-interface-list=LAN protocol=udp src-address=0.0.0.0 src-port=68
/ip firewall raw add action=drop chain=prerouting comment="defconf: drop bogon IP's" src-address-list=bad_ipv4
/ip firewall raw add action=drop chain=prerouting comment="defconf: drop bogon IP's" dst-address-list=bad_ipv4
/ip firewall raw add action=drop chain=prerouting comment="defconf: drop bogon IP's" src-address-list=bad_src_ipv4
/ip firewall raw add action=drop chain=prerouting comment="defconf: drop bogon IP's" dst-address-list=bad_dst_ipv4
/ip firewall raw add action=drop chain=prerouting comment="defconf: drop non global from WAN" in-interface-list=WAN src-address-list=not_global_ipv4
/ip firewall raw add action=drop chain=prerouting comment="defconf: drop forward to local lan from WAN" dst-address-list=private_lans in-interface-list=WAN
/ip firewall raw add action=drop chain=prerouting comment="defconf: drop local if not from default IP range" in-interface-list=LAN src-address=!10.227.79.0/24
/ip firewall raw add action=drop chain=prerouting comment="defconf: drop bad UDP" port=0 protocol=udp
/ip firewall raw add action=jump chain=prerouting comment="defconf: jump to ICMP chain" jump-target=icmp4 protocol=icmp
/ip firewall raw add action=jump chain=prerouting comment="defconf: jump to TCP chain" jump-target=bad_tcp protocol=tcp
/ip firewall raw add action=accept chain=prerouting comment="defconf: accept everything else from LAN" in-interface-list=trusted
/ip firewall raw add action=accept chain=prerouting comment="defconf: accept everything else from WAN" in-interface-list=WAN
/ip firewall raw add action=drop chain=prerouting comment="defconf: drop the rest"
/ip firewall raw add action=drop chain=bad_tcp comment="defconf: TCP flag filter" protocol=tcp tcp-flags=!fin,!syn,!rst,!ack
/ip firewall raw add action=drop chain=bad_tcp comment=defconf protocol=tcp tcp-flags=fin,syn
/ip firewall raw add action=drop chain=bad_tcp comment=defconf protocol=tcp tcp-flags=fin,rst
/ip firewall raw add action=drop chain=bad_tcp comment=defconf protocol=tcp tcp-flags=fin,!ack
/ip firewall raw add action=drop chain=bad_tcp comment=defconf protocol=tcp tcp-flags=fin,urg
/ip firewall raw add action=drop chain=bad_tcp comment=defconf protocol=tcp tcp-flags=syn,rst
/ip firewall raw add action=drop chain=bad_tcp comment=defconf protocol=tcp tcp-flags=rst,urg
/ip firewall raw add action=drop chain=bad_tcp comment="defconf: TCP port 0 drop" port=0 protocol=tcp
/ip firewall raw add action=accept chain=icmp4 comment="defconf: echo reply" icmp-options=0:0 limit=5,10:packet protocol=icmp
/ip firewall raw add action=accept chain=icmp4 comment="defconf: net unreachable" icmp-options=3:0 protocol=icmp
/ip firewall raw add action=accept chain=icmp4 comment="defconf: host unreachable" icmp-options=3:1 protocol=icmp
/ip firewall raw add action=accept chain=icmp4 comment="defconf: protocol unreachable" icmp-options=3:2 protocol=icmp
/ip firewall raw add action=accept chain=icmp4 comment="defconf: port unreachable" icmp-options=3:3 protocol=icmp
/ip firewall raw add action=accept chain=icmp4 comment="defconf: fragmentation needed" icmp-options=3:4 protocol=icmp
/ip firewall raw add action=accept chain=icmp4 comment="defconf: echo" icmp-options=8:0 limit=5,10:packet protocol=icmp
/ip firewall raw add action=accept chain=icmp4 comment="defconf: time exceeded " icmp-options=11:0-255 protocol=icmp
/ip firewall raw add action=drop chain=icmp4 comment="defconf: drop other icmp" protocol=icmp
/ip route add comment="To reach LM1500" disabled=no distance=1 dst-address=192.168.5.1/32 gateway=vlan33@vrf-lte routing-table=vrf-main scope=30 target-scope=10
/ip route add disabled=no distance=1 dst-address=10.227.1.0/24 gateway=vlan99@vrf-main routing-table=vrf-fiber scope=30 target-scope=10
/ip route add disabled=no distance=1 dst-address=10.227.4.0/24 gateway=vlan4@vrf-main routing-table=vrf-fiber scope=30 target-scope=10
/ip route add disabled=no distance=1 dst-address=10.227.33.0/24 gateway=wg-mobile@vrf-main routing-table=vrf-fiber scope=30 target-scope=10
/ip route add disabled=no distance=1 dst-address=10.227.79.0/24 gateway=vlan3@vrf-main routing-table=vrf-fiber scope=30 target-scope=10
/ip route add comment="VRF crossing: Mikrotik" disabled=no distance=1 dst-address=159.148.147.0/24 gateway=172.18.0.21%xvrf-main routing-table=main scope=30 target-scope=10
/ip route add disabled=no distance=1 dst-address=10.227.4.0/24 gateway=vlan4@vrf-main routing-table=vrf-as scope=30 target-scope=10
/ip route add disabled=no distance=1 dst-address=10.227.33.0/24 gateway=wg-mobile@vrf-main routing-table=vrf-as scope=30 target-scope=10
/ip route add disabled=no distance=1 dst-address=10.227.79.0/24 gateway=vlan3@vrf-main routing-table=vrf-as scope=30 target-scope=10
/ip route add comment="TODO: replace by dynamic route leaking (all 192.0.2/24)" disabled=no distance=1 dst-address=192.0.2.208/28 gateway=vlan44@vrf-as routing-table=vrf-main scope=30 target-scope=10
/ip route add comment="For making watchdog work" disabled=no distance=1 dst-address=8.8.8.8/32 gateway=vlan2@vrf-fiber routing-table=main scope=30 target-scope=10
/ip route add comment="For making watchdog work" disabled=no distance=10 dst-address=8.8.8.8/32 gateway=vlan33@vrf-lte routing-table=main scope=30 target-scope=10
/ip route add disabled=no distance=1 dst-address=10.227.1.0/24 gateway=vlan99@vrf-main routing-table=vrf-lte scope=30 target-scope=10
/ip route add disabled=no distance=1 dst-address=10.227.4.0/24 gateway=vlan4@vrf-main routing-table=vrf-lte scope=30 target-scope=10
/ip route add disabled=no distance=1 dst-address=10.227.33.0/24 gateway=wg-mobile@vrf-main routing-table=vrf-lte scope=30 target-scope=10
/ip route add disabled=no distance=1 dst-address=10.227.79.0/24 gateway=vlan3@vrf-main routing-table=vrf-lte scope=30 target-scope=10
/ip route add comment="XVRF: Access services@vrf-main from vrf-as" disabled=no distance=1 dst-address=10.227.4.2/32 gateway=vrf-main@vrf-main routing-table=vrf-as scope=30 target-scope=10
/ip route add comment="XVRF: Access services@vrf-main from vrf-as" disabled=no distance=1 dst-address=10.227.4.254/32 gateway=vrf-main@vrf-main routing-table=vrf-as scope=30 target-scope=10
/ip route add disabled=no dst-address=172.18.2.1/32 gateway=vrf-as@vrf-as routing-table=vrf-main
/ip route add disabled=no dst-address=172.16.10.0/30 gateway=vrf-as@vrf-as routing-table=vrf-main
/ip route add disabled=no distance=1 dst-address=172.18.1.0/24 gateway=xvrf-as@vrf-as routing-table=vrf-as scope=30 target-scope=10
/ip route add comment="XVRF: Access services@vrf-as from vrf-main (wireguard)" disabled=no distance=1 dst-address=192.0.2.222/32 gateway=xvrf-main2@vrf-main routing-table=vrf-main scope=30 target-scope=10
/ip route add disabled=no distance=1 dst-address=10.227.79.104/32 gateway=xvrf-as@vrf-as routing-table=vrf-as scope=30 target-scope=10
/ip vrf add interfaces=vlan99,vlan4,vlan3,vrrp3,wg-mobile,vrrp99,vrrp4,xvrf-main2 name=vrf-main
/ip vrf add interfaces=vlan44,vrrp44,xvrf-as name=vrf-as
/routing rule add action=lookup disabled=no min-prefix=0 table=main
/routing rule add action=lookup chain=custom_vrf_main disabled=no dst-address=0.0.0.0/0 interface=vrf-main table=vrf-main
/routing rule add action=lookup chain=custom_vrf_main disabled=no dst-address=0.0.0.0/0 interface=vrf-main table=vrf-fiber
/routing rule add action=lookup chain=custom_vrf_main disabled=no dst-address=0.0.0.0/0 interface=vrf-main table=vrf-lte
/routing settings set policy-rules=mangle,custom_vrf_main,vrf-lookup,vrf-unreach,local,user,main

Yes, that should not be the problem with the setup above.

I've not had time yes to fully read your post and study your posted configuration, but about this question then yes, it was tested on the Frankenstein CHR setup made up for the MACVLAN tunnel adventure from previous threads. The setup has:

  • WAN (DHCP client) on ether1, which is in the wan VRF, it needs this for the router to be able to go to the internet to check for update, sync time, etc...

  • The is secondary VRF which has ether3 and the 172.20.80.2/24 subnet. This is where I also have the PC that manages the CHR.

  • The main LAN (with DHCP server) is in the tertiary VRF, has the 192.168.88.1/24 subnet on ether2.

  • Router services like DNS, WinBox, WebFig are in main, so it uses the trick from here to be able to reach them from other VRFs (like from LAN in tertiary, or from my PC 172.20.80.1 in secondary).

  • There is PPPoE server which put the clients in the tertiary VRF. These client needs to access the internet, so there are scripts that add the return route to them from wan. PPPoE clients are put in the 192.168.99.0/24 subnet.

  • The WireGuard interface is created like this:

    /interface wireguard
    add listen-port=51820 mtu=1420 name=wg1 vrf=secondary
    

    Which means from my PC at 172.20.80.1 I can make WG connection to the router at 172.20.80.2 (on ether3) being in the secondary VRF.

    This 172.20.80.2 plays the role of your 192.0.2.222 address, and is reachable from outside of the router, if plugged to the right port(s).

    The interface wg1 itself as well as the subnet inside the tunnel (10.5.25.1/24), is in the main VRF, but this is not important for the test, you can move the wg1 interface to other VRFs if you want. What important is the vrf=secondary parameter on the interface definition above, because that determines the VRF of the WireGuard UDP wrapper packets.

  • The vrf-link bridge is created with 4 MACVLAN interfaces, vrf-link-main, vrf-link-secondary, vrf-link-tertiary, vrf-link-wan. They originally have the addresses 172.18.0.20-23 assigned. However with the change made for WireGuard as described in the post above, 172.18.0.21 has been removed from vrf-link-secondary, replaced by the address that should be used for accessing WG, which is 172.20.80.2 (same as address on ether3). 172.18.0.21 now only exists as ARP response that map to the MAC address of vrf-link-secondary (see ARP table).

With the setup, a Win11 client which is in the LAN network (tertiary VRF) with address (obtained via DHCP) 192.168.88.254 can establish a WG connection to 172.20.80.2 (which is in secondary), and get a working tunnel. I can ping 10.5.25.1 from the Win11 machine with address inside tunnel 10.5.25.2. On the router, the peer show correct last handshake and current endpoint information.

Similarly, a PPPoE client (192.168.99.x, which is in tertiary too) is be able to make WG tunnel to 172.20.80.2 too.

Here is the full export without sensitive information from the CHR:

# 2026-08-03 06:19:33 by RouterOS 7.23.2
# system id = **********
#
/interface bridge
add name=vrf-link protocol-mode=none
/interface ethernet
set [ find default-name=ether1 ] disable-running-check=no
set [ find default-name=ether2 ] disable-running-check=no
set [ find default-name=ether3 ] disable-running-check=no
set [ find default-name=ether4 ] disable-running-check=no
/interface macvlan
add interface=vrf-link mac-address=76:CE:75:BF:FD:3F name=vrf-link-main
add interface=vrf-link mac-address=0E:31:55:68:24:DD name=vrf-link-secondary
add interface=vrf-link mac-address=EA:00:F2:A9:1E:6A name=vrf-link-tertiary
add interface=vrf-link mac-address=FE:B7:3A:A6:7D:33 name=vrf-link-wan
/interface list
add name=PPPOEIN
add include=PPPOEIN name=VRFs
/ip pool
add name=dhcp_pool0 ranges=192.168.88.20-192.168.88.254
add name=pppoe-pool ranges=192.168.99.20-192.168.99.254
/ip dhcp-server
add address-pool=dhcp_pool0 interface=ether2 name=dhcp1
/ip vrf
add interfaces=ether1,vrf-link-wan name=wan
add interfaces=ether2,vrf-link-tertiary,PPPOEIN name=tertiary
add interfaces=ether3,vrf-link-secondary name=secondary
/interface wireguard
add listen-port=51820 mtu=1420 name=wg1 vrf=secondary
/ppp profile
add change-tcp-mss=yes interface-list=PPPOEIN local-address=192.168.99.1 \
    name=pppoe-in on-down="/ip route remove [find dst-address=\$\"remote-addre\
    ss\" routing-table=wan];" on-up=":local ifaceName [/interface/pppoe-server\
    \_get \$interface name];\r\
    \n/ip route add dst-address=\$\"remote-address\" gateway=\"\$ifaceName@ter\
    tiary\" routing-table=wan;" remote-address=pppoe-pool
/routing table
add disabled=no fib name=to-vrfs
/interface list member
add interface=ether2 list=VRFs
add interface=ether3 list=VRFs
/interface pppoe-server server
add default-profile=pppoe-in disabled=no interface=ether2 service-name=\
    pppoe-lan
/interface wireguard peers
add allowed-address=10.5.25.2/32 client-address=10.5.25.2/32 \
    client-allowed-address=10.5.25.0/24 client-dns=10.5.25.1 client-endpoint=\
    172.20.80.2 client-keepalive=25s interface=wg1 name=win11ip public-key=\
    "*****" responder=yes
/ip address
add address=172.20.80.2/24 interface=ether3 network=172.20.80.0
add address=172.18.0.20/24 interface=vrf-link-main network=172.18.0.0
add address=192.168.88.1/24 interface=ether2 network=192.168.88.0
add address=172.18.0.22/24 interface=vrf-link-tertiary network=172.18.0.0
add address=172.18.0.23/24 interface=vrf-link-wan network=172.18.0.0
add address=10.5.25.1/24 interface=wg1 network=10.5.25.0
add address=172.20.80.2 interface=vrf-link-secondary network=172.20.80.2
/ip arp
add address=172.18.0.10 interface=vrf-link-wan mac-address=76:CE:75:BF:FD:3F
add address=172.18.0.21 interface=vrf-link-main mac-address=0E:31:55:68:24:DD
/ip dhcp-client
add default-route-tables=wan,tertiary interface=ether1 name=client1
/ip dhcp-server network
add address=192.168.88.0/24 dns-server=192.168.88.1 gateway=192.168.88.1
/ip dns
set allow-remote-requests=yes
/ip firewall address-list
add address=172.20.80.2 list=VRF-LOCAL
add address=192.168.88.1 list=VRF-LOCAL
add address=172.20.80.0/24 list=VRF-RANGES
add address=192.168.88.0/24 list=VRF-RANGES
add address=192.168.99.1 list=VRF-LOCAL
add address=192.168.99.0/24 list=VRF-RANGES
/ip firewall mangle
add action=mark-connection chain=prerouting connection-mark=no-mark \
    connection-state=new in-interface=vrf-link-main new-connection-mark=\
    from-vrfs src-address-list=VRF-RANGES
add action=mark-routing chain=output connection-mark=from-vrfs \
    new-routing-mark=to-vrfs
/ip firewall nat
add action=src-nat chain=srcnat dst-address-list=!VRF-RANGES out-interface=\
    vrf-link-main to-addresses=172.18.0.10
add action=dst-nat chain=dstnat dst-address-list=VRF-LOCAL dst-port=53 \
    in-interface-list=VRFs protocol=udp to-addresses=172.18.0.20
add action=dst-nat chain=dstnat dst-address-list=VRF-LOCAL dst-port=53 \
    in-interface-list=VRFs protocol=tcp to-addresses=172.18.0.20
add action=dst-nat chain=dstnat dst-address-list=VRF-LOCAL dst-port=8291 \
    in-interface-list=VRFs protocol=tcp to-addresses=172.18.0.20
add action=dst-nat chain=dstnat dst-address-list=VRF-LOCAL dst-port=80 \
    in-interface-list=VRFs protocol=tcp to-addresses=172.18.0.20
add action=masquerade chain=srcnat out-interface=ether1
add action=masquerade chain=srcnat dst-address=172.20.80.2 dst-port=51820 \
    out-interface=vrf-link-tertiary protocol=udp
/ip route
add dst-address=172.20.80.0/24 gateway=172.18.0.21@main routing-table=to-vrfs
add dst-address=192.168.88.0/24 gateway=172.18.0.22@main routing-table=\
    to-vrfs
add dst-address=0.0.0.0/0 gateway=172.18.0.23 routing-table=main
add dst-address=192.168.88.0/24 gateway=ether2@tertiary routing-table=wan
add dst-address=192.168.99.0/24 gateway=172.18.0.22@main routing-table=main
add dst-address=172.18.0.0/24 gateway=vrf-link-secondary@secondary \
    routing-table=secondary
add dst-address=172.20.80.2/32 gateway=vrf-link-tertiary@tertiary \
    routing-table=tertiary
/ppp secret
add name=ppp1 profile=pppoe-in service=pppoe

Thank you so much for sharing! I put this on a test CHR and only changed minor things (disabled DHCP server, replaced wireguard keys, 192.168.88 --> 10.227.79, 10.5.25 --> 10.227.33 to match my ranges easily.

Indeed, this works! Weird. So I need to carefully dissect what the difference is to my other config where I am pretty sure I did exactly the same!

One thing that prevents me from doing a direct comparison is something super weird though: As you can see the only filters I added are udp plus the port. So I expect to see the packet to arrive at ether2 first. But you can see from the screenshot that only vrf-* devices are seen in the packet sniffer. How can this be?

By contrast, in my sniffer outputs from above, the packet first arrives in ether1, then br-main, then vlan3, then vrrp3. Sure, your config doesnt have a bridge, VLANs and VRRP but still the first entry must be ether2.

Another thing that I don't understand is that in my original packet capture, the source NAT is clearly visible after the packet is received from physical interface (num: 5).

However, in the screenshot above the source address is still the original client IP (10.227.79.43).

And the more I stare at it, the less it makes sense to me: Why even vrf-link-main shows up?? This is the link to main table, should have nothing to do with this. And vrf-link-wan?? And with all of this, how does the actual wireguard tunnel seem to work? Like I can ping the tunnel endpoint AND handshake AND data with the client looks good.

Nothing makes sense any more

Ha ha ah, wow, thank you for the screenshot with the Packet Sniffer. That bothered me so I decided to removed all the unnecessary stuffs from the Frankenstein config, and reduced it to this, that "works":

# 2026-08-04 17:32:27 by RouterOS 7.23.3
# system id = *******
#
/interface bridge
add name=vrf-link protocol-mode=none
/interface ethernet
set [ find default-name=ether1 ] disable-running-check=no
set [ find default-name=ether2 ] disable-running-check=no
set [ find default-name=ether3 ] disable-running-check=no
/interface macvlan
add interface=vrf-link mac-address=76:CE:75:BF:FD:3F name=vrf-link-main
add interface=vrf-link mac-address=0E:31:55:68:24:DD name=vrf-link-secondary
add interface=vrf-link mac-address=EA:00:F2:A9:1E:6A name=vrf-link-tertiary
/ip pool
add name=dhcp_pool0 ranges=192.168.88.20-192.168.88.254
/ip dhcp-server
add address-pool=dhcp_pool0 interface=ether2 name=dhcp1
/ip vrf
add interfaces=ether2,vrf-link-tertiary name=tertiary
add interfaces=ether3,vrf-link-secondary name=secondary
/interface wireguard
add listen-port=51820 mtu=1420 name=wg1 vrf=secondary
/interface wireguard peers
add allowed-address=10.5.25.2/32 client-address=10.5.25.2/32 \
    client-allowed-address=10.5.25.0/24 client-dns=10.5.25.1 client-endpoint=\
    172.20.80.2 client-keepalive=25s client-listen-port=13131 interface=wg1 \
    name=win11ip public-key="**************" responder=yes
/ip address
add address=10.5.25.1/24 interface=wg1 network=10.5.25.0
add address=192.168.88.1/24 interface=ether2 network=192.168.88.0
add address=172.20.80.2/24 interface=ether3 network=172.20.80.0
add address=172.18.0.20/24 interface=vrf-link-main network=172.18.0.0
add address=172.18.0.22/24 interface=vrf-link-tertiary network=172.18.0.0
add address=172.20.80.2 interface=vrf-link-secondary network=172.20.80.2
/ip dhcp-server network
add address=192.168.88.0/24 dns-server=192.168.88.1 gateway=192.168.88.1
/ip firewall nat
add action=masquerade chain=srcnat dst-address=172.20.80.2 dst-port=51820 \
    out-interface=vrf-link-tertiary protocol=udp
/ip route
add dst-address=172.18.0.0/24 gateway=vrf-link-secondary@secondary \
    routing-table=secondary
add dst-address=172.20.80.2/32 gateway=vrf-link-tertiary@tertiary \
    routing-table=tertiary
add dst-address=192.168.88.0/24 gateway=172.18.0.22@main routing-table=main

Let's look at the export in details: Gone are the WAN stuffs, as well as the PPPoE stuffs, and all the DSTNAT and manging that make the router's services in main accessible. What left are:

  • On ether2 LAN 192.168.88.1/24 with DHCP server in tertiary VRF.

  • A secondary VRF where WireGuard wg1 is listening, as well as ether3 with main IP address 172.20.80.2/24 (my PC at 172.20.80.1 is connected to this).

  • Subnet inside tunnel is 10.5.25.1/24 and is in main. Remote peer has 10.5.25.2.

  • Win11 with WG client connected to ether2 getting address 192.168.88.254 via DHCP (is in tertiary VRF). WG client is listening on port 13131.

  • Win11 WG client connects to endpoint address 172.20.80.2 which is part of the secondary VRF on the CHR.

Now for the MACVLAN tunnel part:

  • Dummy bridge vrf-link with 3 MACVLAN vrf-main, vrf-secondary, vrf-tertiary, with address assignment:

    /ip address
    add address=172.18.0.20/24 interface=vrf-link-main network=172.18.0.0
    add address=172.18.0.22/24 interface=vrf-link-tertiary network=172.18.0.0
    add address=172.20.80.2 interface=vrf-link-secondary network=172.20.80.2
    

    With vrf-link-secondary getting a /32 address assignment being the same as the address on ether3, 172.20.80.2.

  • Because vrf-link-secondary only automatically gets a dynamic /32 route from the address assignment, we also manually add the route to 172.18.0.0/24 with the MACVLAN interface as gateway. The purpose is so that when we have the a return address 172.18.0.22 it's directed to the MACVLAN tunnel, toward the other MACVLAN on VRF tertiary:

    /ip route
    add dst-address=172.18.0.0/24 gateway=vrf-link-secondary@secondary \
        routing-table=secondary
    
  • And the route in tertiary that tells that destination 172.20.80.2/32 is directly connected to vrf-link-tertiary:

    /ip route
    add dst-address=172.20.80.2/32 gateway=vrf-link-tertiary@tertiary \
        routing-table=tertiary
    
  • And we have the SRCNAT rule that changes the source address of the WG packets going out of vrf-link-tertiary to 172.18.0.22:

    /ip firewall nat
    add action=masquerade chain=srcnat dst-address=172.20.80.2 dst-port=51820 \
        out-interface=vrf-link-tertiary protocol=udp
    

All that is already known from the previous post, and logically that should be all what needed for the WG tunnel to work too.

However, that's not the case, without this extra route in the main table the tunnel doesn't work!!!

/ip route
add dst-address=192.168.88.0/24 gateway=172.18.0.22@main routing-table=main

If that route is present and enabled everything works, if that route is disabled, the handshake hangs and never completes!

Now let's look at the original config in the older post that also worked. Actually this route in main to reach 192.168.88.0/24 also existed there, just not explicit, but as combination of these two routes:

which directs the packets (because 192.168.88.x is satisfied by 0.0.0.0/0 ) to the gateway 172.18.0.23, which is the address on vrf-link-wan of VRF wan. The packets will be received on that interface, and in VRF wan we have this route:

that knows where to put the packets for destination 192.168.88.0/24.

So the response packets being sent to 192.168.88.254 were pushed through vrf-link-main and vrf-link-wan as a result, and you saw them your the sniffer screenshot.

In the new reduced configuration that detour route has been replaced by:

/ip route
add dst-address=192.168.88.0/24 gateway=172.18.0.22@main routing-table=main

But the fact is still the same: we need to provide a route in main that is able to reach 192.168.88.0/24, otherwise the handshake will not work :zany_face:.

Indeed, when I look at the connection table when the tunnel between 192.168.88.254:13131 and 172.20.80.2:51820 is established and working, I see this:

We have the blue expected connection entry, with correct NAT information (Reply Dst. Address 172.18.0.22 from masquerade). And we would think that when the packets go out of the tunnel on the vrf-link-secondary side, another conntrack entry would have been created between 172.18.0.22 and 172.20.80.2. But we don't see that.

Instead there is a conntrack entry as pointed by the orange arrow, that has source 172.20.80.2:51820 (ok, understandable if this is catching the return traffic when the WG side of the router response), but instead of the expected destination address 172.18.0.22 (because that was what WG see incoming, 172.18.0.22 is also listed in the Current Client Enpoint field), we have the original source address 192.168.88.254:13131 as destination!!!

And what is even more strange, is that this conntrack entry has a SRCNAT applied to the 172.20.80.2:51820 source:

But the configuration only has a single SRCNAT rule that should solely act on the 192.168.88.254:13131 source!

/ip firewall nat
add action=masquerade chain=srcnat dst-address=172.20.80.2 dst-port=51820 \
    out-interface=vrf-link-tertiary protocol=udp

So how did this connection that absolutely requires the route in main come out of existence??? I have no idea how to explain this at all. You've been correct all the time with this take:

:smile:

Wow that's insane!! Thank you so much for going through this with me, there's no way I could have caught that.

I reproduced your new config and even better, my config from the original post in this thread starts to work once I add the following route to my main table:

/ip route
add comment="#wireguard hack" disabled=no distance=1 dst-address=10.227.79.0/24 gateway=vlan3@vrf-main routing-table=main scope=30 target-scope=10

In other words, assigning the /32 directly to the vrf-link interface as you suggested works. However, this in my original post still works and is in my opinion slightly more elegant:

/ip route add comment="#wireguard hack" dst-address=172.18.1.21/32 gateway=xvrf-as@vrf-as pref-src=192.0.2.222 routing-table=vrf-as

Adding the static route in main is not desired because I made peace abandoning my main VRF due to all the issues (route leaking etc). Now it seems I again need to keep static return routes for all possible wireguard client networks in main :confounded_face: :confounded_face: :confounded_face:

I do not fully grasp what's happening here but to me it seems it's not wireguard: First, as I posted above, the log shows that wireguard responds. Second, the return packet can be seen in the sniffer.

Instead, it seems the return packet has to go through "main" in order for SNAT/masquerde back translation to work. In other words, there seems to be some weird edge case where NAT back translation with a VRF fails.

This smells like yet another bug and I think I am going to file another one... adding to my big stash of (still open and unanswered) bug reports...