在一个包含多个跃点的网络中建立若干条GRE隧道, 如果隧道是从网络外的一侧通往网络外部的另一侧, 且通往网络外部的一侧配置了NAT, 那么会遇到比较头疼的问题.
首先我们知道, 对于conntrack来说, 确立一条”连接”需要五个因素: 协议, 源IP, 源端口, 目标IP, 目标端口, 而GRE协议没有端口的, 所以对于conntrack来说如果源IP目标IP一致, 就会认为是同一条隧道. 进而表现为同一时间只有一个GRE隧道能通过NAT且有流量.
但是当我们打开RFC文档, 不难发现即使是最早的RFC 1701版本中也规定了一个key field可以用来在源IP, 目标IP都相同的时候用来给两侧终端区分不同链接. 后续新版的GRE协议, 也有RFC 2890给出了针对GRE协议的key field扩展, 而且与RFC 1701的协议是兼容的. 那为什么conntrack不把key加入到用来区分不同链接的因素中呢?
带着这个疑问, 让我们打开Linux源码(狗头):
在 net/netfilter/nf_conntrack_proto_gre.c
中, gre_pkt_to_tuple
是用来解析GRE报文并提取链接要素的.
1 | /* gre hdr info to tuple */ |
可以看到这里 判断了GRE包是否的GRE_VERSION
bit 是否不是 GRE_VERSION_1
, 这个宏的定义在 include/uapi/linux/if_tunnel.h
:
1 |
为什么会有 GRE_VERSION_1
呢? 在RFC 2784中可以找到答案:
1 | 7.1. GRE Version Numbers |
所以这里conntrack的逻辑是, 只要是普通的GRE协议, 就直接返回. 只有当协议是PPTP的时候才会提取key字段. 这也就解释了为什么同一时间只能有一个有效的GRE隧道.
1 | gre 47 169 src=<redacted> dst=<redacted> srckey=0x0 dstkey=0x0 src=<redacted> dst=<redacted> srckey=0x0 dstkey=0x0 [ASSURED] mark=0 use=1 |
如此看来唯一的解决办法就是绕过Conntrack, 使用如下命令:
1 | iptables -t raw -A PREROUTING -p gre -j NOTRACK |
注意这会让GRE协议跳过conntrack, 也不会进入nat表, 因此在网络外侧的接收点需要手动或其他方式来配置到网络另一侧节点的IP路由. (原来的时候remote只需要写出网边缘节点的IP即可)