Total Pageviews

Wednesday, 23 March 2016

OpenVPN 日志产生 fragment ttl expired 的原因

OpenVPN 的连接时不时会突然变得非常缓慢,这时候查看 OpenVPN 的日志会发现日志中打印类似的内容:
 Thu Mar 10 16:28:00 2011 FRAG TTL expired i=4
 Thu Mar 10 16:28:10 2011 FRAG TTL expired i=19
 Thu Mar 10 16:29:22 2011 FRAG TTL expired i=10
 Thu Mar 10 16:29:22 2011 FRAG TTL expired i=22
 Thu Mar 10 16:30:12 2011 FRAG TTL expired i=22
 Thu Mar 10 16:32:25 2011 FRAG TTL expired i=13
乍一看这错误信息似乎是说 IP 层的数据包 TTL 超时了,但仔细想想却不太可能,如果 IP 包的 TTL 超时的话,我们根本不可能收到这个数据包,那么这个 FRAG TTL 指的到底是什么呢?这肯定和 OpenVPN 连接变得缓慢有重大关联,要解决 OpenVPN 速度变慢的问题就必须找到产生这些日志的原因。
谷歌搜索这个错误信息之后,只得到了很少的结果,一个是 OpenVPN 邮件列表中的结果,另一个结果是一个论坛中的讨论。OpenVPN 邮件列表中虽有有人提出了这个问题,但并没有人回答。另一个论坛中虽然有人给出了几个建议方案,但尝试之后毫无用处。
谷歌无果,只好将服务器和客户端的 OpenVPN 都升级到最新版本。然而,升级到最新版本之后,日志中的这类错误还是会持续出现。
没办法,只能自己动手丰衣足食了,打开 OpenVPN 的源码,直接搜索这段日志文字,一步一步追查,总算找到了产生此类日志的原因。
其实产生这种日志的原因很简单,但因为打印出来的日志用词不太恰当,所以很容易让人迷惑。日志中的 FRAG 并非指被拆分的 IP 包,而是指 OpenVPN 自己把数据包拆分后的小包。
OpenVPN 有一个 fragment 参数,这个参数可以指定通过 OpenVPN 承载链路传送的最大数据包大小。如果程序试图在 OpenVPN 链路上发送一个大于 fragment 的包,那么 OpenVPN 就会将这个包拆分成几个小包,然后再通过承载链路发送出去。另一头的 OpenVPN 接收到拆分后的小包后,会将小包重组成原始数据包,然后再交给程序去处理。
如果 OpenVPN 使用 UDP 模式,那么拆分后的小包就不一定会顺序到达,因此,OpenVPN 收到小包之后必须先将这些小包缓存起来,待所有的小包都到达之后,再将小包重组成原始包,然后释放用于保存这些小包的内存。
这样做就会出现一个问题,如果某个小包在 UDP 层上丢包了,那么这个小包就可能永远不会到达,与此小包关联的其他小包就会永久缓存在 OpenVPN 的内存中,其占用的内容会永远无法释放。随着 OpenVPN 承载链路上丢弃的包越来越多,OpenVPN 就会占用越来越多的内容,这是无法接受的。
为了解决这个问题,OpenVPN 会定期清理缓存的小包,具体策略是这样的:OpenVPN 对于每一个小包,都会记录其到达时间,每隔 10 秒 OpenVPN 就会对当前已经缓存的所有小包进行检查,如果某个原始数据包对应的所有小包都已经缓存超过了 10s,那么就认为这个原始数据包缺少的小包永远不会达到了,原始数据包无法重组,于是删除这些缓存的小包,释放内存。
当缓存的小包被删除之后,OpenVPN 就会打印这样的日志:
FRAG TTL expired i=4
其中 i 的值就是小包的编号。
我在 OpenVPN 的设置中指定了很小的 fragment 参数(这是为了迷惑某国家防火墙),当链路质量下降、丢包增多的时候,OpenVPN 这边就会有很多缓存的小包被删除,无法重组成原始数据包。由于我的 fragment 参数很小,一个数据包很有可能被拆分成 10 个以上的小包。假设 OpenVPN 承载链路的丢包率是 1%,OpenVPN 链路上的丢包率是
1(10.01)100.0956

丢包率变成了 9.56%,难怪网速突然就变得很慢了。
之后我把 fragment 参数调大,网速立刻就变快了,YouTube 720P 视频流畅无压力。唯一的问题就是这样比较容易被某墙发现,不过现在距离六月四日过去已经快两个月了,似乎墙也放松警惕了,目前就算不拆包似乎也不会受到太大的影响了,至少从目前使用的情况来看是这样的.
from https://www.gsea.com.cn/blog/topic/the-cause-of-frag-ttl-expired-message-in-openvpn/