PPTP 连接成功后挂死在 Linux NAT 后面:GRE 助手
TCP 1723 连得上,数据通路却已死:PPTP 走的是没有端口的 GRE。加载助手模块,显式挂接它们,并放行协议 47。
有人转发了 1723,客户端到达服务器,认证开始,然后一切无声无息地停住。最自然的结论是密码错了,于是密码被重新输入了三遍。转发没有问题,凭据也没有问题。真正坏掉的是转发从未覆盖的第二种协议,以及悄悄不再为它提供帮助的内核默认值。
转发从来不是问题所在
端口转发规则描述的是 TCP 或 UDP 流量。它无法描述在一个会话中途切换到另一种 IP 协议的载荷,而 PPTP 恰恰就是这么做的。
所以连接在卡住的那一刻之前看起来都很健康。端口规则能管辖的那部分成功了,它管不到的那部分才是失败的部分。
TCP 负责控制,GRE 负责数据
呼叫在 TCP 1723 上协商。一旦建立,载荷就进入 GRE,也就是 IP 协议 47,它完全不携带端口号。靠端口匹配的规则无从比对。
GRE 携带的是头部里的呼叫标识符,NAT 转换器必须在两个方向上一致地重写该字段,否则会话的两半就不再彼此吻合。只重写一个方向而另一个不重写,得到的会话虽然能通过认证,却会丢弃每一个数据包。这正是该症状的形态:成功,然后沉默。
不带 PPTP 的裸 GRE 不需要这种重写,所以普通 GRE 隧道能穿过那台对同一路径上 PPTP 客户端失效的路由器。
四个模块和一个把它们关掉的 sysctl
两个模块负责成帧,nf_conntrack_proto_gre 和 nf_nat_proto_gre。另外两个负责呼叫标识符,nf_conntrack_pptp 和 nf_nat_pptp。加载最后一个会通过依赖关系把其余的一并带进来,所以模块列表里应当能看到这四个。
较早的说明使用 2.6 时代的 ip_ 前缀。那些命令可能看起来在运行,却什么也没加载,所以要检查已加载列表,而不是退出状态。
仅仅加载它们已经不够了。从 Linux 4.7 起,net.netfilter.nf_conntrack_helper 这个 sysctl 默认为 0,这会关闭对新流的自动助手分配。路由器可能装齐了所有模块,却依然什么都不转换。再次查看模块列表之前,先确认实际生效的值。
要么全局重新开启自动分配,要么把助手只挂到需要的流上,其余一概不动:
iptables -t raw -A PREROUTING -p tcp --dport 1723 -j CT --helper pptp第二种做法更可取。它把行为限制在 PPTP 流量上,而不是在整个路由器上重新启用自动分配。
放行一个没有端口的协议
允许 1723 的防火墙规则对协议 47 只字未提。在 firewalld 上,要在面向客户端的区域放行该协议。在普通 iptables 上,要在输入链里接受 gre 协议。
然后在两侧抓包。从客户端发出的包没有回应到达,说明返回路径被过滤了,这和凭据被拒是另一类故障。
连上之后的大小限制
GRE 会插入一个头部,其中的 PPP 还会再加一些,所以能用的链路报告的 MTU 远低于 1500。小响应能加载,大响应就卡住,看起来像是第一个问题刚解决就冒出来的第二个无关问题。
读取客户端 PPP 适配器上的 MTU,并把它与路由器自身的计算核对一致,而不是一味调低数值直到能用。全尺寸包发出去却什么也不回来,指向的是那个上限,而不是地址转换。
修好路径能让用户恢复工作,但这并不意味着 PPTP 是个好选择。MS-CHAPv2 与 RC4 早已废弃,所以把加载模块当作临时修补,并规划迁移到仍在维护的协议上。