Linux内核默认情况下未开启TCP的MTU探测功能。

$ cat /proc/sys/net/ipv4/tcp_mtu_probing
0

当TCP客户端发起连接建立请求时,在函数tcp_connect_init中调用TCP的MTU探测初始化函数tcp_mtup_init。如上所述默认情况下enabled为零,使用MSS最大限制值mss_clamp加上TCP头部长度和网络层头部长度作为MTU探测的上限值,下限值由函数tcp_mss_to_mtu通过基础MSS值计算得到。

void tcp_mtup_init(struct sock *sk)
{struct tcp_sock *tp = tcp_sk(sk);struct inet_connection_sock *icsk = inet_csk(sk);icsk->icsk_mtup.enabled = net->ipv4.sysctl_tcp_mtu_probing > 1;icsk->icsk_mtup.search_high = tp->rx_opt.mss_clamp + sizeof(struct tcphdr) + icsk->icsk_af_ops->net_header_len;icsk->icsk_mtup.search_low = tcp_mss_to_mtu(sk, net->ipv4.sysctl_tcp_base_mss);icsk->icsk_mtup.probe_size = 0;if (icsk->icsk_mtup.enabled)icsk->icsk_mtup.probe_timestamp = tcp_jiffies32;
}

TCP的MTU探测的基础MSS默认初始化为1024,见宏定义TCP_BASE_MSS,可通过PROC文件tcp_base_mss修改其值。

$ cat /proc/sys/net/ipv4/tcp_base_mss
1024
$ cat /proc/sys/net/ipv4/tcp_probe_threshold
8
$ cat /proc/sys/net/ipv4/tcp_probe_interval
600

内核定义值如下:

#define TCP_BASE_MSS        1024
#define TCP_PROBE_INTERVAL  600
#define TCP_PROBE_THRESHOLD 8static int __net_init tcp_sk_init(struct net *net)
{net->ipv4.sysctl_tcp_base_mss = TCP_BASE_MSS;net->ipv4.sysctl_tcp_probe_threshold = TCP_PROBE_THRESHOLD;net->ipv4.sysctl_tcp_probe_interval = TCP_PROBE_INTERVAL;
}

MTU到MSS推算

基础函数__tcp_mtu_to_mss如下。首先,路径MTU减去网路层头部和TCP标准头部的长度得到一个MSS的长度。其次,对于IPv6而言,需要在减去一个分片头部的长度;再次,MSS值不能够超过协商的限定值mss_clamp(其不包括TCP选项长度);之后减去扩展头部长度,例如IP选项的长度;最终得到的MSS值不能小于48,否则使用48,即全部TCP选项的长度40加上8字节的数据。需要注意的是__tcp_mtu_to_mss函数在计算过程中并没有考虑TCP选项的长度。

static inline int __tcp_mtu_to_mss(struct sock *sk, int pmtu)
{   /* 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);/* IPv6 adds a frag_hdr in case RTAX_FEATURE_ALLFRAG is set */if (icsk->icsk_af_ops->net_frag_header_len) {const struct dst_entry *dst = __sk_dst_get(sk);if (dst && dst_allfrag(dst))mss_now -= icsk->icsk_af_ops->net_frag_header_len;}if (mss_now > tp->rx_opt.mss_clamp)mss_now = tp->rx_opt.mss_clamp;mss_now -= icsk->icsk_ext_hdr_len;if (mss_now < 48)mss_now = 48;return mss_now;
}

函数tcp_mtu_to_mss为对以上函数的封装,将函数__tcp_mtu_to_mss的返回结果值减去了选项的长度,即其考虑了TCP大部分选项的长度,但是并没有将SACK的选项考虑在内。

int tcp_mtu_to_mss(struct sock *sk, int pmtu)
{                     /* Subtract TCP options size, not including SACKs */return __tcp_mtu_to_mss(sk, pmtu) - (tcp_sk(sk)->tcp_header_len - sizeof(struct tcphdr));
} 

另外一个与MTU到MSS转换相关的函数为tcp_bound_to_half_wnd。如果当前最大的接收窗口大于TCP_MSS_DEFAULT(536),将发送MSS限制在最大接收窗口的一半内;否则,对于小于536的小窗口,发送MSS的值不应超出整个窗口的值。

static inline int tcp_bound_to_half_wnd(struct tcp_sock *tp, int pktsize)
{  /* When peer uses tiny windows, there is no use in packetizing are enough packets in the pipe for fast recovery.* On the other hand, for extremely large MSS devices, handling smaller than MSS windows in this way does make sense. */if (tp->max_window > TCP_MSS_DEFAULT)cutoff = (tp->max_window >> 1);elsecutoff = tp->max_window;if (cutoff && pktsize > cutoff)return max_t(int, cutoff, 68U - tp->tcp_header_len);elsereturn pktsize;
}

最终由函数tcp_sync_mss负责更新当前TCP发送使用的MSS值mss_cache,其包括除SACK选项之外的所有其它选项的长度,参见函数tcp_mtu_to_mss。并且使用tcp_bound_to_half_wnd控制发送MSS与对端接收窗口的比例关系,得到mss_cache的值,同时也更新当前连接的路径PMTU值icsk_pmtu_cookie。需要注意的是,如果启用了TCP的MTU探测功能,最后的发送mss_cache的值取当前值和以search_low计算得到的mss值两者之间的较小值。

unsigned int tcp_sync_mss(struct sock *sk, u32 pmtu)
{if (icsk->icsk_mtup.search_high > pmtu)icsk->icsk_mtup.search_high = pmtu;mss_now = tcp_mtu_to_mss(sk, pmtu);mss_now = tcp_bound_to_half_wnd(tp, mss_now);icsk->icsk_pmtu_cookie = pmtu;if (icsk->icsk_mtup.enabled)mss_now = min(mss_now, tcp_mtu_to_mss(sk, icsk->icsk_mtup.search_low));tp->mss_cache = mss_now;return mss_now;
}

当前发送MSS的计算有函数tcp_current_mss实现,其更进一步的考虑了TCP的SACK选项数据长度,最后得到TCP发送路径使用的MSS值。

unsigned int tcp_current_mss(struct sock *sk)
{const struct dst_entry *dst = __sk_dst_get(sk);mss_now = tp->mss_cache;if (dst) {u32 mtu = dst_mtu(dst);if (mtu != inet_csk(sk)->icsk_pmtu_cookie)mss_now = tcp_sync_mss(sk, mtu);}header_len = tcp_established_options(sk, NULL, &opts, &md5) + sizeof(struct tcphdr);if (header_len != tp->tcp_header_len) {int delta = (int) header_len - tp->tcp_header_len;mss_now -= delta;}return mss_now;
}

MTU探测

在TCP发送路径中,如果由于TCP_CORK选项累计了数据包,或者合并了小微数据包,在数据发送函数tcp_write_xmit中,内核调用tcp_mtu_probe发送MTU探测报文。首要条件是,没有正在运行的探测、拥塞状态在初始态、拥塞窗口大于11,并且没有SACK,以上条件只要有一个不满足,就不能进行MTU探测。

static int tcp_mtu_probe(struct sock *sk)
{if (likely(!icsk->icsk_mtup.enabled || icsk->icsk_mtup.probe_size || inet_csk(sk)->icsk_ca_state != TCP_CA_Open ||tp->snd_cwnd < 11 || tp->rx_opt.num_sacks || tp->rx_opt.dsack))return -1;

选取的MTU探测值probe_size等于下限值search_low加上其与上限值search_high之差的1/2,即search_low+1/2*(search_high-search_low)转换得到的MSS值,作为新的MTU探测值。但是如果新选取的值大于search_high对应的MSS值,或者上限值与下限值小于设定的探测阈值tcp_probe_threshold(8),返回失败。

    mss_now = tcp_current_mss(sk);probe_size = tcp_mtu_to_mss(sk, (icsk->icsk_mtup.search_high + icsk->icsk_mtup.search_low) >> 1);size_needed = probe_size + (tp->reordering + 1) * tp->mss_cache;interval = icsk->icsk_mtup.search_high - icsk->icsk_mtup.search_low;if (probe_size > tcp_mtu_to_mss(sk, icsk->icsk_mtup.search_high) || interval < net->ipv4.sysctl_tcp_probe_threshold) {/* Check whether enough time has elaplased for another round of probing. */tcp_mtu_check_reprobe(sk);return -1;}

在判断可发送之后,内核将开始组建数据长度为probe_size值的探测报文,新分配一个nskb,将发送队列sk_write_queue前端的数据包拷贝probe_size的数据到新的nskb中,释放拷贝过的数据包。

    nskb = sk_stream_alloc_skb(sk, probe_size, GFP_ATOMIC, false);skb = tcp_send_head(sk);TCP_SKB_CB(nskb)->seq = TCP_SKB_CB(skb)->seq;TCP_SKB_CB(nskb)->end_seq = TCP_SKB_CB(skb)->seq + probe_size;TCP_SKB_CB(nskb)->tcp_flags = TCPHDR_ACK;tcp_insert_write_queue_before(nskb, skb, sk);tcp_highest_sack_replace(sk, skb, nskb);tcp_for_write_queue_from_safe(skb, next, sk) {}tcp_init_tso_segs(nskb, nskb->len);

最后,调用TCP传输函数发送此数据包。

    /* We're ready to send.  If this fails, the probe will be resegmented into mss-sized pieces by tcp_write_xmit(). */if (!tcp_transmit_skb(sk, nskb, 1, GFP_ATOMIC)) {/* Decrement cwnd here because we are sending effectively two packets. */tp->snd_cwnd--;tcp_event_new_data_sent(sk, nskb);icsk->icsk_mtup.probe_size = tcp_mss_to_mtu(sk, nskb->len);tp->mtu_probe.probe_seq_start = TCP_SKB_CB(nskb)->seq;tp->mtu_probe.probe_seq_end = TCP_SKB_CB(nskb)->end_seq;return 1;}return -1;
}

以上tcp_mtu_probe函数中,如果遇到新的探测值probe_size大于search_high对应的MSS值,或者上限值与下限值小于设定的探测阈值tcp_probe_threshold(8),在返回错误之前,内核调用tcp_mtu_check_reprobe重新安排一次探测。前提是本次探测与上一次探测的时间间隔不小于设定的间隔值tcp_probe_interval(600),即10分钟。

static inline void tcp_mtu_check_reprobe(struct sock *sk)
{interval = net->ipv4.sysctl_tcp_probe_interval;delta = tcp_jiffies32 - icsk->icsk_mtup.probe_timestamp;if (unlikely(delta >= interval * HZ)) {int mss = tcp_current_mss(sk);/* Update current search range */icsk->icsk_mtup.probe_size = 0;icsk->icsk_mtup.search_high = tp->rx_opt.mss_clamp + sizeof(struct tcphdr) + icsk->icsk_af_ops->net_header_len;icsk->icsk_mtup.search_low = tcp_mss_to_mtu(sk, mss);/* Update probe time stamp */icsk->icsk_mtup.probe_timestamp = tcp_jiffies32;}
}

在tcp_ack函数中,如果接收到的时旧的ACK或者重复的SACK报文等非正常ACK报文,将调用tcp_fastretrans_alert处理。其中拥塞状态为非TCP_CA_Recovery,即处于TCP_CA_Loss或者其它,并且未确认的序号等于探测报文的开始序号,内核判断探测失败。

static void tcp_fastretrans_alert(struct sock *sk, const u32 prior_snd_una, bool is_dupack, int *ack_flag, int *rexmit)
{switch (icsk->icsk_ca_state) {case TCP_CA_Recovery:break;case TCP_CA_Loss:default:/* MTU probe failure: don't reduce cwnd */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);/* Restores the reduction we did in tcp_mtup_probe() */tp->snd_cwnd++;tcp_simple_retransmit(sk);return;}}
}

探测失败处理函数tcp_mtup_probe_failed如下,如果探测失败的话,表明探测的MTU值过大,将探测值减去1赋值给探测上限值search_high。

static void tcp_mtup_probe_failed(struct sock *sk)
{struct inet_connection_sock *icsk = inet_csk(sk);icsk->icsk_mtup.search_high = icsk->icsk_mtup.probe_size - 1;icsk->icsk_mtup.probe_size = 0;NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPMTUPFAIL);
}

TCP接收到的ACK报文处理tcp_ack函数,检查重传队列tcp_clean_rtx_queue。如果发送了MTU探测报文(probe_size有值),并且探测报文的结束序号已被对端确认,意味值探测成功,由函数tcp_mtup_probe_success进行处理。

static int tcp_clean_rtx_queue(struct sock *sk, u32 prior_fack, u32 prior_snd_una, struct tcp_sacktag_state *sack)
{if (flag & FLAG_ACKED) {flag |= FLAG_SET_XMIT_TIMER;  /* set TLP or RTO timer */if (unlikely(icsk->icsk_mtup.probe_size && !after(tp->mtu_probe.probe_seq_end, tp->snd_una))) {tcp_mtup_probe_success(sk);}}
}

函数tcp_mtup_probe_success的调用表明探测成功,意味着连接的MTU值已增加,随即将探测值probe_size赋予MTU探测的下限值,复位probe_size。由函数tcp_sync_mss同步TCP的MSS值。

static void tcp_mtup_probe_success(struct sock *sk)
{tp->prior_ssthresh = tcp_current_ssthresh(sk);tp->snd_cwnd = tp->snd_cwnd * tcp_mss_to_mtu(sk, tp->mss_cache) / icsk->icsk_mtup.probe_size;tp->snd_cwnd_cnt = 0;tp->snd_cwnd_stamp = tcp_jiffies32;tp->snd_ssthresh = tcp_current_ssthresh(sk);icsk->icsk_mtup.search_low = icsk->icsk_mtup.probe_size;icsk->icsk_mtup.probe_size = 0;tcp_sync_mss(sk, icsk->icsk_pmtu_cookie);
}

路径黑洞探测

如果TCP的重传次数超过了tcp_retries1限定的值(默认为3),表明网络可能存在一定的问题,但是此时内核并不会结束此连接(直到超过tcp_retries2的值),此时TCP重传处理函数,将发起MTU探测。

static int tcp_write_timeout(struct sock *sk)
{if ((1 << sk->sk_state) & (TCPF_SYN_SENT | TCPF_SYN_RECV)) {} else {if (retransmits_timed_out(sk, net->ipv4.sysctl_tcp_retries1, 0)) {/* Black hole detection */tcp_mtu_probing(icsk, sk);dst_negative_advice(sk);}}
}

如下函数tcp_mtu_probing,如果tcp_mtu_probing等于零表示为开启,直接返回。如果未使能,此处使能并且更新探测开始时间戳。否则,内核在此时将降低探测所使用的MTU值,首先将探测的下限值search_low减低一半,但是不能低于规定的最小值tcp_base_mss,还要至少大于TCP头部最大长度加上8个TCP数据长度的结果减去TCP实际头部长度的值。

static void tcp_mtu_probing(struct inet_connection_sock *icsk, struct sock *sk)
{/* Black hole detection */if (!net->ipv4.sysctl_tcp_mtu_probing)return;if (!icsk->icsk_mtup.enabled) {icsk->icsk_mtup.enabled = 1;icsk->icsk_mtup.probe_timestamp = tcp_jiffies32;} else {mss = tcp_mtu_to_mss(sk, icsk->icsk_mtup.search_low) >> 1;mss = min(net->ipv4.sysctl_tcp_base_mss, mss);mss = max(mss, 68 - tcp_sk(sk)->tcp_header_len);icsk->icsk_mtup.search_low = tcp_mss_to_mtu(sk, mss);}tcp_sync_mss(sk, icsk->icsk_pmtu_cookie);
}

内核版本 4.15

TCP的MTU探测功能相关推荐

  1. 从入门到入土:基于Python采用TCP协议实现通信功能的程序

    此博客仅用于记录个人学习进度,学识浅薄,若有错误观点欢迎评论区指出.欢迎各位前来交流.(部分材料来源网络,若有侵权,立即删除) 本人博客所有文章纯属学习之用,不涉及商业利益.不合适引用,自当删除! 若 ...

  2. 从入门到入土:基于C语言采用TCP协议实现通信功能的程序(仅有代码)

    此博客仅用于记录个人学习进度,学识浅薄,若有错误观点欢迎评论区指出.欢迎各位前来交流.(部分材料来源网络,若有侵权,立即删除) 本人博客所有文章纯属学习之用,不涉及商业利益.不合适引用,自当删除! 若 ...

  3. 【操作教程】如何手动配置EasyNVR安防视频云服务平台的Onvif探测功能?

    EasyNVR安防视频云服务平台可支持设备通过RTSP/Onvif协议接入平台,用户可以使用Onvif探测功能实现摄像头的设备IP探测,成功后返回需要的视频流地址,同时还能实现对摄像头的PTZ云台控制 ...

  4. tcp协议的主要功能是什么_计算机网络 | 一文搞懂什么是TCP/IP协议

    什么是TCP/IP协议? 计算机与网络设备之间如果要相互通信,双方就必须基于相同的方法.比如如何探测到通信目标.由哪一边先发起通信,使用哪种语言进行通信,怎样结束通信等规则都需要事先确定.不同的硬件, ...

  5. linux自动探测mtu,Arch Linux 启用 MTU 探测

    最近在家里经常遇到 ssh 超时的问题,一开始也没太当回事,感觉是网络不稳定导致的,但是后来慢慢的发现这种超时问题只会出现在跟 ssh 相关的程序中,例如 git.ssh.这成功的引起了我的注意,于是 ...

  6. tcp协议的主要功能是什么_前端要知道的网络知识一:TCP/IP 协议到底在讲什么...

    你之所以不知道那套书在讲什么,是因为你还没有认识到网络协议有什么用,怎么用,以什么形式在使用,网络协议的概念很简单,就几句话,你只知道网络协议的概念,只知道很多大神都推荐这套书,都强调网络协议的重要性 ...

  7. 计算机上网络接口层,2016计算机专业知识:TCP/IP 各层功能

    [导读] 为了帮助广大考生更好的备考,中公事业单位考试网提供2016年计算机专业知识学习,为考生定制计算机基础知识复习计划. 1.网络接口层: 这是TCP/IP模型的最低层,包括能使用TCP/IP与物 ...

  8. python进程socket通信_Python Socket TCP双端聊天功能实现过程详解

    SOCKET编程 socket(套接字):是一个网络通信的端点,能实现不同主机的进程通信, -通过IP+端口定位对方并发送消息的通信机制 分为UDP和TCP 客户端Client: 发起访问的一-方 服 ...

  9. 基于TCP实现双向通信对话功能

    TCP是一个全双工协议,数据通信允许数据同时在两个方向上传输,因此全双工是两个单工通信方式的结合,它要求发送设备和接收设备都有独立的接收和发送能力.我们来做一个简单的实现 Server端 public ...

  10. python 监听tcp端口_创建TCP监听_创建TCP监听_功能示例_Python SDK示例_SDK 参考_开发指南_负载均衡 - 阿里云...

    # encoding=utf-8 import json import sys # 调用AcsClient参数进行身份验证 from aliyunsdkcore.client import AcsCl ...

最新文章

  1. Microsoft Security Essentials 4.1.522.0 RTM
  2. hdu 2830(矩形dp)
  3. 创建springmvc配置
  4. 纽约大街上的免费WiFi,终于铺起来了
  5. 【Windows】DACL SACL
  6. 5.4php-fpm配置,CentOS5.4下配置Nginx0.8.33+PHP5.3+PHP-fpm+MySQL5.5
  7. OscillatorNode
  8. 开环控制的两轮差速驱动小车_汽车的差速器、差速锁有什么不同?很多车主容易把它俩搞混...
  9. ELK学习笔记之Elasticsearch启动常见错误
  10. leetcode963. Minimum Area Rectangle II
  11. 人人开源vue无法启动
  12. DS18B20驱动详解(蓝桥杯单片机)
  13. 综述类论文怎么写引言和结语?
  14. 2018经济寒冬,2019你凭什么活下来?
  15. 管理信息系统需求调研分析指南
  16. GBase XDM集群服务配置
  17. java操作excel方法_Java实现操作excel表格的方法
  18. Lenovo E570 Windows 10 换Windows7
  19. 2019年云架构和云计算趋势如何?|中机智库干货
  20. 黑苹果开荒记: 华硕迷你主机 PN62 – 最后的 Hackintosh Mini

热门文章

  1. 虚拟机服务器一直自动关机,诡异的Vmware虚拟机自动关机故障
  2. 【官档整理】原生 MSDN 纯净版 WIN PE 创建 下载方法
  3. 把握本质规律——《数学之美》作者吴军接受《程序员》采访
  4. c语言入门之项目2.3——利息计算器
  5. Excel妙用-公式结果我都要
  6. 一份优秀的前端开发工程师简历是怎样的?
  7. STM32-面试题及答案
  8. OllyDbg 常用命令 【Pray收集整理】
  9. Windows10桌面优化 | 如何修改图标大小 | 如何把win10快捷方式小箭头去掉
  10. 圣剑传说 玛娜传奇(Legend of Mana)(LOM)主原料取得方法