知道 IP 之后,数据包下一步该往哪里走?
知道目的地,还不代表知道第一步
上一次,数据包终于弄清楚了 www.example.com 是怎么变成一个具体地址的。手里握着这个来之不易的 IP,它以为接下来的事情很简单——出发就是了。
但它很快卡住了。
它盯着这个 IP,发现一个问题:知道最终要去哪里,和知道这一步该往哪个方向迈,根本不是一回事。
方向感,不是名字带来的,也不是 DNS 带来的。
数据包意识到,自己需要先弄明白一件更基础的事:
这个目的地,离我近不近?
目标到底在不在这个网络里?
本机 IP 和子网掩码
数据包这才想起,自己出发的地方——也就是这台主机本身——同样有一个 IP 地址,而且这个地址通常还带着一个”/“后面的数字。比如:
1 | 192.168.1.100/24 |
这个 /24 不是随便写的。它划出了一条界线:前 24 位属于”网络部分”,剩下的部分才是”这个网络里具体是哪一台主机”。换句话说,/24 告诉这台主机:自己身处的这片直接可达的网络,范围是:
1 | 192.168.1.0/24 |
也就是从 192.168.1.0 到 192.168.1.255 这一段。
同一个网络,意味着什么?
有了这条界线,数据包终于可以回答那个问题了——只要看目的 IP 是不是也落在这个范围里。
假设这次的目的地是:
1 | 192.168.1.50 |
这个地址落在 192.168.1.0/24 里面。
数据包明白了:
“原来它就在我身边。”
假设换一个目的地:
1 | 8.8.8.8 |
这个地址完全不在 192.168.1.0/24 的范围里。
数据包明白了:
“这一次,目标在很远的地方。”
同一个问题,答案却完全不同——这决定了接下来完全不同的两条路。
如果目标就在附近
目的主机就是下一跳
先看第一种情况。
如果目的 IP 是 192.168.1.50,而本机是 192.168.1.100/24——两者在同一个网络里——数据包发现一件让它有点意外的事:
不需要经过任何网关,直接把数据交给对方就行。
1 | 本机 |
这里藏着一个很容易被忽略的事实:下一跳不一定是网关。在同一个直连网络里目的主机自己就可以是下一跳。
但我还不知道它的 MAC
方向倒是确定了——直接送过去就行。可数据包很快发现,自己手里只有一个 IP,192.168.1.50,并不知道对方在这条链路上到底是谁,该把数据交给哪块网卡。
这个问题,先放一放。现在只需要记住:方向已经确定了,细节留给以后。
如果目标在远方
数据包掏出自己的”小地图”
现在换成第二种情况——目的地是 8.8.8.8,一个明显不在本地网络里的地址。
这一次,直接发送行不通了。数据包需要一样新的东西来做决定:路由表。
路由表可以理解成本机手里的一张小地图,上面记录着”不同的目的地,该往哪个方向送”。
路由表告诉它下一跳
Linux 上,可以直接看到这张地图:
1 | ip route |
输出大致是这样的:
1 |
|
第一行说的是:192.168.1.0/24 这个网络,直接从 eth0 出去就能到,不需要绕路——这正是前面那种”本地直连”的情况在路由表里的样子。
第二行是这次真正用得上的:default via 192.168.1.1 dev eth0。它的意思是:当没有更具体的路由能匹配目标时,把数据交给 192.168.1.1,从 eth0 这块网卡出去。
8.8.8.8 没有匹配到任何更具体的条目,于是落到了这条 default 路由上。
默认网关到底是什么?
这里正好可以说清楚一件容易被含糊过去的事:默认网关,就是默认路由指定的那个下一跳。它不是”所有远程流量的必经之路”,它只是”当没有其他更具体路由可用时”被选中的那一个。如果路由表里还有别的、更具体的条目能匹配某个目的地,数据就会走那条更具体的路,压根用不上默认网关。
对这次的 8.8.8.8 来说,匹配上的正好是默认路由,所以下一跳就是 192.168.1.1。
让 Linux 告诉我们下一步往哪里走
查看本机地址
不妨亲自看一眼这一切是不是真的。先确认本机的地址和前缀:
1 | ip addr |
在输出里找到类似这样的一行:
1 |
|
/24 就是前面说的那条网络边界线。
查看路由表
再看一眼路由表:
1 | ip route |
前面展示过的两行,大概率就出现在这里——一条直连网络的路由,一条默认路由。
使用 ip route get
最有意思的一步来了。与其自己在脑子里判断”这个目标在不在本地”,不如直接问 Linux:
1 | ip route get 8.8.8.8 |
输出类似:
1 |
|
这一行信息量不小:
1 |
|
再试试一个本地目标:
1 | ip route get 192.168.1.50 |
如果这个地址在本机的直连网络里,输出会明显不一样——不会再出现 via 某个网关,而是直接显示从哪块网卡送出去,因为目标本身就在这条链路上。
(如果手边的网络地址和示例不一样,把命令里的 IP 换成自己环境中的真实地址即可,原理是通用的。)
核心的一张图
把这一整套判断过程画出来,就是这样:
1 | 目的 IP |
数据包终于知道第一步怎么走了
绕了一圈,数据包总算把最开始那个问题想明白了。
它手里的目的 IP,从一开始就只负责一件事——告诉它最终要去哪里。而这一步具体该交给谁,靠的是另一套东西:本机的 IP 和子网掩码,加上一张路由表。
如果目标就在身边,它自己就是下一跳。如果目标在远方,路由表会给出一个下一跳的地址——可能是默认网关,也可能是某条更具体的路由指向的地方。
有一件事让它印象很深:它完全不需要知道从这里到 8.8.8.8 的完整路线。它只需要弄清楚眼前这一步该交给谁——剩下的,交给下一跳去操心。
但是,下一跳的 IP 还不够
方向确定了。
对于这次的 8.8.8.8,数据包已经知道:下一跳是 192.168.1.1,从 eth0 出发。
可它低头一看,又卡住了。
手里这个 192.168.1.1,终究还只是一个 IP 地址。而在眼前这条链路上,数据真正要交出去的时候,靠的从来不是 IP——是这条链路上具体的那块网卡的身份。
数据包第一次认真地问自己:
知道了 IP,为什么还需要 MAC?
这个问题,留给下一次旅行。
知道 IP 之后,数据包下一步该往哪里走?