Wireguard source ip selection

Hi there,

i have a wired problem with source adress selection of wireguard.

Basicly i have asymetric routing where the connection comes in via one interface and has to leave from another.

Now the router when that happens always chosses as source ip the ip on that interface instead of using the address that the connection came for. This only happens when the outgoing answer goes via a different interface then it came in. If it’s the same Interface incoming and outgoing i can’t observe that behaviour, then it always picks the source address that the incoming package had as destination even when it’s different from the ip adress of the interface.

It’s not a nat rule. ICMP and winbox doesn’t bahave like that even though it’s also traffic originating from the router so in my mind it should behave exectly the same. Only difference winbox is tcp instead of udp.

Coincidentally:

Maybe you can find in that thread and in the linked to post:
RouterOS blatantly ignores pref-src. Can this really be a bug? - #72 by lurker888
something useful to solve your problem.

Yeah it is a bit vague, I thought somone already had the issue.

I made a complete testing setup in gns3 to reproduce the issue which is close to mine with the difference of no bgp only static, no igp and different adresses.
I use a CCR as client in that exemple and one as provider.

I only added the firewall rule established and otherwise drop on the client to basicly showcase how it would be from any client behind nat like i have it. In reality there is only public ips both as loopback and on the crossconnects. As long as the connection is symetrical it works no matter if via crossconnect or via provider but as soon as in-inter

face != out-interface it will select the source adress of the outgoing interface as you can see in the open torch tool. That breaks the connection for every client that is behind nat. The Arrows show the traffic flow to and from the server in the different scenarios.

Don’t be confused by src and dst ip in torch out of some reasen those have always been in wrong order on mikrotik. Look at TX and RX in torch to see if it’s incoming or outgoing.

Client

[admin@client] > export

# 2026-01-02 18:15:04 by RouterOS 7.20.6

# system id = CHX26FcEgXC

#

/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

set [ find default-name=ether5 ] disable-running-check=no

set [ find default-name=ether6 ] disable-running-check=no

set [ find default-name=ether7 ] disable-running-check=no

set [ find default-name=ether8 ] disable-running-check=no

/interface wireguard

add listen-port=13231 mtu=1420 name=wg1

/port

set 0 name=serial0

/interface wireguard peers

add allowed-address=6.6.6.6/32 endpoint-address=10.0.0.1 endpoint-port=13231 interface=wg1 name=peer1 \

persistent-keepalive=1s public-key="k+vjUYrAzO5mKESLvvpQqwab2gDJXgNoUjhB23rOqQY="

/ip address

add address=192.168.90.1/24 interface=ether1 network=192.168.90.0

add address=5.5.5.5 interface=wg1 network=5.5.5.5

/ip dhcp-client

add interface=ether1

/ip firewall filter

add chain=input protocol=icmp

add chain=input connection-state=established,related

add action=drop chain=input

/ip route

add gateway=192.168.90.254

add dst-address=6.6.6.6 gateway=wg1

/system identity

set name=client

Provider:

[admin@Provider] > export

# 2026-01-02 18:15:31 by RouterOS 7.20.6

# system id = qzyYuNFioDD

#

/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

set [ find default-name=ether5 ] disable-running-check=no

set [ find default-name=ether6 ] disable-running-check=no

set [ find default-name=ether7 ] disable-running-check=no

set [ find default-name=ether8 ] disable-running-check=no

/port

set 0 name=serial0

/ip address

add address=192.168.90.254/24 interface=ether8 network=192.168.90.0

add address=192.168.7.4/28 interface=ether2 network=192.168.7.0

add address=192.168.15.3/28 interface=ether1 network=192.168.15.0

/ip dhcp-client

add interface=ether1

/ip route

add distance=1 dst-address=10.0.0.0/24 gateway=192.168.15.4

add distance=10 dst-address=10.0.0.0/24 gateway=192.168.7.5

add distance=1 dst-address=10.0.0.0/24 gateway=192.168.15.4

add distance=10 dst-address=10.0.0.0/24 gateway=192.168.7.5

/system identity

set name=Provider

PM1

[admin@PM1] > export

# 2026-01-02 18:15:50 by RouterOS 7.20.6

# system id = IF6ioZg1GqK

#

/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

set [ find default-name=ether5 ] disable-running-check=no

set [ find default-name=ether6 ] disable-running-check=no

set [ find default-name=ether7 ] disable-running-check=no

set [ find default-name=ether8 ] disable-running-check=no

/port

set 0 name=serial0

/ip address

add address=192.168.0.2/31 interface=ether2 network=192.168.0.2

add address=192.168.15.4/28 interface=ether1 network=192.168.15.0

/ip dhcp-client

add interface=ether1

/ip route

add dst-address=10.0.0.1 gateway=192.168.0.3

add gateway=192.168.15.3

/system identity

set name=PM1

Dus1

[admin@DUS1] > export

# 2026-01-02 18:16:10 by RouterOS 7.20.6

# system id = +OdonftSVlP

#

/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

set [ find default-name=ether5 ] disable-running-check=no

set [ find default-name=ether6 ] disable-running-check=no

set [ find default-name=ether7 ] disable-running-check=no

set [ find default-name=ether8 ] disable-running-check=no

/interface wireguard

add listen-port=13231 mtu=1420 name=wg1

/port

set 0 name=serial0

/interface wireguard peers

add allowed-address=5.5.5.5/32 interface=wg1 name=peer1 public-key=\

"nYZC2ostUH0bGjCiE2UdluzLRqq6SONTRD1jQGDTbQg=" responder=yes

/ip address

add address=192.168.7.5/28 interface=ether1 network=192.168.7.0

add address=192.168.0.3/31 interface=ether2 network=192.168.0.2

add address=10.0.0.1 interface=lo network=10.0.0.1

add address=6.6.6.6 interface=wg1 network=6.6.6.6

/ip dhcp-client

add interface=ether1

/ip route

add gateway=192.168.7.4

add distance=10 gateway=192.168.0.2

add dst-address=5.5.5.5 gateway=wg1

/system identity

set name=DUS1

Yes, as you can read from the link @jaclaz posted above to @lurker888's post: What you see is not a new finding and has been explained by @lurker888. He also posted the workaround in the linked thread.

I read that already but as far as i understood that is a bit of a different case there. The wan connections are with masquarade rules changing the sorce ip. I can’t do any of that since i have connection tracking disabled and run completly stateless on that router with only some very simple firewall rules since it’s one of the edge routers for me. I don’t understand that behaviour because tcp connections like ssh and winbox work as expected and always use the src address also for replies. Is there any other solution to this? Why does it behave that way. Also i noticed pref. source gets ignored, i haven’t set any but i also don’t want to use pref source anyways because that would lead to some other undesired behaiviour in other cases.

Selection of the pref src for the source address is correct (i.e. the intended) behavior for wireguard. It is specifically designed this way as a logical consequence of roaming being a feature and the symmetrical roles of the parties (foregoing the usual server-client arrangement.)

The thread that was suggested to you contains full play-by-play explanations on how this comes about. Also linked is my (slightly verbose) treatise on source address selection in general, the gist of which is that for tcp, the source address must be (and is) set to the dst address of the initiating packet; this also holds true for common udp-based protocols like dns and openvpn. That is why you don't experience this elsewhere.

This is hardly the first time someone has encountered what you have on your hands, and you are correct that in case you have disabled connection tracking, that advice is not for you.

The correct path for you is, believe it or not, through correct assignment of pref srcs. The correct way of doing that is by using the relatively new feature (at least in RouterOS) of having the ability to assign a vrf to the wg tunnel, and having your preferred address for the wg endpoint set up there.

The other, far less satisfying, solution is to use different devices for the wireguard server role. While this sounds a bit strange, generally having forwarding and management or service type devices separate has benefits in terms of stability. Of course I don't really know what the rationale behind your setup is and this last remark could be way off base.

By design, Wireguard itself does not use symmetric UDP flows. It treats each packet it sends individually in terms that it always finds the best route and thus also source address for it, ignoring any transport (UDP) level context. The reason for this is to make it possible to accommodate to address changes on both its own and peer side. The complementary behavior in the receiving direction consists in accepting an incoming packet based on its “session ID” (I don’t know the official term) alone, i. e. regardless its source socket and destination address.

This would work well in the ideal internet “where every neighbor is a friend” without any NAT and/or firewalls. But as soon as the remote peer is behind just a firewall, not even NAT, which only lets through incoming packets that are UDP-level responses to previously seen outgoing ones, a change of address and/or port on the local peer means that packets from this new socket will not make it to the remote peer through that firewall.

Since there is no checkbox to tell the Wireguard implementation on Mikrotik to stick with the local socket at which it has received the initial handshake packet from the peer, @lurker888’s workaround is the only way how to facilitate communication between a Wireguard instance on Mikrotik and some peer in the internet that is behind any kind of traffic filtering device.

Given that you know that it is possible to disable connection tracking completely, no doubt you also know you can keep it enabled but use action=notrack rules in /ip/firewall/raw to prevent only chosen traffic from being actually connection tracked. The question I cannot answer is the impact of adding the following rules on the CPU load/throughput of your edge router:

/ip firewall raw
add chain=prerouting dst-address-type=!local action=notrack
add chain=prerouting protocol=!udp action=notrack
add chain=prerouting protocol=udp dst-port=!13231 action=notrack
add chain=output protocol=!udp action=notrack
add chain=output protocol=udp src-port=!13231 action=notrack

(you may probably replace dst-address-type=!local by dst-address=!one.specific.ip, it should save some CPU instructions too).

From 7.21 we can create WG instances (interfaces) per VRF and with that lock the outgoing UDP packets to the routing table of the VRF. Of course the WG instances are distinct, with own subnets.

1 Like

Just placing the transport side of the WG interface into a dedicated VRF isn’t enough to pin it to a single address, you have to make sure that only a single address is available in that VRF. So if you want the router as a whole to connect to the world using multiple interfaces and non-elementary routing, the complete setup would have to look somehow like this:

/interface ipip add name=ipip-main local-address=127.0.0.1 remote-address=127.0.0.2
/interface ipip add name=ipip-wg local-address=127.0.0.2 remote-address=127.0.0.1
/ip vrf add name=wg interfaces=ipip-wg
/ip address set [find where address~"^wg.if.ip.addr/32"] interface=ipip-wg
/ip route add routing-table=wg gateway=ipip-wg@wg
/ip route add dst-address=wg.if.ip.addr gateway=ipip-main
/interface wireguard set wg1 vrf=wg
1 Like

The idea with vrfs is to use @main routes and pref-src.

Put OSPF or another dynamic routing protocol into the mix and the beauty is lost again. The suggestion above just jails the transport side of the WG interface into the VRF and lets it talk to the rest of the world via a single uplink, the IPIP tunnel. What emerges from the main end of the tunnel gets routed using the main routing table in all its complexity.

1 Like

There's always some sort of story behind these requirements.

If someone wants a lean-mean ospf routing machine then they would like to have fastpath. Understandable. Of course then they can't do much else besides injecting a pref-src which is okay for all communication where the device behaves as a host.

But if you want a lean-mean router, then really you don't have a problem, because you're using wireguard at most for an admin plane mesh, right?

If you want a wireguard "concentrator" or "service" then a) you probably don't really want to use MT hw, but if you do b) there's no reason not to selective track/notrack traffic on a dedicated device.

If you're def obsessed with doing things by routing, you actually don't have to not misroute outgoing packets, so you're free to choose any one or two peers also participating in ospf and dump your traffic to them... (okay, this is not pretty)

And yes, as a last resort you are free to tunnel stuff to yourself.

If someone doesn't find what they're looking for in all of these possibilities, there's something going on.

Interestingly, I've done a few (not many) deployments with ospf, and coincidentally I really like interconnecting my routers into a wireguard private network for administration. I have yet to run into a situation where some extreme uber hacking had to be done in order to accomplish this. In these situations I usually have central point for mgmt access, which is a small and compact Mikrotik, which I can firewall and use for nat etc. at my heart's content.

1 Like

If both Wireguard peers are located inside a mesh network without any NATs and firewalls between the peers that would create the need for symmetric flows, nobody cares about the source address the Wireguard instance uses to send the next packet; the peer will get it and accommodate.

The setup in the OP is hybrid in terms that the Wireguard device has multiple WANs and NATing is not possible but the symmetric UDP flows are required because the remote peers are behind NATs or at least firewalls so packets from a “wrong” source cannot reach them.

And I totally agree with you that there is no “one size fits all” solution, it’s just that “unusual” scenarios require “specific” solutions, which aren’t necessarily simple.

My proposal with both a VRF and a hairpin tunnel is basically an alternative implementation of your “src-nat on input” solution; mine seems to me more complex to configure but easier to comprehend than yours, as it requires some knowledge to realize that in yours the outgoing packets are actually also routed twice. In mine, the two passes through routing are somehow more visible at first sight.

1 Like

I'm not against the tunnel-to-self solution. Indeed if re-injecting the packet is the goal it's mostly the only solution. The other on Linux would be namespaces, which is not exactly accessible outside containers on MT, and in the end isn't really different.

I'm just saying that it's often smarter to design your network in a way that this is not needed.

What I'm ultimately getting at is, in the diagram of the simulation, why can't the router send it's outgoing packets from 10.0.0.1 every time it's behaving like a normal host?

In the end you have to make a decision regarding what the "normal" (as host) address should be for your router. Some (most?) non-MT devices make this easier.

But you just have to face it ultimately that vrfs are, for the time being, not a part of Mikrotik's packet acceleration technologies (fastpath/fasttrack/l3hw) and wireguard is the straight linux implementation, which in certain circumstances is not easy to handle.

EDIT: Maybe I'm not fully getting the hybridness of the situation. My point is simply that I've always found kind of trivial ways to bypass it.

1 Like

well… the Wireguard AEAD algorithm is not implemented in hardware, plus it’s a local service anyway so I’m not sure any amount of fast-whatever or L3HW would speed up the processing time of a Wireguard packet significantly. So unless creation of the first custom VRF blocks those features globally (i.e. also for the main VRF), does it really matter?

1 Like

Of course it doesn't. That's why I've said repeatedly that I can only guess as to the true motivation for the setup.

The only case I could imagine for having conntrack disabled altogether was that fastpath was needed for routing performance apart from wireguard, which is only a side gig for that router. If wg is the main job, conntrack can just be selectively disabled (as you already proposed.)

1 Like

Thanks for all the replies.

Like already guessed it’s just an admin connection from outside and it’s only there for 2 cases.

If my Proxmox cluster is down or if my own IP network doesn’t work because of issues with bgp on the two routers.

If everything works I have several other ways to get access via other vpns with chr routers that i use all the time.

Basicly the Idea is to have those wireguards one on each router and be able to connect via any IP of that router.

At some point that is not needed anyways because i will design a dedicated oob network with it’s own uplink.

My provider uplinks have a /28 subnet, but i only use one ip address basicly only to run bgp sessions. Is there any way to have a virtual ethernet interface as subinterface of that uplink interface which i could put in a vrf with the wireguard and it’s own ip address? Kinda like a vlan just without tagging traffic.

Also if i put the wg interface in a vrf it will use that one also for the tunnel traffic, not just the traffic through the tunnel?

Would be best if i can assign one vrf for wg traffic and another for the traffic that goes through the tunnel, but that can be solved with route leaking or routing mark in mangle.

You can add a MACVLAN interface to your ethernet interface (can also be VLAN interface or bridge, anything with MAC address).

1 Like

What is called a Wireguard “interface” in the configuraton tree is actually a Wireguard “instance”. So it consists of the virtual interface through which it exchanges the payload traffic with the rest of the router, and the software module behind it that sends and receives transport packets carrying that payload in encrypted form, plus the handshake traffic; when sending and receiving this “transport&handshake” traffic, it acts as any other local process on the router like e.g. the DNS client. By specifying a VRF as a parameter of a Wireguard "instance”, you control which VRF it will use for these transport&handshake packets. If you need to place the payload interface into another VRF, you do that the usual way, i.e. by putting that interface to a list of interfaces forming up that VRF (ex: /ip vrf add name=mgmt interfaces=ether1,ether2,wg1).

But mind you, the possibility to choose a particular VRF for the transport&handshake traffic of a Wireguard instance is only available in 7.21.x which is a testing version as of now.

Ah ok, so I just tested it in 7.21. Now there is the option to just set a vrf for the wireguard interface/instance. That fixes the issue for me.

That i didn’t get before, i thought you ment just binding the wg0 to a vrf via /ip/vrf interfaces=wg0,wther1 would also affect the transport&handshake traffic which didn’t make sense to me.

Until then i can live with the current behaviour.

Macvlan interface of course that will work, even remembered that option as soon as i read it, was searching for some virtual type but macvlan is exectly what i need.

That way i have another interface and route for wireguard.

Better then using main table because there i run severall full tables.

This is really a place full of knowladge and people that like to help, thanks!