同一段对话里会出现两个端口号,两者都没有错。服务器日志显示提供服务的一端监听端口,笔记本电脑显示发出数据报的套接字,而中间的路由器可能在外出时改写第二个号。比较对话的两端,比比较数字本身更有用。下面的比较把监听端口、客户端套接字和转换后的源地址分开,因为它们回答的是不同的问题。

描述相对两端的两个数字

服务器配置的端口是客户端拨号的目标。它由策略固定,出现在配置文件中,并且必须在服务器一侧开放。客户端的源端口在本地选择,通常来自临时端口范围,存在的意义是让回复有地方落地。它可能每次重连都改变,设计上也不依赖它保持稳定。

中间的路由器可能把这个源改写成自己的 'NAT' 映射。因此,在服务器上看到的编号往往与笔记本选择的编号毫无关系。

源地址编号在出口处哪里发生变化

举一个虚构的 IPv4 例子:客户端位于执行转换的路由器之后,服务器只有一个 'UDP' 监听端口,一个数据报同时穿过两者。表中列出每个端点以及它会发生什么,而不是在三个观察点重复播放数据包。

EndpointWhat stays fixedWhat changes on reconnectWho owns it
Server listenerThe service port and the address the profile namesNothing; the listener does not follow the clientThe server operator
Client socketThe destination it dialsThe local source port, chosen per socketThe client, or its operating system
Translated sourceUsually the public address of the routerThe port, and sometimes the address after a network switchThe NAT device or the upstream carrier

每一行的目标都不变,变化的只是源的标识。UDP NAT 的要求把内部地址和端口与外部映射区分开来,这正是第二行与第三行的差别。路由器可以保留客户端的源端口,也可以另选一个;应用不应假定它会被保留。

从主机本身读取三个端点

下面的列表来自三台不同的机器,合起来展示了整段对话。第一个在服务器上运行,第二个在客户端上运行,第三个在任何存在映射的地方运行。单条日志永远无法同时显示三者。

ss -lunp 'sport = :1194'
ss -unp | grep 1194
conntrack -L -p udp | grep 1194

第一个列表证明服务器绑定在服务端口上,并显示哪个进程拥有它。第二个显示客户端的本地套接字以及它拨号的目标。第三个显示路由器创建的映射,这也是转换后的源唯一出现的地方。按这个顺序阅读,两个编号就不再显得矛盾。

重连会改变什么,不会改变什么

有些路由器在源端口空闲时保留它,有些每次分配不同的端口。两种行为都正常,要求前者的应用会在后者上出错。重连或切换网络后映射发生变化,并不是服务器的故障:客户端打开新套接字,路由器分配它现有的资源,服务器在同一个监听端口上看到新的源。真正的故障是期待服务器接受发往任意客户端端口的流量。

社区版 OpenVPN 客户端通过 nobind 获得这一行为,它让操作系统选择本地端口;远端端口仍来自配置文件。显式绑定是刻意的选择,而不是协议要求。服务器继续提供一个端点,而客户端各自选择自己的本地身份。

在记录中分别标注各层

隧道内部还有一段自己的对话,有自己的端口。隧道承载的 HTTPS 请求目标是 TCP 443,而外层加密连接使用 UDP 1194,因此改变外层源端口不会影响内层服务。

即使在外层,协议也很重要。UDP 1194 与 TCP 1194 是不同的端点,'TCP' 监听端口不会仅仅因为编号相同就收到 UDP 数据报。记录部署时,请把服务器监听端口、客户端套接字和转换后的源标为三件不同的事,并为每一项注明传输协议。

然后让策略跟随角色。服务器在监听端口上打开预期的传输,同时仍然验证对端;客户端的防火墙放行自己出站交换的回复,而无需发布入站服务。无状态过滤器必须明确写出返回方向,这正是这一区分在整齐的图之外也重要的实际原因。

相关文章