路由 TCP/IP 卷一 第一章:TCP/IP Review 总结
这章的主要内容是大致复习一下 TCP/IP 协议的内容,而不是对 TCP/IP 协议栈进行深入的研究。章末的一些推荐阅读书目会覆盖 TCP/IP 协议栈的深入部分。
TCP/IP Protocal Layers
Vint Cerf 和 Bob Kahn 提出的 TCP/IP 以及层式的架构早于 ISO 的七层 OSI 模型。
TCP/IP 的模型分成四层,自下而上是 Network Interface, Internet, Host-to-host 和 Application 层。它们对应了 OSI 的 7 层模型,至于这 7 层模型的内容是什么,会在下层展开,
Physical Layer
我之前一直不理解物理层和数据链路层怎么区分,以及物理层到底有什么东西,现在这本书告诉我了,物理层包含了 TCP/IP 协议栈中和物理媒介有关的部分,包含以下 4 类:
- 电信号或者光信号协议,比如电压、光强、时钟、编码和信号形状等信息;
- 机械协议,如连接器的尺寸和线缆的金属组成;
- 功能协议描述了一件事物做了什么,比如,「请求发送」是 EIA-232-D 连接器的第 4 针的功能描述;
- 过程协议描述了一件事情是如何完成的,比如,在 EIA-232-D 导线中,一个二进制的 1 表示电压小于 -3V。
所以从功利的角度看,一名网络工程师不需要花大心思考虑物理层的协议,因为这大多是通信工程师或者电子信息工程师要考虑的内容,偏硬件。
Data Link Layer
数据链路层包含了控制物理层的协议,比如媒介是如何共享、如何访问的,以及媒介上的设备如何被识别等等。我现在已经知道的数据链路层的协议是 IEEE 802.3/Ethernet,尤其是其中的 CSMA/CD 和 CSMA/CA 等等。
从这层开始,网络工程师就需要关注具体内容了。人们常说的 L2 就是指数据链路层。
Internet Layer
互联网层,和 OSI 模型的网络层对应,负责给逻辑网络的路由打基础,定义了包格式和地址格式等等。L3 是这本书的重点。
Host-to-Host Layer
主机到主机层,对应了 OSI 的传输层,明确了控制网络层的协议,就像数据链路层控制物理层一样。差别是,这一层控制的是逻辑链路,数据链路层控制的是物理链路。
Application Layer
应用层对应了 OSI 模型中传输层之上的内容,即会话层、表示层和应用层。即使有一些路由协议被划分在了这一层,但这层主要的功能是为应用程序提供网络接口。
在这层与层之间常见的现象是多路复用。它的含义是不同种类的高层服务借助了同一个底层提供的服务。就像一个主机上运行的不同应用程序都会借助传输层提供的服务,通过端口号来区分不同的应用一样。
现实生活中的例子,不同客户可以利用一家快递公司提供的快递服务,区分的方法是快递单上的收件人部分。
一个服务器接收到了一个 TCP 包,端口号是 443,协议栈就会把包传递给 HTTPS 的模块来处理,而不是邮件模块或者 SSH 模块。一个快递集散中心收到了一个包裹,收件人是张三,快递员会把包裹给张三,而不是李四、王五。
IP Packet Header
先展示 RFC 791 的 IP 报头格式。
1 | |
IPv4 包的包头格式是老生常谈的内容,到底有没有必要记住呢?在不少保研经验帖里,都提到同济大学在某次笔试的时候考了 IP 包的包头,并怀疑其是否有意义。
死记硬背是没有意义的,不过,如果理解了 IP 服务所需的每个组分,再将其对应到包头的格式上,难度就不大了。
一个 IP 包的长度是可变的,如果没有 Options 部分,则长度是 20 个字节。按照课本中常见的 4 个字节一行的排列方式,可以分成 5 行。前三行包含了一些 meta info,后两行是地址部分。
Row 1
先说第一行。
前 4 位是版本信息,如果是 IPv4 的包头,则是数字 4,IPv6 的则是数字 6。除此之外,还有许多已经分配了的号码,不过我们并不在意。
接下来的 4 位是包的长度。从数值的表示范围可以看出来,包头只能 0 - 15 长度。那么,合理推测,长度的单位肯定不是一个字节。事实上,长度是以 4 个字节为单位的包头长度。因为一个包肯定是 4 个字节为单位的(否则填充到 4 个字节的倍数),所以这样表示比较节约空间。
下面的 8 位表示 Type of Service,也就是服务类别。可以从两个层面描述这部分的功能。
早期,TOS 部分有两个功能:优先级和 TOS。优先级很好理解,优先级越高的包最优先被转发,但这可能造成一些不公平的现象。大家都想让网速更高,都把优先级设置为最高,那这部分内容就没有意义了。TOS 则根据吞吐量、延迟、可靠性等信息决定传递服务的种类,现在用的也不多。
现在,TOS 部分被 Differentiated Services 框架使用。这个框架使用了 TOS 的前 6 位,用来表示不同包的类别,之后,路由器会根据不同的类别分别排列和转发包。而剩下的两位被某些路由器用来支持显式拥塞指示。
最后的 16 位表示整个包的长度,和包头长度部分不一样,这部分长度的单位是字节。所以,每个 IPv4 包的长度在 0 - 65535 个字节之间。
Row 2
第二行和 IP 包的 Fragmentation 相关,发生在当前 IP 包的长度 + 数据链路包头的长度大于数据链路层最大传输单元时。换句话说,IP 包太长了,数据链路层无法用一个包装下全部内容。用生活中的说法,你这批货得拆分成多个包裹运输。
既然拆分成了多个包裹,就需要一些标记来表明这批货是同一批次。前 16 位的 Identifier 就用来表明这批货的标记。
下面的 Flags 3 位用来表示一些标记情况,用来调试。第一位没有用,第二位是 Don’t Fragment 位,当设置为 1 时,就算包太大需要分割,也不会分割这个包,而是会返回一个错误。
第三位叫 More Fragments 位,表示这个包后面还有更多的碎片。当一个路由器分割了一个包后,它把 MF 位置 1,只有最后一个碎片 MF 位是 0。
Fragment Offset 部分有 13 位,用来表示这批碎片的最开始到当前碎片头部的偏移量。它用来帮助碎片重新按顺序组装回去。
思考一下,Fragment Offset 的单位是什么呢?因为一个包最大是 65535 个字节, 需要 16 位来表示,因此如果要用 13 位表示同样的范围,应该以 8 个字节为长度单位。
Row 3
最开始的 8 位 Time to Live 表示包的存活时间。初始化一个值,经过路由器的过程中,值会减小,减到 0 则会被丢掉,这样可以防止一个包持续漫游在网络中,造成压力。
一开始的设想是如果包在一个路由器中延迟了超过 1 秒,则 TTL 减 1,但是这个设想因为难度太大,从来没实现,而且,现代化网络中很少出现一个包滞留在某个路由器超过 1 秒的情况。因此,真实的实现思路是,每经过一次路由器转发,TTL 减 1。所以在 IPv6 中,TTL 改名为 Hop Count。
8 位 Protocol 涉及到了前文所述的 multiplexing,多路复用。TCP/IP 协议栈需要根据这部分来确定更高层的协议是什么,是 TCP, UDP 还是ICMP 等等。
16 位的 Header Checksum 计算了头部的校验和,没有涉及到数据部分。计算的方法是对头部每两个字节当作一个计算单位,进行加法运算。最后,对运算结果取反(反码,one’s complement)写入到 Header Checksum 中。
当接收方接收到包后,仍然是以两个字节为单位进行加法运算,如果结果为全 1,则说明校验通过。
举个例子。发送方对头部计算得到的结果是
1011,则会将其取反变成0100写入到 Checksum Header 中。接收方接收到包后,做同样的计算,1011+0100=1111则说明校验正确。
Row 4 and Row 5
剩下两行很好理解,是 32 位的 IPv4 地址,分别是发送方和接收方的地址。
Options
IPv4 还包含 Options 字段,是可变长度部分。涉及到如下几个常用功能:
- Loose source routing:发送的包必须经过列出的 IP 地址,当然可能会出现一些绕路,只要最终的路线经过了途径点即可;
- Strict source routing:在 loose source routing 的基础上,包不能绕路,必须严格按照给出的 IP 列表进行发送;
- Record route:允许每个路由器写入其发送接口的 IP 地址,和 trace 的功能类似,只不过会同时记录包被发送和传回的所有地址;
- Timestamp 和 record route 类似,只是路由器会额外写下包到达的时间。
实验部分
在网络设备的调试过程中,有一些程序就利用到了本节所述的原理。
实验 1: IOS 扩展 ping 命令
在 Cisco IOS 的扩展 ping 命令中,我们可以设定一些高级参数,这些参数可以修改对应 IP 包的相应字段,实现高级功能。
第一个实验用来测试数据链路的 MTU 和 Options 的 Record route 功能。期望的现象是,返回的包能够记录其经过的路由信息,同时当包超过 MTU 时,会返回一个错误。下面是课本中的期望输出:
1 | |
然而现实很骨感,经过测试,如果 ping 外部网络,不会返回 Options;如果 ping 实验网络,有 Options,但不会返回最大 MTU。
1 | |
1 | |
如果傻乎乎地,每次 ping 的时候都进行多次选择,效率不高,且只要输入错误一个,前功尽弃,容易红温。因此,可以用 Python 写一段小程序。
在本次实验中,paramiko 比 netmiko 更方便,因为可以一次输入多个命令,netmiko 一次只能输入一个,当面对多个 \n 输入时,每次都修改 expect_string 有些冗长。
1 | |
实验 2: traceroute
traceroute 利用了 IP 包的 TTL 字段。程序会从 TTL = 1 开始向目标地址发包。每经过一个路由器,TTL 减 1。当一个路由器接收到 TTL = 0 的包时,它会丢掉这个包,并返回一个错误信息。这样就定位了 n-hop 路由器的 IP 信息与传播耗时。
程序发送的包的 TTL 逐渐增大,因此传播的范围逐渐增加,最终会到达目标地址。与此同时,传播路径上每个路由器的信息也都记录下来了。
不过由于 Windows 防火墙的设置,在 Cisco IOS 中只能获取到网关的 traceroute 信息,更远的信息获取不到。
1 | |
IPv4 Addresses
IPv4 的地址长度为 32 位,包含网络号和主机号。网络号的作用是描述一个数据链路,主机号的作用是定位这个数据链路的主机。
举个生活中的例子,宛平南路就相当于网络号,表示了一个数据链路;曹安公路也相当于是网络号,表示另一个数据链路。表示数据链路还不够,还需要主机号才能表示一个主机。600 号相当于主机号,不同的数据链路都可能有 600 号,不过如果限定了宛平南路,那么,宛平南路 600 号就指的是唯一的地点。
表示一个 IP 地址有许多种方法,最底层的是 32 位二进制数,也可以用二进制数对应的十进制数来表示,不过不好阅读。常见的表示方法是点分十进制,比如 192.168.1.1。三个 . 把字符串分成了 4 部分。每个部分对应 8 位,用十进制数表示,范围是 0 - 255。
值得注意的地方是,点分十进制只是为了方便人类阅读,机器内部看到的仍然是 32 位二进制数。
TCP/IP 中的网络地址和其他协议的不同点在于,网络号和主机号的大小可变,这样可以适应不同大小的网络。
First Octet Rule
总的来说,TCP/IP 的 IP 地址分成三种类型,分别适应大型网络、中型网络和小型网络。这几种网络对应的网络号和主机号占比不同,大型网络的网络号部分小,主机号部分大;小型网络则反之;中型网络在二者之间。总之,和三种网络规模对应的三种 IP 地址类型是:
- A 类地址:第一个字节是网络号,后三个字节是主机号,有 256 个网络号组合,但对于每个网络号,却有 16,777,216 个主机号之多。
- B 类地址:前两个字节是网络号,后两个字节是主机号,网络号和主机号都只有 65,536 种可能;
- C 类地址:前三个字节是网络号,第四个字节是主机号,数量和 A 类地址对应的相反。
因为所有的 IP 地址都是 32 位,因此有必要给它们在形式上有所区分。这就是本节的「首字节原则」。对 A 类地址,第一个字节的第一位是 0,剩余位随意。因此,网络号的范围是 0 - 127。此外,0 是默认地址的一部分,而 127 预留给本地回环地址。
对于 B 类地址,前两位是 10;对 C 类地址,前三位是 110,因此对应的十进制范围分别是 128 - 191 和 192 - 223。注意到 223 并没有达到 8 位二进制数能表示的最大范围,因此实际上还有 D 类地址,遵循同样的规则。
这种地址的划分方法符合「前缀码」的编码规则,不同类别的地址前缀不能相同。编码形成了一棵 Huffman 树。
看上去目前进行寻址很简单,只需要根据第一个字节的前缀特征来区分网络号和主机号,但仔细想想,并没有那么轻松。
注:这本书使用了 bogus IP 地址来表示 IP,每类 IP 地址都有保留 IP 段,它们是:10.0.0.0 - 10.255.255.255,172.16.0.0 - 172.31.25.255, 192.168.0.0 - 192.168.255.255。
我觉得记法是先记住 IP 段,对于范围的记忆,A 类地址可变部分是 24。B 类的是 20,C 类的是 16。
Address Masks
在继续之前,先补充一个概念,叫做子网掩码。表示一个数据链路全体,可以通过网络号不变,主机号全置 0 的方式来实现。比如,192.168.31.1 这个主机所在的数据链路可以用 192.168.31.0 来表示。网络号必须是前三个字节的内容不变,而主机部分可以随意利用。
对于一个设备来说,它除了知道自己的 IP,还需要知道自己所在的子网才能实现路由的功能,这一功能通过子网掩码来实现。子网掩码也是 32 位,对 IP 的网络部分,子网掩码置 1,主机部分,子网掩码置 0。
用数学的语言来描述,得到子网掩码的过程是一个映射。它的输入是一个 IP 地址,输出是一个子网掩码。映射的过程是一个布尔操作:所有网络位输出 1,所有主机位输出 0。
对于上文所述的 192.168.31.1 这个 IP 地址,其对应的子网掩码就是 255.255.255.0。
反过来,如果知道子网掩码和 IP 地址,通过与运算就能得到网络部分。
问题来了,为什么要用子网掩码呢?首字节原则似乎比子网掩码方便多了?
Subnets and Subnet Masks
之前讲到,每个数据链路都需要一类的 IP 地址来表示,比如 192.168.1.0,就只能分配给一个数据链路。ABC 三类 IP 地址加起来有 1700 万种,说实话,并不多;而且对于一个 B 类地址,充分利用它需要超过 65,000 个设备,难以实现!
因此,让 IP 地址可行,需要把每类 IP 地址再划分为子网,这就是子网掩码派上用途的地方。因为每类 IP 地址的主机号可以随便利用,网络号部分是通过子网掩码来区分的。
现在,地址的类别界限就没有那么明确。比如,一个 B 类地址可以通过扩展子网掩码的方式形成子网。子网就是 A,B,C 类地址空间的子集。
现在一个 IPv4 地址的表示就分成了网络号、子网号和主机号三个部分。其中子网号是通过占用主机号一定份额来实现的。这点可以留意一下,因为在 IPv6 中,子网号是通过占用网络号的一部分来实现的。
两点提醒:
- 并不是所有路由协议都支持子网位是全 0 或全 1 的情况。因为在这些 classful 协议中,无法区分全 0 子网和主网络号。比如一个 B 类地址下的全 0 子网这样来表示:
127.31.0.0,但它也表示了主 IP 地址的全体。同理,一个全 1 子网也不可行,因为无法区分到底是这个子网的广播地址还是主网络号的广播地址。 - 另外是关于措辞的注意事项,如果扩展了一个 B 类地址的子网掩码为 24 位,不能说把 B 类地址子网为 C 类地址等等。
最后,关于子网掩码的表示,最常见的是 255.255.255.0 这种点分十进制表示,Cisco IOS 或者 Windows 中的 IP 设置都要求这种格式;最近,172.31.0.0/24 这样的 bitcount 表示比较欢迎,一些云服务商支持这种表示来配置出入站规则;最后是十六进制表示,不赘述了。
Designing Subnets
接下来设计子网。需要注意的规则是,子网不能全 0 全 1,主机也不能全 0 或全 1。子网的规则上文说过了,主机的部分也好理解,全 0 表示这个子网本身,全 1 表示广播地址。
到底需要多少子网、或者到底需要多少主机,根据这个公式:$2^n - 2$,减去的 2 表示全 0 和全 1 地址。得到的 $n$ 表示子网位数和主机位数。
设计的四个步骤是:
- 确定所需子网或主机的数量;
- 用 $2^n - 2$ 的公式计算子网位数和主机位数,如果多种情况满足,选择最符合未来扩展需要的位数;
- 在二进制下,确定子网空间的组合方式,转化为点分十进制范围;
- 在二进制下,固定一个子网,考虑主机号的范围,转化为点分十进制的范围。
这里强调在二进制下处理范围问题,因为如果想当然地用十进制计算,可能会出现意外错误。
Breaking the Octet Boundary
本节讲解了一个具体的计算例子,从略。
Troubeshooting a Subnet Mask
这节给出一个 IP 地址排错的方式,总之,是给定了一个 IP 地址和子网掩码,来确定这个 IP 地址是否在正确的范围内。
- 用二进制写下子网掩码;
- 用二进制写下 IPv4 地址;
- 知道了 IP 的类别,可以划分出网络部分、子网部分和主机部分;
- 主机部分置全 0, 得到主机所在的子网地址;
- 主机部分置全 1,得到广播地址;
- 在这两个地址范围内的地址是合法的主机地址。
这里举的例子是 172.30.0.141/25,根据上面的规则,子网地址是 172.30.0.128,广播地址是 172.30.0.255。
初学者可能觉得很难受,因为这里的子网地址不是全 0,广播地址第三个字节有个别扭的 0。这一切都是因为 IP 地址的十进制表示,回归到二进制,一切是显然的。
Address Resolution Protocol(ARP)
数据链路层需要地址才能发送报文,如何获得数据链路层的地址呢?IPv4 协议采用的是 ARP。ARP 实现了一个从 IPv4 地址到 MAC 地址的映射。
ARP 报文头部的格式浅显易懂,总是以硬件、协议对的形式出现。依次是类型、长度、操作、发送放地址和目标地址。
有一些 ARP 的变体在路由中发挥作用。
Proxy ARP
代理 ARP 达到的效果是,路由器代理了主机响应请求。
比如,一个主机要和不同网段的另一个主机通信,但是不知道对方的 MAC 地址。如果配置了默认网关,则寻找目标的麻烦事会传递给网关。但如果没有配置默认网关,这个主机会尝试发送一个 ARP 请求。如果链路中的路由器知道自己可以到达接收方,则会返回自己的出口 MAC 地址。
问题来了,要是链路中的路由器不知道自己是否能到达目标呢?比如主机请求了一个之前从未访问过的 IP。那么此时无人响应,发送方会得到错误信息。所以配置缺省路由还是有必要的。
下一个特殊场景是,一个路由器划分了两个子网,但是子网内的主机不知道它们在两个不同子网里,而是以为在同一个普通 B 类地址中。一个子网的主机想给另一个子网的主机发送消息,同样会经过 ARP,中心路由器会代替目标响应请求,返回自己的出口 MAC 地址。
这个场景的 takeaway 是,对主机来说,中心的路由器好像不存在,子网的划分也好像不存在。
Gratuitous ARP
平凡 ARP 的意思是,一个主机可能会偶尔发送一个以自己 IPv4 地址为目标的 ARP 请求,有以下几个用处:
- 检查重复地址。要是叫自己的名字由别人答应,说明有重名的人;
- 更新他人的 ARP 缓存。ARP 请求会包含发送方的网络地址和物理地址,因此接收方可以根据这点被动地更新 ARP 缓存;
- 一个运行热备份路由协议(HSRP) 的新接班的路由器会从此来更新子网路由的信息。
许多 IP 实现不使用平凡 ARP,而且它默认被 IOS 禁用。
为什么它被默认禁用呢?因为平凡 ARP 可能被黑客利用。细想,主机会通过平凡 ARP 自动更新 ARP 缓存,如果我伪造一个广播的 ARP 消息,告诉所有人张三的网络地址对应的物理地址是我的。那我就可以窃取所有给张三的信息。
Reverse ARP
反向 ARP 则是把一般 ARP 请求反过来,通过物理地址请求 IPv4 地址。它的用途在于,让初始化阶段的设备知道自己被分配的 IPv4 地址。当然,现在它已经被 DHCP 取代了。
Internet Control Message Protocol(ICMP)
ICMP 协议规定了一系列管理网络的消息格式,一般来讲,包括请求、响应和错误消息。
ICMP 报文格式很简单,TYPE 用来指明大类,CODE 是大类中的小类,之后是 16 位的校验和以及后序的可变部分。
最常见的肯定是 ECHO 和 ECHO REPLY,用在 ping 中。前面讲的 traceroute 也可以用 ICMP 协议来实现,如果设置了 DF 位但是包的大小超过了 MTU,大概率会返回 type:3, code:4,
Fragmentation Needed and Don’t Fragment Flag Set。
不过前面的实验部分讲到了,我们的
traceroute实验只能在虚拟局域网中得到回答,AI 推测的原因是,思科 IOS 的traceroute包裹了 UDP 包,而不是 ICMP 包,因此包太大的时候并不会返回超过 MTU 的错误。同时,由于模拟器 IOS 的限制,我也没法把 UDP 更换为 ICMP。
有 3 个 ICMP 类型在路由功能中发挥了显著作用:
- Router Advertisement 和 Router Selection,它们被 ICMP 路由发现协议(IRDP)使用,是 Windows 大部分系统使用的路由器发现协议;
- Redirect,也就是路由器 A 告诉主机,应该选择数据链路上的另一个路由器 B 来转发这个目标地址的消息,因为 A 也得转给 B 才能发送这个消息;
一个防止 Redirect 的取巧是把一个主机的默认网关设置为自己,这样,主机会发送一个 ARP 请求,知道如何到达目的地的路由器会响应请求。
但同样有前文 Proxy ARP 谈到的问题,要是没有路由器知道(目标地址已在缓存中)目标地址怎么走,那就没人响应,即使路由器通过 ARP 可能会找到去往目标地址的路径。如果设置了主机的默认网关为路由器,则不会有此问题,因为即使路由器在缓存中并不知道目标地址的路由,但可以发送 ARP 请求更新缓存。
Host-to-Host Layer
主机到主机层,也就是传输层,有两个大名鼎鼎的协议:TCP 和 UDP。作者认为这个层次的命名非常好,因为 IP 层负责网络之间的逻辑链路,而这一层负责了从一个主机到另一个主机的完整逻辑链路。不过按照我的想法,我觉得这层其实是主机中的应用到应用层次。
TCP
TCP 我们见过很多次了,它是一个面向连接的、可靠的数据传输,提供了一个点到点连接的幻象。
这句话我觉得特别好,点到点连接的特征是什么?
- 只有一个到终点的路径,从连接一端进入的包不可能迷失,因为只有一个地方能出去;
- 包到达的顺序和发送的顺序一样。
TCP 面向连接的实现依赖了以下三个机制:
- 包是有序列号的,所以 TCP 可以把乱序的 TCP 包排序;
- TCP 用了 ACK、校验和、计时器组件来提供可靠的传输;
- TCP 通过窗口机制来管理包流,这降低了丢包率。
看看 TCP 报头的格式吧。TCP 报文的基础长度还是 5 行,每行 4 个字节。
第一行是源端口号和目的端口号,各自都是 16 位,从 0 - 65535,常见的有 80,22,443 等等。
第二行是顺序号,表示当前发送的报文在发送方的数据流中的位置。
第三行是 ACK 号,表示预期接收到的下一个报文的编号。
第四行比较混杂。先是 4 位的 Header Length 表示头部的长度,单位是 32 位,所以 IP 包的最大长度和 TCP 包一样。之后是 4 位的保留位。之后是 8 位的标记位,设计 ACK, PSH, RST, SYN, FIN 等几个标志位。
之后 16 位的 Window Size 用来流量控制,表示从当前 ACK 号开始,能接收的段数量,以字节为单位。
第五行分成左右两部分,左边是校验和,考虑了头部和数据部分;右侧是紧急指针,在 URG Flag 为 1 时有效,它加上顺序号表示紧急信息的末尾。
第六行则是 Options 和填充部分,最常用的 Options 是最大段长度。
UDP
为什么有的应用会倾向于一个不可靠的 UDP 连接呢?因为 UDP 快啊!
- UDP 的报文头部很小,只有两行(4 字节一行)。第一行还是端口号,第二行是报文头部长度和校验和;
- 校验和还是可选的。
章末有一个思考题,我觉得很耐人寻味:UDP 的意义在什么地方呢?它在一个无连接层上提供了一个无连接层的服务,它做了什么呢?
这个题目很狡猾,它抓住了 UDP 和 IP 都是无连接的特点,然而 UDP 是一个传输层的协议,IP 是网络层的。UDP 在 IP 的基础上为同一个主机上的不同应用提供服务——根据报头的端口号。所以,仅靠 IP 层,可以把报文发送到目标 IP,但只有传输层的协议才能精确到目标主机的应用。
