连接钩子是会话策略,不是数据包过滤器
客户端连接钩子每个会话只运行一次,永远看不到载荷。把准入放在存在会话的地方,把内容改动放在存在请求的地方。
有人想检查客户端通过隧道发送的内容,在文档里找到连接钩子,并开始写一个永远收不到任何一个数据包的脚本。这个钩子是真实且有用的,但它工作的单位是会话,而不是字节。选择合适的组件要比较三个可能发生工作的地方,而决定性的问题是:每一个究竟能看到什么单位。
钩子收到什么,永远收不到什么
客户端连接钩子在已认证客户端建立会话时运行一次,运行在服务器进程的上下文里,接收有文档记录的参数,以及通用名称、分配地址之类的环境值。它可以写入按客户端区分的配置、作出准入决定、分配外部资源,或者记录这次连接已经发生。它做不到的是观察流量,因为当第一个数据包穿过隧道时,钩子早已返回。
这不是需要绕开的限制。想处理数据流的会话回调必须运行在整个连接生命周期里,而守护进程的脚本契约并不是为这种形态构建的。在断定它挡路之前,先问清楚钩子是做什么用的。
重连同样重要。掉线后重新连接的客户端是一个新会话,所以钩子分配的任何东西都必须幂等,或者与负责清理的断开事件配对。每次重连都泄漏的状态,是基于钩子的设计里最常见的第一号缺陷。
会话策略属于钩子
准入是钩子天然的工作。你知道谁连上了、从哪里连、用的是哪张证书,于是可以决定这个会话应当放行还是拒绝。这个决定很便宜,只发生一次,也不需要看载荷里的任何一个字节。
配置分配属于同一种形态。钩子可以选择客户端地址、推送适合该身份的选项,或者写入服务器会为这个客户端读取的片段。由于决定是按会话作出的,当出问题、需要有人解释为什么某个客户端拿到了某个地址时,它仍然讲得清楚。
记录是第三种契合。一条说明会话已被放行的审计记录,连同身份和时间,正该放在这里。它不是流量日志,也不该假装是。如果以后有人问客户端在隧道里做了什么,答案必须来自别处。
数据包的工作属于数据包路径
如果需求是看到或修改在途的字节,组件就必须位于转发路径上:带队列的防火墙规则、流量控制钩子,或者用户态转发器。这些机制能看到数据包,它们有自己的状态、自己的失效方式和自己的性能特征。
在这一层修改载荷比读取它更难。改动一个字节就可能让长度字段、校验和或协议状态机失效,而三层隧道并不能替你挡住其中任何一项。它只意味着你修改的不是外层报头。在向任何人承诺改写内容之前,先把这部分修复工作量算进去。
接口抓包是这一类里最便宜的工具,而且它是观察,不是编辑。人们把两者混为一谈,因为两者都被描述成看流量,但抓包产出的是文件,数据包路径产出的是决定。
应用层改动属于应用
当目标是改变请求里包含什么、或者响应如何构成时,正确的位置是服务本身,或者有意终止该协议的代理。这些组件本来就理解请求、响应以及应用所期待的认证,而这正是数据包过滤器缺少的上下文。
地址转换排在这份清单的末尾是有原因的。它重写地址和端口,对载荷不作改动,所以它不是内容工具,无论它多么频繁地出现在一份同时也提到内容的配方里。
把这些边界分开,才让设计可解释。钩子里的会话策略、数据包路径上的数据包处理、以及在协议被有意终止之处的应用改动:这三句话同时也是一份评审清单。
TLS 仍然终止对话的地方
以上一切都绕不过一件事:隧道内部承载 HTTPS 的流量保留着自己的传输加密。持有 VPN 的密钥能给你内层 IP 包,却给不了另一端网站的会话密钥。
这就是为什么钩子即使在原则上也无法检查内容,也是为什么除非部署有意终止内层 TLS,数据包路径看到的只会是密文。 脚本契约 描述的是交给钩子的东西;它对载荷只字不提,因为没有载荷可以给出。