起因
策略路由是个好东西,就是用起来特别麻烦,经常产生一些很奇怪的问题,这次遇到的问题也是特别迷惑。
有一条规则会使用CONNTRACK对链接进行分类,然后把整个链接打上标记,如果不符合任何规则就打一个默认标记。
这条规则看起来一点问题都没有,使用起来也非常正常。但是如果使用MTR对负责这条规则的链接做路由追踪的话,就会出现第一次路由追踪完毕后,从第二跳开始就会丢包。
排查
遇到这个问题之后,第一时间就怀疑是标记发生了错误。检查CONNTRACK之后,发现标记果然被打上了默认的标记。
但是仔细查找后发现,并未发现规则有明显的问题,且丢包只会发生在第2轮开始后从第2个hop开始丢包;而手动使用ping和traceroute则是全程没有问题的。
用iptables检查日志后,发现在第一轮的一个包时,mark被更改为默认的标记,但是后续的包又变回正常的。而在第二轮的第一个包时,mark又被更改为默认的标记,此时后续的包都变为默认的标记的了。
那么就有下面两个疑问
- 第一轮的第一个包后,CONNMARK被置为默认,但是后续的包又恢复正常的了。而第二轮的第一个包后,后续包为什么又不正常了。
- 为什么第一二轮的第一个包,会被设置为默认标记
原因
又经过一番研究,通过iptables日志中发现了问题的关键所在:
第一个包的TTL都是为1,即如果不是本地的IP,则会被本机直接返回TTL超时,那么就会经过netfilter的OUTPUT chain。而OUTPUT chain中有一条,如果DST是本地网络就设置为默认标记的规则。
这就回答了第2个问题:因为TTL exceed包是由本机发出的,导致被包被标记为默认标记并存入CONNTRACK。
但是这让问题更加迷惑了:第一轮的一个包被标记后,链接中后续的包怎么没有被设置为默认标记反而使用了正常的标记?按照CONNTRACK的描述来看,应该是对整条链接打标记才对,这是一条链接,应该已经绑起来了才对呀?
未解之谜
在网上搜寻了半天,也没有找到任何线索之后,决定还是使用老办法:检查nf_conntrack和iptables日志,最终发发现了conntrack的一个奇怪行为:
- conntrack对于ICMP链接也会进行追踪,猜测应该是通过type/code/seq来实现的
- conntrack追踪ICMP链接时,只会对与TTL>1的非本地连接进行追踪;而对于本地IP即使TTL=1时,也会进行追踪
这个行为我猜测可能是因为,TTL=1的非本地地址ICMP链接没有追踪的意义,所以直接就不追踪了。因为对于非本地地址来说,TTL=1一定是不可达的,终止在本地的OUTPUT就行了。
知道这个行为之后,之前提到的问题2,似乎也就清晰了。
对于mtr来说,他目前的行为是从TTL=1开始,一直到目的地(即不出现TTL exceed,同时出现ICMP echo-reply)的TTL,按照从小到大顺序进行轮询;到达目的地之后,在从TTL=1开始继续递增,重复进行轮询
因为其type/code都是一致的,且seq是规律递增的,按理来说conntrack会将其归为一条连接。但是因为存在上面不追踪非本地TTL=1的ICMP链接行为的存在,导致conntrack会将其视为两个链接。
即:conntrack会忽略第一个TTL=1的MTR ICMP包,从TTL=2的第二个MTR ICMP包才开始追踪链接。
所以对于conntrack来说,我们实际的一个MTR的链接,会被视为两个链接。所以就出现了MTR中第1轮TTL=1的包和第2轮TTL=1的包不在一个链接中的情况,最终导致conntrack的restore-mark没有按照预期执行。
同时对于上述行为,也衍生出下面的问题:对于TTL=1的ICMP包,因为conntrack不会追踪,所以restore-mark会失效,save-mark也有意义。这样会导致这些包无法通过restore-mark保持标记,会重复的进行iptables规则匹配。
// TODO下
上述这两个行为通过iptables的log也可以观察到,比较神奇;但是没有在网路中找到相关的讨论(可能是我英语太菜,没找到);感觉有空还是得翻一翻源码,看看有没有注释啥的。
解决方法
既然知道原因,解决方法也很简单啦;可以直接忽略icmp,或者对icmp做特殊的规则,亦或者是对于icmp的链接不save-mark。
可以参考openwrt
中的mwan3
包的规则,其中的规则比较完善。
参考文章
[1] https://www.frozentux.net/iptables-tutorial/chunkyhtml/x1582.html
example
三酱贴贴~