Linux 内核在tcp_sock
这个数据结构中保存与 MSS
有关的信息。
struct tcp_sock{ // code omitted struct tcp_options_received rx_opt; { // code omitted... u16 user_mss; /* 用户通过TCP_MAXSEG设置的MSS */ u16 mss_clamp; /* 在连接建立阶段协商出来的 min(user_mss, SYN‘s mss) 即对端通告的MSS */ } // code omitted... u32 mss_cache; // 有效MSS, = rx_opt.mss_clamp - TCP附件选项(如 时间戳12字节) u16 advmss; }
各个字段的意义如下:
rx_opt.user_mss
:用户设置的本端 MSS。用户可以通过 TCP_MAXSEG
这个 socket 选项对这个字段进行设置,这个字段在 rx_opt
中,说明用户设置该字段起到的作用就如同收到了对端通告的 MSS 值。rx_opt.mss_clamp
: 连接建立阶段本端计算出的 MSS。它取user_mss
和 对端 SYN(SYNACK) 报文通告的 MSS 值中的较小值,如果用户没有设置 user_mss,则就为对端报文中的 MSS 值。clamp 中文翻译为”夹钳”,我们可以理解为生效 MSS的最大值。mss_cache
: 生效 MSS。它是这几个字段中最重要的,表示本端 TCP 发包实际的分段大小依据,它的值在连接过程中可能发生变化。advmss
:本端向对端通告的包含option的 MSS 值。举个例子,当网卡 MTU 为 1500 字节时,通信双方通告的 MSS 都应该为 1460 字节,但如果双方都开启了 TCP timestamp 选项(会占用 12 字节),则advmss
的值会是 1448mss_cache
在 Linux 内核中表示 TCP 连接当前生效的 MSS,它是如此重要,我们来看下它的值是如何确定的。
首先,无论是主动端还是被动端,在创建tcp_sock
时,就会对mss_cache
进行初始化为 TCP_MSS_DEFAULT(536)
void tcp_init_sock(struct sock *sk) { // code omitted tp->mss_cache = TCP_MSS_DEFAULT; }
在这之后,通过tcp_sync_mss()
方法,内核可以对mss_cache
进行修改。
unsigned int tcp_sync_mss(struct sock *sk, u32 pmtu) { struct tcp_sock *tp = tcp_sk(sk); struct inet_connection_sock *icsk = inet_csk(sk); int mss_now; /* 用 pmtu 计算 mss_now */ mss_now = tcp_mtu_to_mss(sk, pmtu); | |-- __tcp_mtu_to_mss(sk, pmtu) - (tcp_sk(sk)->tcp_header_len - sizeof(struct tcphdr)); /* 其他条件限制 mss_now */ // code omitted tp->mss_cache = mss_now; // code omitted }
在不考虑其他条件(如对端最大接收窗口大小)时,mss_cache
的值由tcp_mtu_to_mss()
计算而来,进而由__tcp_mtu_to_mss()
减去 TCP 首部的 option 长度而来
而__tcp_mtu_to_mss()
呢?它也就是传入的 pmtu 减去IP首部长度(含option),在减去TCP首部长度(不含option)。噢,对了,它还不能超过rx_opt.mss_clamp
static inline int __tcp_mtu_to_mss(struct sock *sk, int pmtu) { const struct tcp_sock *tp = tcp_sk(sk); const struct inet_connection_sock *icsk = inet_csk(sk); int mss_now; /* Calculate base mss without TCP options: It is MMS_S - sizeof(tcphdr) of rfc1122 */ mss_now = pmtu - icsk->icsk_af_ops->net_header_len - sizeof(struct tcphdr); // code omitted /* Clamp it (mss_clamp does not include tcp options) */ if (mss_now > tp->rx_opt.mss_clamp) mss_now = tp->rx_opt.mss_clamp; // code omitted return mss_now; }
所以,如果tcp_sync_mss()
传入的pmtu
等于 1500,IP 不包含任何 option,则__tcp_mtu_to_mss
会得到1500-20-20=1460
,如果 TCP 使能了 timestamp,则tcp_mtu_to_mss()
会返回1460-(32-20)=1448
那么,tcp_mtu_to_mss()
在什么时候被调用呢?
mss_cache
tcp_connect_init
|
|-- tcp_sync_mss(sk, dst_mtu(dst))
mss_cache
tcp_v4_syn_recv_sock:
|
|-- tcp_sync_mss(newsk, dst_mtu(dst));
mss_cache
void tcp_v4_err(struct sk_buff *icmp_skb, u32 info)
|
|-- dst = inet_csk_update_pmtu(sk, mtu);
|-- tcp_sync_mss(sk, mtu);
有了前面的铺垫,再来看所谓的 MSS “协商过程”就容易多了。
这里我用两台虚拟机作为 TCP 连接的双方,虚拟机网卡的默认 mtu 是 1500,而我将主动端虚拟机网卡 mtu 设置为 1399. TCP 默认开启了 timestamp 选项。
最终主动端(vm-2)和被动端(vm-1)的mss_cache
都被设置成了 1347
报文类型:
23 #define ICMP_DEST_UNREACH 3 /* Destination Unreachable */ 43 #define ICMP_FRAG_NEEDED 4 /* Fragmentation Needed/DF set */
icmp_rcv(struct sk_buff *skb),根据ICMP类型处理skb:
icmp_pointers[icmph->type].handler(skb);
icmp_pointers的定义显示,ICMP_DEST_UNRAECH的handler为icmp_unreach,后者获取出ICMP头部的mtu后,投递给icmp_socket_deliver(skb, mtu)。icmp_socket_deliver
最终将skb和mtu值投递给ipprot->err_handler(skb, info);
tcp_v4_err(skb, info),
调用tcp_v4_mtu_reduced(struct sock *sk)
更新路由的MTU,inet_csk_update_pmtu(struct sock *sk, u32 mtu)
找到路由inet_csk_rebuild_route(sk, &inet->cork.fl);
更新路由MTU dst->ops->update_pmtu(dst, sk, NULL, mtu);
tcp_v4_err
,如果MTU变小,且可分片,则会tcp_sync_mss(struct sock *sk, u32 pmtu)。关于TCP的MSS与MTU相关的内容很多:
1274 /* This function synchronize snd mss to current pmtu/exthdr set. 1275 1276 tp->rx_opt.user_mss is mss set by user by TCP_MAXSEG. It does NOT counts 1277 for TCP options, but includes only bare TCP header. 1278 1279 tp->rx_opt.mss_clamp is mss negotiated at connection setup. 1280 It is minimum of user_mss and mss received with SYN. 1281 It also does not include TCP options. 1282 1283 inet_csk(sk)->icsk_pmtu_cookie is last pmtu, seen by this function. 1284 1285 tp->mss_cache is current effective sending mss, including 1286 all tcp options except for SACKs. It is evaluated, 1287 taking into account current pmtu, but never exceeds 1288 tp->rx_opt.mss_clamp. 1289 1290 NOTE1. rfc1122 clearly states that advertised MSS 1291 DOES NOT include either tcp or ip options. 1292 1293 NOTE2. inet_csk(sk)->icsk_pmtu_cookie and tp->mss_cache 1294 are READ ONLY outside this function. --ANK (980731) 1295 */
__udp4_lib_err(struct sk_buff skb, u32 info, struct udp_table udptable)
ipv4_sk_update_pmtu(struct sk_buff skb, struct sock sk, u32 mtu),也是基于路由更新MTU。
__ip_rt_update_pmtu
ping报文的处理也是类似,见ping_err。
经阅读代码,发现控制DF标志的地方主要有两处:
以关键字pmtudisc和local_df做全局字符串搜索,发现对pmtudisc和local_df有赋值的地方很少。如下各小节分析。
90 /* IP_MTU_DISCOVER values */ 91 #define IP_PMTUDISC_DONT 0 /* Never send DF frames */ 92 #define IP_PMTUDISC_WANT 1 /* Use per route hints */ 93 #define IP_PMTUDISC_DO 2 /* Always DF */ 94 #define IP_PMTUDISC_PROBE 3 /* Ignore dst pmtu */
源代码:ip_queue_xmit和__ip_make_skb,后者用于UDP。
382 if (ip_dont_fragment(sk, &rt->dst) && !skb->local_df) 383 iph->frag_off = htons(IP_DF); 384 else 385 iph->frag_off = 0; 248 int ip_dont_fragment(struct sock *sk, struct dst_entry *dst) 249 { 250 return inet_sk(sk)->pmtudisc == IP_PMTUDISC_DO || 251 (inet_sk(sk)->pmtudisc == IP_PMTUDISC_WANT && 252 !(dst_metric_locked(dst, RTAX_MTU))); 253 }
inet_create,依赖ipv4_config
配置。该配置体现在/proc/sys/net/ipv4/ip_no_pmtu_disc
,目前Ubuntu 14.04上默认ip_no_pmtu_disc=0
,即由每个路由确定。
374 if (ipv4_config.no_pmtu_disc) 375 inet->pmtudisc = IP_PMTUDISC_DONT; 376 else 377 inet->pmtudisc = IP_PMTUDISC_WANT;
可用sysctl
命令配置,或通过/etc/sysctl.conf
配置文件配置ip_no_pmtu_disc
值,最终体现在/proc/sys/net/ipv4/ip_no_pmtu_disc
。源码链接。
默认创建ICMP sk时,不发送DF报文。icmp_sk_init(struct net *net)。
PS:ICMP报文不超过576字节的限制在icmp_send接口中有体现。
原文:https://www.cnblogs.com/dream397/p/14610029.html