Multiple Road Warrior L2TP/IPsec clients behind NAT - solved

I’m also interested if this issue is fixed in RoS 7…

I will test it as soon as i find some time…

People are complaining that the L2TP Server isn’t stable in v7 at the moment, so you should probably only test this when you have an independent test environment available.

I’ve found the time right now - as expected, it behaves the same like it did before.


As outlined in the OP of this topic, it is not a bug of RouterOS to be “fixed” - it is a design choice of the gentlemen who standardized L2TP over IPsec, who gave priority to saving a few bytes of overhead over a possibility to allow multiple clients connect to the same server from behind the same public IP.

So if you have strong reasons to connect multiple non-Mikrotik L2TP/IPsec clients to the same L2TP/IPsec server from behind the same public IP, you have to use the workaround outlined in the OP even on ROS 7.2rc1 where I have just tested how it behaves without that workaround.

I think that the issue could be solved in the IPsec layer by allowing the use of the NAT-T UDP source port as an extra key for selection of the matching IPsec policy.
Even though all other parameters of the IPsec policy are the same, the NAT-T UDP ports cannot be, because the NAT router at the client side has seen that it has two UDP sessions from two clients to the same server, so it translates the source port from 4500 (the usual NAT-T port) to some random port.
When the IPsec implementation in v7 allows to use this extra information to key the policy selection, it can work around this problem without requiring configuration tricks.
That is probably also what other manufacturer’s routers (that are claimed not to have this problem) are doing.

However, be warned: back in the days when MikroTik wrote that v7 would be the solution for all difficult problems, they probably had a rough sketch of all those modules that they would be going to rewrite and how they all would be so much better. In the years following, it looks like only the autorouting (BGP,OSPF) and maybe some features in the routing itself have survived that optimism. Many other new features were instead implemented in v6 and taken into v7 as well, and this is not one of them. It is not obvious that v7 has different IPsec code than v6 (as it is for routing).

The way you suggest, the information about the additional port number would have to reach the L2TP stack in the form of some meta-header of the payload packets extracted from those received using the particular IPsec policy, and then in the reverse direction, the L2TP stack would have to send the same meta-header and the IPsec traffic selection would have to use it as a key to select the policy. So not only that some specific behaviour would have to be linked to the IPsec identity - the L2TP stack would also have to be modified. So it would be a complex modification but, in a way, a clean one.

Another way would be to limit the specific behaviour to the decryption and encryption process of IPsec, leaving the L2TP code completely unchanged - instead of using a metaheader, it would be “enough” to let the decrypted UDP payload packet inherit the port number from the transport one. However, this would require a stateful behaviour of the policy, as it would have to remember the originally received payload port number in order to use it when substituting the payload destination port during encryption in the server->client direction.

I think that is the reason why MikroTik wrote that it cannot be fixed in v6 but will be fixed in v7 (“when we do that IPsec rewrite anyway”).
However, now that v7 has arrived, that rewrite probably did not (yet) take place, and the situation is still the same (cannot fix that now, maybe later).

Yes, that’s what i had in mind…

@sindy, really ? It would be so nice if it worked …

It looks like this case won’t be fixed any time soon and other solutions will have to be found. Has anyone tried SoftEther for multiple L2TP/IPsec clients behind NAT?

Just for curiosity, what makes my workaround outlined in the OP unattractive?

@sindy to be honest its not the fact that it is unattractive… simply there is no reason for that kind of complexity since i can just work with another type of VPN…
In ROS 7 openVPN works on UDP ( in ROS 6 only TCP was supported ) so i can use for the road warriors oVPN or Wireguard or IKEv2 …

I am with you on this, I prefer IKEv2 myself for Windows connections as it is the only VPN type to currently support route pushing on Mikrotik acting as a server and once you beat the fear of certificate handling, it performs great. However, many people prefer native VPN clients, and L2TP/IPsec seems to be the only VPN type available at Android, Apple, and Windows without need to install a 3rd party app. So since @GuntOn mentions SoftEther acting as an L2TP/IPsec server in particular, I assume L2TP/IPsec is a must for their application. I did try SoftEther in the past (2-3 yers ago) and I remember had some no go issue with it, but I can’t remember what it was.

@sindy - thank you very much the solution!
It’s perfectly working on ROS 6.49.7

Thank you so much for starting this thread, sindy.

A total noob here running ROS 6.49.7, on an hAP ac2 and also want VPN access across multiple OS’es (iOS, Android, Ubuntu and Windows) and for multiple devices simultaneous on the same LAN. Are these the lines to customize, the bold items, specifically:


Add routes for the addresses used for the solution

/ip route
add distance=1 dst-address=> 10.0.0.0/20 > gateway=ipip-inner
add distance=1 dst-address=> 10.0.15.254/32 > gateway=ipip-outer

Add the firewall rule permitting forwarding of dst-nated packets in the first pass

/ip firewall filter
add action=accept chain=forward connection-state=new dst-address=> 10.0.15.254

Add the NAT rules

/ip firewall nat

Restore our public IP address on packets after they’ve passed through the tunnel

add action=dst-nat chain=dstnat dst-address=> 10.0.15.254 > in-interface=ipip-inner to-addresses=> 1.2.3.4

src-nat the packets before sending them to the tunnel

add action=src-nat chain=srcnat out-interface=ipip-outer protocol=udp to-addresses=> 10.0.0.1

Redirect packets to port 4500 to the auxiliary destination address to give them the special treatment;

for testing that it works with only two client devices, remove the “src-port=!4500”

add action=dst-nat chain=dstnat dst-port=4500 src-port=!4500 dst-address=> 1.2.3.4 > protocol=udp to-addresses=> 10.0.15.254

Add the cleaner script

/system script
add name=l2tp-helper owner=admin policy=ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon source=
“:local cntr 0; \
\n:local auxip [/ip firewall nat get [find chain="srcnat" && out-interface="ipip-outer"] to-addresses]; \
\n:while ([/ip firewall connection print count-only where src-address~"^$auxip" && dst-address~":4500" && seen-reply]=1)
do={
\n :set auxip ($auxip+1); \
\n :if ($auxip>> 10.0.15.253> ) do={:set auxip > 10.0.0.1> };:set cntr ($cntr+1)
\n}
\n:if ($cntr>0) do={
\n /ip firewall nat set [find chain="srcnat" && out-interface="ipip-outer"] to-addresses="$auxip"; \
\n /ip firewall connection remove [find dst-address~":4500" && !seen-reply]
\n}
\n”

Questions:

  • What is ip 1.2.3.4 used for?
    What if my gateway / DHCP server is 192.168.88.1, how exactly would I change line: “if ($auxip>10.0.15.253) do={:set auxip 10.0.0.1};:set cntr ($cntr+1)”

Thank you.

Consider zerotier and/or wireguard for simple fast VPN accross all OSs.

1.2.3.4 needs to be substituted by the actual WAN IP of the router. By default, Windows clients refuse to connect to servers that indicate a different IP address than the one to which the client connects, i.e. those behind a NAT at server side. So the IPsec responder and L2TP server must listen at the public address, but at the same time we need that the incoming traffic was pushed through the tunnel, so we have to dst-nat it twice - to an auxiliary address (10.0.15.254 in the example) and then back to the original one(1.2.3.4 in the example). (NB - during those four years I have learned that the same effect can be accomplished another way, but it does not simplify the overall concept, at least not enough to be worth modifying the example).


In the example, a range from 10.0.0.1 to 10.0.15.253 is used to provide a unique address for each client, and 10.0.15.254 is used for another internal purpose. This must be a range that is not used anywhere inside your network; if the only subnet you use is 192.168.88.0/24, you can stick with 10.0.0.1-10.0.15.254. If 10.0.0.0/20 or a part of it was used in your network, you would have to use any other non-conflicting range of private addresses instead. The script row in particular ensures a rollover from the highest address in that range back to the lowest one (with each new connection attempt, the address is incremented by one, so after 4091 connection attempts it reaches the end of the range and needs to start from the first one again).


I didn’t know I was starting a thread, I was thinking I was posting an article :slight_smile:

Thanks, but as OP, I’m not looking to install any non-native apps on any of the platforms.

Hi Sindy, I am asking for your help.I have exactly the same problem as you describe, but it doesn’t work for me.

Hi, no notification has arrived back in March… I guess it is already irrelevant for you, but if not, try responding again.

Is there a documented solution for “Script Error: No such item (4)”?

I have implemented your solution in my setup in Argentina, but I encountered a connection lockout issue when trying to connect from the office in Spain. I am currently trying to match the ISP setup to determine if it is related to the connection provided by the ISP.

However, regardless of whether people are connected or not, I keep seeing “Script error: no such item (4)” in the logs.

I am running ROS 6.47.9 on a RB750Gr3 and am currently attempting to modify the l2tp-helper script to see if that resolves the issue. I understand that:
dst-address~“:4500” could be updated to dst-port=4500 when trying to match the NAT rule.