Solving OpenVPN MTU issues

Introduction

For some time now I’ve systematically used an OpenVPN-connection whenever I was using an untrusted WLAN (at hotels, restaurants, etc.). And so should you: Whether or not a hotel’s WLAN is WPA-encrypted has no influence on its trustworthyness. After all, are you really sure that among all of the connected users there’s not a single bored teen that’s having fun sniffing your passwords or doing a man-in-the-middle-attack through a few taps on his smartphone? (Yes, it’s that easy!)

And here’s the problem: You’ve set up your own OpenVPN-Server following one of the many detailed Howto guides available online and now that you’re actually abroad your VPN is failing miserably: The VPN-client connects just fine, but even the simplest webbrowsing has weird issues or doesn’t work at all. The most frequent cause (besides an overly restrictive firewall) is a problematic MTU-setting.

Here’s what it may look like in the logs:

MTU issue (OpenVPN for Android)

If you just want a quick’n’dirty solution that works perfectly fine (at least in my limited experience), simply change your OpenVPN’s transport protocol from the default UDP to TCP. (i.e. change "proto udp" to "proto tcp" on client and server. Better yet, have two server instances running, one for UDP and one for TCP.)

For various technical reasons (page referenced by the official manual!), such a setup is not recommended (TCP-over-TCP, reduced bandwidth), but in practice it works remarkably well “due to all kinds of magic related to mssfix” (as it’s “explained” on the OpenVPN-bugtracker, issue 375)

MTU you say?

MTU stands for Maximum Transmission Unit and represents the maximum size an IP packet can have before it must be fragmented, i.e. split up. And IP-fragmentation,  even though it’s part of the IP specifications, is a feature you’ll want to avoid whenever possible. (And that’s completely independent of your using OpenVPN or not. It applies generally to any kind of IP-based traffic.) Fragmentation entails security issues, performance penalities and transmission issues. It’s obvious that on the path a given IP packet travels to its destination, it’s the smallest encountered MTU that determines whether the packet has to be fragmented. This is the so-called Path-MTU. (The expected value is 1500; this corresponds to the MTU of regular ethernet or wifi connections. Problems arise when Path-MTU is less than 1500.)

In order to detect Path-MTU most IP packets are actually transmitted with the "Don’t Fragment"-bit set. This leaves an intermediate router with an MTU less than the packet’s length no choice but to drop it. It notifies the sender by means of a special ICMP message (Destination unreachable: fragmentation needed and DF set). The sender can then progressively scale down the size of the packets it emits. (This reduces throughput, since the headers are of a fixed size and the actual payload decreases.) In essence, this process is known as Path-MTU Discovery (PMTUD).

Unfortunately, PMTUD often fails due to a misconfigured firewall dropping those precious ICMP messages. Another reason is the possibly suboptimal handling of PMTUD by OpenVPN itself. But I’m not entirely sure of that. (See this bug report or read up about the Linux-only --mtu-disc setting. Feel free to comment.)

And that’s when the OpenVPN’s various MTU-related configuration directives come into play. I won’t go in all the gory details (I’m no networking expert either...), but I’ll try my best to sum up the sometimes contradicting or incorrect information I could find.

All of the following is strictly for the case you’re using the recommended UDP as transport protocol. (link-mtu and tun-mtu are also valid for TCP as transport protocol, but normally they’re not needed in this case.)

My objective is to derive a setup that’s (hopefully) compatible with a wide range of clients (at least Linux, Windows and the two major Android clients).

On to the first step:

RTFM

Actually I read the fine manual for you and here’s the summary:

--link-mtu n

Sets an upper bound on the size of UDP packets which are sent between OpenVPN peers. It’s best not to set this parameter unless you know what you’re doing.

--tun-mtu n

Take the TUN device MTU to be n and derive the link MTU from it (default=1500). In most cases, you will probably want to leave this parameter set to its default value.

The MTU (Maximum Transmission Units) is the maximum datagram size in bytes that can be sent unfragmented over a particular network path. OpenVPN requires that packets on the control or data channels be sent unfragmented.

MTU problems often manifest themselves as connections which hang during periods of active usage. It’s best to use the --fragment and/or --mssfix options to deal with MTU sizing issues.

--fragment max

Enable internal datagram fragmentation so that no UDP datagrams are sent which are larger than max bytes.

The max parameter is interpreted in the same way as the --link-mtu parameter, i.e. the UDP packet size after encapsulation overhead has been added in, but not including the UDP header itself. [...]

It should also be noted that this option is not meant to replace UDP fragmentation at the IP stack level. It is only meant as a last resort when path MTU discovery is broken. Using this option is less efficient than fixing path MTU discovery for your IP link and using native IP fragmentation instead. Having said that, there are circumstances where using OpenVPN’s internal fragmentation capability may be your only option, such as tunneling a UDP multicast stream which requires fragmentation.

--mssfix max

Announce to TCP sessions running over the tunnel that they should limit their send packet sizes such that after OpenVPN has encapsulated them, the resulting UDP packet size that OpenVPN sends to its peer will not exceed max bytes. The default value is 1450.

The max parameter is interpreted in the same way as the --link-mtu parameter, i.e. the UDP packet size after encapsulation overhead has been added in, but not including the UDP header itself. [...]

--mssfix and --fragment can be ideally used together, where --mssfix will try to keep TCP from needing packet fragmentation in the first place, and if big packets come through anyhow (from protocols other than TCP), --fragment will internally fragment them. [...] If --fragment and --mssfix are used together, --mssfix will take its default max parameter from the --fragment max option.

Therefore, one could lower the maximum UDP packet size to 1300 (a good first try for solving MTU-related connection problems) with the following options:
--tun-mtu 1500 --fragment 1300 --mssfix

So, it’s not THAT complicated after all? In order to adapt to a smaller MTU, I’ll just set --tun-mtu to some smaller value, e.g. 1300?

Unfortunately, it’s not that simple. What I haven’t told you about are the various restrictions.

Caveats, restrictions, limitations, compatibility issues...

OpenVPN can’t set or modify the MTU on Windows

Quoting the official documentation:

Currently on Windows, the only way to change the TAP-Windows MTU is to go to the adapter advanced properties and do it manually. Because of this, the easiest choice is to leave the TAP-Windows MTU setting at “1500” and tell OpenVPN on both sides of the connection to use an MTU of “1500” with the config option:
tun-mtu 1500
If you then need to lower the MTU because of fragmentation or router problems, use something like
fragment 1400
mssfix
to lower OpenVPN’s tunnel carrier UDP packet size to “1400”.

That’s the reason why older versions of OpenVPN (before the Windows-port) simply used --link-mtu 1300, but newer ones no longer take this route... If you want the broadest possible compatibility, you’ll always manually specify --tun-mtu 1500 even if it’s a fictitious value.

Limitations of the official Android client

Ok, then I’ll set mssfix and fragment on client and server and be done with it?

Not quite.

Neither configuration directive is supported by the official OpenVPN client for Android, “OpenVPN Connect”!

fragment — The fragment directive is not supported due to the complexity it adds to the OpenVPN implementation and the fact that it is usually better to leave fragmentation up to the lower-level transport protocols. Note as well that the client does not support connecting to a server that uses the fragment directive.

mssfix — This directive will be added in a future release. Since the functionality of mssfix can be achieved on either the client or server side, specifying it on the server side will enable it even if the client doesn’t support the directive.

(Source)

Do note that the mssfix limitation is not applicable to the excellent “OpenVPN for Android” by Arne Schwabe. (I’m not sure about fragment support, but mssfix definitely works!)

Server limitations

All of the clients connecting to the same server instance must share the same tun-mtu and fragment settings.

Recommendations

Considering all of the above, I see only one possibility that comes close to a one-size-fits-all approach and that’s setting mssfix 1300 and leaving all of the other settings at their default values: tun-mtu 1500, no fragmentation. Do note that mssfix can’t be pushed to clients (or at least it fails on OpenVPN for Android). The following snippet is included in both the client and server config files:

proto udp
dev tun
tun-mtu 1500
mssfix 1300
# no fragment setting :-/

Even though these settings should be compatible with a wide array of clients, they’re not perfect: only TCP-traffic (through the UDP-tunnel) is taken care of. As soon as the client device emits UDP packets that are too large (once encrypted and encapsulated), packet loss is still a possibility. This is entirely dependent of the traffic you generate. The good news is that most "stuff" is TCP-based (web browsing, e-mail, streaming audio/video, etc.), whereas UDP is mainly used for some "management protocols" such as DNS, DHCP and similar. Usually these UDP packets are rather small, hence unproblematic. It’s the exceptions that might prove problematic: realtime video applications (think Skype or Facetime), NFS, some types of online games. In these cases, there’s probably no way around enabling OpenVPN’s internal fragmentation routines and avoiding incompatible clients.

Conclusion

Getting OpenVPN right when confronted with non-standard networks can be cumbersome... At the same time it’s a very powerful tool that can be adapted to suit almost any secure tunneling needs.

Tags : OpenVPNMTUTCPUDPhowto

Copyright © 2015–2018 Hambier
GS RU