Internet protocol suite

Internet protocol suite

标准 TCP/IP 模型。标准模型采用分层设计,下层的数据包为 Header+ Payload + Footer (Footer 只有部分协议有),其中 Payload 即为上一层的数据包。

|-应用层(Application layer):App Data
|-传输层(Transfer layer):TCP / UDP Packet
|-网络层(Network layer):IP Packet / ARP Packet
|-链路层(Link layer):Ethernet Frame

Structure

Ethernet Frame

即以太网帧。有很多版本标准。目前大部分设备实际使用的是 "Ethernet II framing" 格式,也是 IP 网络标准以太网帧,其帧格式如下:

MAC Header (14 bytes) + PAYLOAD (46-1500 bytes) + CRC Checksum (4 bytes)

ARP Packet

ARP 是二层协议,用于查询本地 LAN 网络里指定 IP 主机的 MAC 地址。发送任何 IP 包之前,都必须获取对方的 Mac 地址;但如果对方不在本地 LAN 网段,那么获取的是查询路由表得到的出口网关的 Mac 地址。ARP 协议的查询 request 包会广播给本地 LAN 里所有设备。因此对于较大规模的 LAN,存在广播风暴(broadcast storm)问题,解决方法是 VLAN

发送 IP 包流程:

本机 App 想要发送 IP 包 src IP -> dst IP ->
    dst IP 位于本机所属的 LAN ? -> ARP 查询 dst IP 的 dst MAC -> 发送 Ethernet Frame: "dst Mac, src MAC, src IP, dst IP"
    否则: 查询路由表寻找到 dst IP 的路由 -> 找到匹配的路由项的网关 gw IP -> ARP 查询 gw IP 的 gw MAC -> 发送 Ethernet Frame: "gw Mac, src Mac, src IP, dst IP"

主机设备网卡接收到 Ethernet Frame 后,会检查 Ethernet Frame Header 里的 "destination Mac" 字段,如果与本机 MAC 一致,才会将从帧拆出 IP Packet,交给 TCP / IP 协议栈处理(否则直接忽略)。

而交换机(包括路由器集成的 LAN 交换芯片)接收到 Ethernet Frame 后,则会根据 Header 里的 "destination Mac" 将数据帧转发到对应的网络端口上。

IP Packet

即 IP 包。格式: Header + Payload

其中:

  • IPV4:Header 为固定的 20 bytes + options (0-40 bytes)。(现实中,大多数情况下没有使用 options,所以 Header 一般就是 20 bytes)
  • IPV6:Header 为固定的 40 bytes。

Payload 理论上可以有任意字节,只要整个 IP Packet (Header + Payload) 不超过 65536 字节即可。但如果 IP 包大小超过 Ethernet Frame 的 Payload 容量限制,那么 IP 包通过以太网传输时必须要分片(Fragmentation),把一个 IP 包分成多个 IP 包传输(分片后的 IP 包称为 "fragmented packet",以下简称 "子 IP 包")。分片有很多潜在的问题:

  • 如果 IP 包的 Header 里设置了 DF (禁止分片)标志位,那么这个 IP 包在通过链路中某个中间节点设备时如果需要分片才能传输,设备会直接丢弃这个 IP 包(并会通过 ICMP 协议给源设备发送一个回应以告知其)。
  • 分片后的所有子 IP 包会在目的地设备 (dst) 的网络层重组还原为 1 个 IP 包 (链路的中间节点设备会直接转发子 IP 包,不会重组),如果其中任意一个子 IP 包在传输中丢失或延迟了,那么(等待超时后)dst 会丢弃所有已收到的 IP 子包。
  • 网络链路的中间节点路由器可能根据IP包的四层信息(如tcp/udp端口)做策略路由,由于分片后的 IP 包只有第一个子包里含有四层信息,路由器可能将这些IP子包通过不同的端口路由发送,造成网络延迟(Round trip time, RTT)不一致。

ICMP Packet

ICMP 是 IP 层附属协议。ICMP Packet 也是一个 IP 包,其格式如下:(以 IPV4 环境下的 ICMPV4 为例)

IP Header (20 bytes)(典型值) + ICMP Header (8 bytes) + Payload

MTU

链路层允许的(无需分片的)最大发送 IP 包大小即为 MTU (Maximum transmission unit, 最大传输单元)。

对于标准的 Ethernet,MTU 即为 1500,这也就是 IP 包通常的最大大小。

但如果使用了封装协议(PPPoE、GRE / ipsec 以及其它任何 IP over X 的 tunnel / VPN),那么协议里封装的 IP 流的 MTU 都会降低,因为任何包装协议都会增加 overhead。

例如,PPPoE 协议(常用于宽带拨号)在实际的 IP 包前增加 8 bytes 的 overhead,所以通过 PPPoE 连接的网络 MTU 只有 1500 - 8 = 1492。

本地回环网络 lo 的 MTU 通常为(IP包)最大值 65536。

PMTU

PMTU (Path MTU,链路 MTU)指经过多个节点的一条网络链路的所有节点设备的 MTU 的最小值。PMTU 是一条网络链路上能够传输的最大 Ethernet Payload 大小。

可以使用 ping 来测试 PMTU。设置 ping 参数以指定 ICMP 包的 Payload 大小,并设置禁止 IP 包分片标志位:

  • Linux: ping -M do 8.8.8.8 -s 1464
  • Windows: ping -f -l 1464 8.8.8.8

上面的示例命令中的 1464 是链路 MTU 为 1500 时的最大能够发送成功的 ICMP 包 Payload 大小(IPV4 header + ICMP Header = 28 bytes)。

如果 ping 命令显示 "Frag needed and DF set" 或 "Packet needs to be fragmented but DF set." 之类的错误提示,则说明由于链路 MTU 问题导致了 ICMP 包发送失败。

TCP / UDP Packet

TCP Packet (TCP包) 格式: Header (20 bytes + 可选最大 40 bytes) + Payload。

UDP Packet UDP包) 格式:Header (20 bytes) + Payload。

理论上 TCP (UDP) 包的 Payload 大小也可以是任意字节,只要整个 TCP / UDP 包大小不超过 65535。但由于实际上 TCP 包必须通过下层的网络层和链路层传输,TCP / UDP 包的 Payload 实际最大大小有限制,对于 TCP 协议,称这个限制为 MSS (Maximum segment size)。

TCP包 header 的可选区域在实际使用中用于放 Tcp Timestamp option 和 SACK。

MSS

MSS 即为 TCP 包 Payload 的最大大小。

MSS = MTU - IP Header Size - TCP Header Size (根据标准,计算 MSS 时,TCP header 按照固定的 20 字节算,不考虑 optional 部分)

对于标准 Ethernet, MTU = 1500, IP Header 和 TCP Header 都是 20 字节,所以 MSS = 1500 - 20 -20 = 1460。

TCP 协议支持通信双方之间的 MSS 协商,取己方 MSS 与对方 MSS 当中的较小值作为己方发送数据包时的 MSS。

通过 PMTUD (Path MTU Discovery),整个通信链路中的任意一个节点都可以改变经过其的 TCP 会话的 MSS。

对于 Linux 而言,一条 iptables 命令就能解决几乎所有 MSS / MTU 相关问题:

iptables -t mangle -A POSTROUTING -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu

关于 MTU / MSS 的详细解释,参考这篇文档:Resolve IP Fragmentation, MTU, MSS, and PMTUD Issues with GRE and IPSEC

PORT

TCP / UDP 协议使用一个2字节的无符号整数型 PORT (端口) 字段用于区分设备上运行的不同程序。PORT 范围为 0 - 65535。(其中 "0" 端口特殊,一般不使用)

  • 特权端口(Privileged ports):0-1023。拥有 root 权限的 App 才能监听或绑定本地设备上这些端口。
  • 普通端口:1024-65535。任何 App 都可以监听或绑定(只要端口没有已经被其他 App 占用)。

Ephemeral port

App 作为客户端连接服务器端时,如果没有显式地指定自身绑定的端口号,OS 会自动给其分配一个随机的临时端口,即 Ephemeral port。在很多 OS 里,App 显式地绑定 0 端口会被 OS 理解为使用 Ephemeral port,尽管 0 端口本身在 TCP/UDP 协议里是完全合法的端口号。

Ephemeral port 范围:

  • Linux:32768-61000 (/proc/sys/net/ipv4/ip_local_port_range)
  • Windows Vista+:49152-65535 (IANA 推荐使用的 Ephemeral port 范围)
Linux
# cat /proc/sys/net/ipv4/ip_local_port_range | hexdump  -C
00000000  33 32 37 36 38 09 36 31  30 30 30 0a              |32768.61000.|
0000000c
  • 0x09 == "\t",0x0a == "\n"

Last update: 2018-07-12 09:17:01 UTC