L2TP、GRE 还是 IPsec:会话该由哪一层承载?
L2TP 承载 PPP 会话,GRE 承载被路由的数据包,IPsec 保护选定的流量。为每一层分配一项任务,再分别验证每一层。
只要 L2TP、GRE 和 IPsec 出现在同一份设计笔记里,两个问题就会纠缠在一起:我承载的是哪种会话,哪一层保护它?第一个问题答错,下游一切仍会在内部自洽,却在解决错误的问题——在本该让用户拨号接入的地方建了路由叠加网络,或用拨号接入集中器代替站点到站点网关。下面的工作表在任何人登录设备之前就强制区分这两者。按顺序填写,每一行都会变成可以单独测试的东西。
从会话开始,而不是从协议清单开始
持有 PPP 会话的远程用户与两个网关之间的路由叠加网络是两种不同的网络关系,即便两者最终都包在 IPsec 里。前者终结按用户的链路,需要地址分配、对人员或设备的认证,以及一条指明该用户可访问哪些内部网络的政策。后者在无人拨号接入的站点之间转发数据包。
写一句话,说明你要建的是两者中的哪一个,然后像运维它的人那样再读一遍。如果这句话需要“和”字,你就是在同时建两者,工作表就需要两列,而不是一列对两边都只算半用的折中方案。如果它只提到用户却没有地址池,或只提到前缀却没有转发政策,这句话就仍不完整。这种措辞听起来官僚,但它是抓住设计问题最便宜的地方,否则问题会由一张服务台工单来发现。
给每一层恰好一项任务
L2TPv2 在两个端点之间承载 PPP 会话。PPP 负责链路协商,并在协商时负责用户认证;L2TP 不添加任何加密,因此一个裸 L2TP 守护进程不实现 IPsec 安全关系的任何部分,只应存在于你已信任的链路内部。
GRE 是两个地址之间的封装。它在逻辑叠加网络上承载网络层数据包,本身既不提供机密性,也不提供经过认证的对端身份。这不是缺陷:报头被设计得很简单,以便在转发硬件上处理起来仍然便宜。结果是 GRE 只回答“如何在两个地址之间搬运这个数据包”,别的什么都不答——而这正是你应该问它的问题。
于是 IPsec 成为选择流量、认证对端并加密指定保护对象的层。它保护哪些流量是一项配置决定,必须与端点实际生成的封装相匹配,因为为 GRE 外部地址编写的策略与为内部前缀编写的策略行为不同。GRE 规范让载荷、GRE 报头和外部投递协议清晰分离,你的策略也需要同样的分离。
| Layer | Its one job | What it never does |
|---|---|---|
| PPP | Link negotiation, optional user authentication, address assignment | Protect traffic between gateways |
| L2TPv2 | Carry the PPP session between two endpoints | Encrypt or authenticate peers |
| GRE | Encapsulate network-layer packets between two addresses | Hide or authenticate the payload |
| IPsec | Authenticate peers and encrypt selected traffic | Carry a dial-in user session by itself |
先写数据包路径,再写配置
拿一个数据包,按添加顺序列出每一个被添加的报头,并指出添加它的设备。对拨号用户来说,通常是 PPP 在 L2TP 内、再由 IPsec 承载;对站点叠加网络,是内部 IP 数据包在 GRE 内、GRE 在 IPsec 内。如果两台设备添加同一个报头,你就有了环路或重复隧道,再怎么改策略也修不好。
同一份清单也决定 MTU,因为可用的内部大小就是实测路径 MTU 减去你刚列出的报头,别无其他。不要从论坛帖子里抄数字:把外部 IP、UDP 和 ESP 的开销加起来,再加上 GRE 或 L2TP,需要时再加 PPP,然后从你真正测得的路径 MTU 中减去。只在大传输时出现的症状,或者建立后在第一个大响应上就死掉的会话,通常都可追溯到这笔加法是猜出来的。
逐层单独检查,从外到内
按顺序执行检查,遇到第一项失败就停下。每一行回答的是不同问题,所以失败指向工作表的某一列,而不是告诉你隧道坏了。
ip xfrm state # is the IPsec SA up?
ip -s link show gre1 # does the overlay interface carry packets?
ip route get 10.20.0.5 # does the inner prefix route where you think?
ss -lun | grep -E "1701|4500" # are the L2TP and NAT-T listeners bound?
在 Cisco 设备上,对应做法取决于角色,确切语法随平台和版本而异:用 show crypto session 查看 IPsec 对端,用接口计数器查看 GRE 或隧道接口,用 VPDN 或 PPP 验证查看拨号角色。能终止 PPP 的命令集不能证明支持 GRE,能工作的 GRE 接口也不能证明 PPP 服务正常,所以要验证你实际配置的角色,而不是图上的角色。
把每项输出记录在它所属的工作表行旁边。故障仍在发生时的记录,比事后凭记忆重画的拓扑更有价值。
安排拆解顺序,然后重新验证
拆解按相反顺序进行是有原因的:先把受保护的流量从 IPsec 策略中移除,再移除该策略所针对的封装,否则策略会继续指向已不存在的接口,下一次加载配置就会在一个看似无关的地方失败。在 Linux 上这意味着先删除 state 和 policy 条目,再删除隧道接口;在路由器上则是先移除 crypto map 或 profile 条目,再移除隧道。
重建之后,逐项重复四个验收观察:外部可达性、已建立的 IPsec 关联、设计包含时的已建立 PPP 会话,以及到真实目的地址的内部转发。一盏绿灯不能证明另外三项,而这个缺口正是生产环境中存活最久的故障——隧道已启动、已加密,却什么都不转发,因为内部路由从未加过。
把填好的工作表放在设备配置旁边。任何你填不出的列,都是没有分配任务的层,而没有分配任务的层通常就不该出现在协议栈里。