四元组,路由与防火墙 —— 一个数据包的西游记
很多朋友在折腾防火墙或者科学上网时,经常遇到一些底层网络问题不知如何深挖,或自己的需求过于古怪没人提过解决方案。究其原因是对 TCP/IP 或 UDP/IP 认知还处于黑盒子的阶段。
接下来我们来一个实际例子,跟着一个小数据包出门溜一圈,帮助你建立初步的概念,之后折腾时不至于完全瞎蒙。这个例子里,你装了一个使用 TUN 模式的梯子 sing-box
。
写快递单时,最重要的东西是寄件人地址和收件人地址。四元组基本上就是这个功能,再多一个寄/发件人邮箱号码的概念。想象一个大楼里住着 65535 户人,光有收件地址是不够精准定位到一个人的。
我下面所说的「四元组」,指的其实都是带上协议编号的「五元组」。
你的电脑内
包的诞生
你在 Chrome 里敲入 https://twitter.com
并按了回车。浏览器发现这是
https
协议,于是开始构造这个连接的第一个 TCP 数据包。
-
DST IP
数据包的目的 IP ,也就是收件人地址。浏览器查到
twitter.com
在内部 DNS 缓存里有记录为104.244.42.65
。我们先跳过本应有的 DNS 环节。DNS 只是把本例的 TCP 改成 UDP ,443 改成 53 而已。流程上没有特殊之处。
-
DST PORT
数据包的目的端口号。也就是收件人快递箱编号。这个例子里,用户没在地址里指定
:端口号
,所以浏览器填上了 HTTPS 协议默认的端口号443
。下面两个字段浏览器没填就提交给系统的网络 API 了,由系统帮它补上。
-
SRC IP
数据包的来源 IP ,也就是寄件人地址。系统根据当前网络的默认设置,帮忙填上了这一栏(本例中的具体内容下详),也就是系统默认网关所在网卡的当前 IP 。
-
SRC Port
数据包的来源端口,也就是寄件人的快递箱编号。系统从当前空闲的端口分配里随机挑了一个
43235
。同时操作系统把这个端口号返回给浏览器,浏览器开始监听这个端口后续的数据包传入。向系统申请监听成功后,系统会把它标记为「正在使用」。未来这个「连接」的后续数据包都会发生在这个
43235
端口上。1 2 3 4 5 6
# 如何查看现有的监听呢? $ sudo ss -tulp # -t: TCP # -u: UDP # -l: 只显示正在 LISTEN 的 # -p: 输出监听它的进程的信息 (不用 sudo 显示不完整)
现在数据包头长这样,这就是四元组了:
Address (IP) | Port (TCP) | |
---|---|---|
SRC | (下详) |
43235 |
DST | 104.244.42.65 |
443 |
我们暂不关心数据包的内容。
为什么 SRC 很重要?有 DST 不就能发包了?
是的你能发,理论上让对方能收到的信息已经齐备。但
- 我们讨论的是 TCP 连接,对方得回信的。如果你没填寄件地址,收件人该向哪儿回信呢?
- 网络服务提供商(ISP)很可能会把 SRC 错误(没填或填了内网地址之类的)的包给丢弃掉,因为这个数据包大概率是配置错误了,转发它是浪费硬件资源。一个不指望回信的 TCP 包?常理来说不太可能(
先去哪儿呢
现在数据包在内核里了。内核首先根据当前的系统路由表决定它要被发往哪个网络设备。
以使用 ip
命令的 Linux 为例,在启动 sing-box 的 TUN inbound 之前,路由规则(路由表的表)长这样:
|
|
启动 sing-box 之后则变为这样:
|
|
很容易猜出:
- 编号越小优先级越高
from
和to
指的是如何通过包里的四元组来做判断的。- 多出来的
90xx
和lookup 2022
应该就是 sing-box 的逻辑了。那 2022 究竟是啥呢?
|
|
就一句话,全导到 tun0
里了。果真如此么?我们可以做个测试,让系统告诉我们连接到某个 DST IP 时会怎么走:
|
|
啊哈,果然如此。顺便还告诉我们是第 2022 号路由表起作用了,以及系统会帮我们自动填写的 SRC IP 是什么 (172.19.0.0
)。
其它系统一定有对标的工具,一定有。你现在知道这东西叫什么了,就知道怎么搜索了。
上面这个探针例子不精确。若想更精确地检测路由规则,你需要提供更多四元组信息:
1
$ ip route get to 104.244.42.65 ipproto tcp dport 443
想想你心中那个数据包的四元组是什么内容,你就知道这里条件该怎么写了。能用的条件都在
ip route get help
里。
现在数据包里的四元组长这样,并准备发给 tun0
设备了:
Address (IP) | Port (TCP) | |
---|---|---|
SRC | 172.19.0.0 |
43235 |
DST | 104.244.42.65 |
443 |
梯子介入了
tun0
是被你的 sing-box 梯子创建的虚拟设备。sing-box 进程接到这个数据包后,也分析了它的四元组,根据你的 JSON 配置里写的规则,决定了它该走哪个 outbound 被转发出去。
举个例子,根据你写的规则,这个包被转发到某个基于 TCP 的梯子
1.2.3.4:5555
上了,那么 sing-box 会构建一个全新的 TCP 包,把老 TCP
包整个加密,成为新数据包的内容。然后将新包转发到正确的网卡上,不失一般性地举例为 enp3s0
(IP 为 10.0.0.15
) 。SRC 的 IP 和 Port 同样由系统告诉我们。下面是新包的四元组例子:
Address (IP) | Port (TCP) | |
---|---|---|
SRC | 10.0.0.15 |
36823 |
DST | 1.2.3.4 |
5555 |
sing-box 让系统不要查路由表直接把这个数据包转送给 enp3s0
。现在这个包到达 enp3s0
网卡的硬件缓冲区了,网卡把它发给了网关 10.0.0.1
,也就是你家里的路由器。
为什么 sing-box 知道是
enp3s0
网卡?
- 你可以在 JSON 配置里手动指定出口
.route.default_interface
- 或者,你可以让 sing-box 查找系统路由表的默认出站规则对应的网口
.route.auto_detect_interface
为什么系统知道网关是
10.0.0.1
这是路由器 DHCP 给你的信息的一部分。可以在
ip route
里看到详情。default via
开头的便是。IPv6 虽然是全球唯一的 IP ,但你的数据包依然需要一个物理出口才能流出你家。
ip -6 route
可以看 IPv6 的数据包网关在哪里。
路由器收到了
你家的路由器收到了这个包,同样解析了里面的四元组,发现 SRC IP 和 DST
IP 都不是它自己 10.0.0.1
,于是走 FORWARD
那套防火墙规则。规则跑完后发现这个包得被转发给 PPPoE 网卡里。临走前,路由器又顺手改了一下你的包头,并且又随机映射了一个新端口。
假设你家有公网 IP 114.5.6.7
。现在包长这样:
Address (IP) | Port (TCP) | |
---|---|---|
SRC | 114.5.6.7 |
32899 |
DST | 1.2.3.4 |
5555 |
谁允许路由器动我包的!
上面这个修改法叫 NAT
网络地址转换,更精确地说是 masquerade
包伪装。试想如果路由器没有改 SRC,那对面 DST是没法「回信」的:SRC IP 是个内网地址 10.0.0.15
。见上「为什么 SRC 很重要?有 DST 不就能发包了?」。
当然,路由器会记下这个转换关系:下次如果我收到 DST PORT 为 32899
的,记住是我们内网里 10.0.0.15:36283
那位要的数据包,转发给他就好。
这时你应该懂了,路由器通过不同的 port 来区分这个数据包应该被转发到内网中的哪台机器,原因是从内网里发出的包都被路由器 masquerade
过。
NAT 有两种,对源地址的修改 srcnat
/ SNAT
和对目的地址的修改
dstnat
/ DNAT
。上面这个例子是 srcnat
,我们只修改了四元组里的
SRC 部分。
dstnat
最典型的例子是「公网开端口」:你家 NAS 10.0.0.25
里跑着一个
BT 下载,正在监听 TCP 端口号 37000
。你家路由器有公网 IP 如上,并配置好了将公网端口 41234
DNAT 给内网的 10.0.0.25:37000
。请在脑内试着模拟一下,一个数据包从 INTERNET 流入 NAS 的过程中,每一环节的四元组是什么。
「防火墙」?
防火墙这名字有点误导,我觉得「包过滤和转发规则」更合适。你应该听说过「三表五链」这个说法,结合上面四元组的知识,我简单地介绍一下。
三表:
filter
:判断一个包能不能放行的规则nat
:要不要修改,以及如何修改一个包的 四元组 的规则- 注意
nat
只关心四元组
- 注意
mangle
:要不要修改,以及如何修改一个包的 内容 的规则
五链:
PREROUTING
:所有下面这些规则的前置,数据包刚进入内核的一环INPUT
:DST IP
为本机时生效OUTPUT
:SRC IP
为本机时生效FORWARD
:SRC IP
和DST IP
都不是本机时生效- 说明这个数据包流过本机但不停留,本机作为一个转手者存在。这种设备通常叫路由器。这也是为什么路由器里
FORWARD
的规则最多。
- 说明这个数据包流过本机但不停留,本机作为一个转手者存在。这种设备通常叫路由器。这也是为什么路由器里
POSTROUTING
:所有上面这些规则的后置,进入网卡之前的一环
我没公网 IP 啊
那就是多几个环节的 NAT。想象出了你家之后依然是一个 100.x.x.x
的「大局域网」,还要再做几个环节的 NAT 才能和公网 IP 通信。
互联网内
这个包现在到 ISP 的小区路由里了,ISP 根据你的四元组,不停地把包往下一个路由器传递。你的包经过了一跳又一跳,漂洋过海,绕了半个地球和一个大洋,终于到达了你的梯子服务器 1.2.3.4
。
怎么跳的?
traceroute
(及其衍生工具 mtr
或 nexttrace
)能尽力帮你还原每一跳的细节。
运营商怎么定义往哪儿跳的呢?这就是 BGP 的范畴了,三篇博客说不完。
梯子内
你的服务器 1.2.3.4
跑了梯子的服务端,正在监听 TCP 5555
端口。一个闲来无事的下午,它突然收到了一个数据包:
Address (IP) | Port (TCP) | |
---|---|---|
SRC | 114.5.6.7 |
32899 |
DST | 1.2.3.4 |
5555 |
它用梯子协议解密了包内容,还原出了真正的「原版」数据包:
Address (IP) | Port (TCP) | |
---|---|---|
SRC | 172.19.0.0 |
43235 |
DST | 104.244.42.65 |
443 |
梯子服务端用这个数据包请求了 104.244.42.65:443
(当然顺手
srcnat
了一下,要不然收不到回信了),拿到了回复:
Address (IP) | Port (TCP) | |
---|---|---|
SRC | 104.244.42.65 |
443 |
DST | 1.2.3.4 |
无所谓 |
以及一个新的返回包的内容。
好吧,服务端任务完成了,是时候回复给梯子客户端了。服务端把这个返回包也用梯子协议加密了后,把一开始收到的包的四元组反转了一下,作为新包的头:
Address (IP) | Port (TCP) | |
---|---|---|
SRC | 1.2.3.4 |
5555 |
DST | 114.5.6.7 |
32899 |
再把它扔进茫茫的互联网里。
后日谈
剩下的就是上面流程的反转了。因为每一步 NAT 都有记录,该答复的对象都是会收到答复的。
最后的最后,浏览器收到了客户端解密后的包:
Address (IP) | Port (TCP) | |
---|---|---|
SRC | 104.244.42.65 |
443 |
DST | 172.19.0.0 |
43235 |
看上去那么地美好,就像那啥不存在一样。
补充
其它暂时想不到了。留言催更我。
这回可以说说 OSI 七层模型了吧
确实,此时可以介绍下 OSI 的主要几层了:
- 1 物理层:双绞线、RJ45、光纤之类的
- 2 数据链路层:MAC 地址是这一层的概念
- 3 网络层:IP、ICMP(你最熟悉的
ping
就是)、IGMP(运营商的 IPTV 用的就是这个协议发送直播 URL 的) - 4 传输层:最常见的是 TCP 和 UDP。
- 7 应用层:HTTP、SSH、IMAP、SMTP、NTP……
TCP/IP 的意思是,建立在 IP 网络层之上的 TCP 协议。
注意 TCP 和 UDP 是相同地位的不同种东西,所以 TCP 端口号和 UDP 端口号是不一样的(虽然他们都是 65535 个)。上面表格里的 Port 我特地标注了是 TCP 的。
今后上淘宝看到「二层交换机」、「三层交换机」、「七层交换机」,你就知道他们的能力范围了。
上层应用使用什么下层技术不一定有绑定关系,比如 HTTP 用的 TCP,而
http3
(quic
) 是 UDP 。
Fake IP 是什么
非常麻烦的一环是如何拿到 twitter.com
的正确 IP 地址(以使得 sing-box
里基于 IP 的规则能生效)。
然后社区突然想到,其实应用(比如浏览器)只是要一个 IP 而已,至于这个 IP 最后真的发到哪个服务器,完全可以让服务端来判断。
加密后的数据包里已经有明确清晰的四元组了,并且和里面「真正」的包的四元组完全无关。拉上去再看看,想想对不对。
服务端那里倒是有信息判断原版请求的:对HTTP,明文的,看header的 Host:
就行;对 HTTPS,SNI 握手环节也是有明文的域名的。其它协议暂不考虑了。
客户端只需要收结果就好了,而服务端要考虑的就有很多了。
此时 FakeIP 就诞生了:随便生成一个 IP ,make 应用 happy 。
你应该能看懂一丢丢 wireshark 了
点开一个包的详情,你就能看到
- 第一层:从哪个网络设备收到
- 第二层:收发该包的网卡 MAC 地址
- 第三层:包的源 IP 和目标 IP
- 第四层:TCP 的源端口号和目标端口号
接下来 filter 一个包你应该也心中有数了,只要想象一下你要抓的包的四元组长什么样,然后让 ChatGPT 告诉你就行。