IPv6 不能分片总不被理解,可 IPv4 TCP 几乎都携带 DF 标识却无人问津。意思是说,我们天天打交道的 IPv4 网络上的 99.999% 的 TCP 流量也都不允许分片,却没人觉得不合理。
人们从 RFC791(真看过的人并不多) 就习惯了 IP 有分片机制,将其视作一个核心特性(feature),但其实分片挺鸡肋挺没用的,反而更像是个问题(issue),所以 IPv6 直接取消了它。本文主要谈这个问题。
由于事实上的传输层协议几乎就是 TCP 和 UDP,IP 分片恰恰对它俩都有问题:
- TCP 载荷分片:若承载 TCP 段的 IP 数据报被分片,只要一个片段丢失,整个原始 TCP 段全部要重传,这类似一个无条件 GBN,而重传段亦可能再次被分片(再次被丢失?),而让 TCP 自己做这件事,配合 MTU 发现,TCP 对发送更小的段,若丢失,它只需要重传丢失的段。
- UDP 载荷分片:若承载 UDP 段的 IP 数据报被分片,若有一个片段丢失,接收端亦要等待重组,而 UDP 本身并不要求可靠送达,实际上不需要 IP 层等待重组。
IPv6 去掉这种没用的老式机制并没有带来任何不便,反而提高了效率。
日常场景,几乎所有 TCP 流量,绝大多数 UDP 流量均设置了 DF 标识,我们抓包即可观察。几乎任何协议都有自己适应当前 MTU 的机制,带内实现效果更佳。我以 TCP 举例。
TCP 在重传失败时会启用 MTU 适配,尝试每次将 mss 减半,获得适合当前网络的 mss,快速降低 mss 成功率较高,这并不会过分降低传输传输效率,此后 TCP 会采用二分探测机制尝试将 mss 再次增大到协商大小,以适配网络实际的 MTU 或捕获路由切换导致的 MTU 增长。
以 Linux TCP/IP 协议栈实现为例,来从代码角度看看这如何实现。
若 TCP 屡次重传均失败,这大概率意味网络出了问题(小概率是主机出了问题),原因之一可能是路由重收敛,而 MTU 发生了变化,此时便是减小 mss 再重试的好时机。前面提到过,减半 mss 可以获得更高的成功率,执行 mss减半 mss 的逻辑如下,相关 RFC 规定我以注释写在代码间:
/*
* 启动 MTU Probe 的时机:retries1 到期后
* 根据 RFC1122 4.2.3.5 TCP Connection Failures,同一个 TCP 段多次重传均失败意味着底层故障,
* retries1 规定了一个短期故障处理的重传次数阈值,到期后应检测 IP 层故障,此时需进行 MTU Probe。
* RFC1122 同样规定了 retries1 的取值约束:The value of R1 SHOULD correspond to at least 3
* retransmissions, at the current RTO.
*/
static void tcp_mtu_probing(struct inet_connection_sock *icsk, struct sock *sk)
{
mss = tcp_mtu_to_mss(sk, icsk->icsk_mtup.search_low) >> 1;
icsk->icsk_mtup.search_low = tcp_mss_to_mtu(sk, mss);
tp->mss_cache = mss;
}
/*in tcp_write_timeout*/
if (retransmits_timed_out(sk, net->ipv4.sysctl_tcp_retries1, 0)) {
/* Black hole detection */
tcp_mtu_probing(icsk, sk);
dst_negative_advice(sk);
}
还有一种基于 PMTU(MTU 发现,详见 RFC1191) 精确的 mss 降低,即收到 ICMP Frag-Needed 之后,根据 ICMP 报文的指示,将 mss 降低到指示值,简单的事不多说。
前面也提到过,减半 mss 虽然提高了成功率,但过度降低 mss 也降低了传输效率,相反的一面则是 mss 减 1 再重试,但成功率最低,TCP 选择了前者。为了弥补效率损耗,TCP 执行二分探测,下面是试图增加 mss 的二分探测逻辑:
static void tcp_mtup_probe_failed(struct sock *sk)
{
icsk->icsk_mtup.search_high = icsk->icsk_mtup.probe_size - 1;
icsk->icsk_mtup.probe_size = 0;
}
static void tcp_mtup_probe_success(struct sock *sk)
{
icsk->icsk_mtup.search_low = icsk->icsk_mtup.probe_size;
icsk->icsk_mtup.probe_size = 0;
}
/*in tcp_write_xmit->tcp_mtu_probe:*/
icsk->icsk_mtup.probe_size = (icsk->icsk_mtup.search_high +
icsk->icsk_mtup.search_low) >> 1;
/*send skb with length = probe_size*/
/*in tcp_fastretrans_alert: 异常流,状态切换/重传*/
if (icsk->icsk_ca_state < TCP_CA_CWR &&
icsk->icsk_mtup.probe_size &&
tp->snd_una == tp->mtu_probe.probe_seq_start)
tcp_mtup_probe_failed(sk);
/*in tcp_clean_rtx_queue:正常流:清理重传队列*/
if ((flag & FLAG_ACKED) &&
unlikely(icsk->icsk_mtup.probe_size &&
!after(tp->mtu_probe.probe_seq_end, tp->snd_una)))
tcp_mtup_probe_success(sk);
TCP 有这套机制,还需要 IP 分片吗?UDP-based 协议若需要可以照着做,也可以玩得更花。
虽然 TCP 流量的 IP 头缺省都携带 DF 标识,但并不绝对。iproute2 提供了路由配置接口,禁止 MTU 发现,以下是 ip-route 的 manual 相关配置字段说明:
mtu lock MTU
the MTU along the path to the destination. If the modifier lock is not used, the MTU may be updated by the kernel due to Path MTU Discovery. If the modifier lock is used, no path MTU discovery will be tried, all packets will be sent without the DF bit in IPv4 case or fragmented to MTU for IPv6.
路由配置命令行后面加上 mtu lock 1234 相当于禁用了 MTU 发现,这就为过此路的 IP 数据报恢复 RFC791 分片机制了。
最后,值得一提,关于 IP 分片,MTU 发现等话题,依旧是历史问题。IP 分片旨在提供一种机制保障报文尽力而为的可达性,这显然是在仅存在 IP 协议被标准的 1981 年的通用机制,要知道那时还没有 MTU 发现(迟至 1988 年始出来),如果不支持 IP 分片,就只能依靠 IMCP(RFC792) 控制报文回送错误信息并处理,这显然是复杂的做法,违背了尽力而为的假设。
另一方面,IP 被标准化(RFC791)的 1981 年,理论上它可以承载 255 种传输层协议,但事实上几乎只有 TCP(RFC793) 和 UDP(RFC768),如本文最前面所述,TCP 依靠超时可靠性判定可以自己发现问题,UDP 又不在乎可靠性问题,这种尴尬境地下,IP 分片的效用形同虚设。
加之如今的底层传输网络几乎趋向同质化,在技术变革伊始,合久必分,随着技术的成熟,分久必合,这意味着 TCP/IP 沙漏的底座正在收窄,MTU 差异度越发不明显,我们可从 IPv6 规范中看出这种强硬态度的升级。RFC 8200 如是说:
IPv6 requires that every link in the Internet have an MTU of 1280 octets or greater. This is known as the IPv6 minimum link MTU. On any link that cannot convey a 1280-octet packet in one piece, link-specific fragmentation and reassembly must be provided at a layer below IPv6.
但在早期的 IPv6 规范 RFC1883 中可不是这么说的,它只是简单继承了 IPv4 的 minMTU要求:
IPv6 requires that every link in the internet have an MTU of 576 octets or greater. …
这就是 IPv6 取消对 IP 分片直接支持的背景,不过请注意,IPv6 只是路由器等转发节点不分片,始发站无要求,但这就是另一个话题了。
浙江温州皮鞋湿,下雨进水不会胖。