通过 Socket 系统接口,链接到一个TCP服务器,那么在链接成功之后会被配置一个从本地端到目的端最佳的TCP_MSS大小。
我们通过这个特点,即可轻松的实现,链路MTU大小发现功能,在不依赖ROOT管理员权限的情况下,推算出一个较为OK的MTU大小。
一般来说,TCP在服务器SYN|ACK的时候,指定的TCP选项设置的最大分段大小,通常是需要-12字节的,这是因为在PUSH/ACK分段的时候头部有扩展12字节的TCP选项,两个NOP填充,一个TIMESPAN时间戳。
所以:
当如上图,TCP选项协商设置最大分段大小为;1332,那么意味着TCP_MSS为1320,扣除TCP PUSH/在协议头上额外设定的12字节,一个TCP选项占四个字节,三个则12字节。
所以:
我们只要需要通过 socket 接口,获取 TCP_MAXSEG 的值,加12,那么就是TCP的大小,IPV4头20字节,那么意味着;
MTU=1320+12+20=1352,则:从本机到目的主机最佳MTU大小为1352,理论更大会导致传输效率的降低,因为这意味着沿途某一个路由需要进行分片传输。
注意:
TCP是通过 “Path MTU Discovery(PMTUD)” 路径MTU发现机制来探测MTU大小的,但是这个在非TCP模式下不可用。
其原理大致为:
1、 发送端发送一个较大尺寸的数据包,并将 "Don't Fragment"(DF)标志位置为1,这样数据包就不会在传输过程中被分片。
2、如果数据包超出了某个路由器的MTU,该路由器将会丢弃数据包,并向发送端发送一个 "ICMP Fragmentation Needed" 消息,其中包含其MTU值。
3、发送端收到 "ICMP Fragmentation Needed" 消息后,会调整其传输数据包的大小,使其不超过最小的发现的MTU值。然后继续发送新的数据包进行测试,直到找到两个节点之间的最佳MTU值。
PMTUD机制是操作系统内核协议栈已经实现的功能,当我们调用 connect 函数的时候,内核那边就会做这样的行为,当然如果已有缓存就不会触发PMTUD机制,这个可以抓包工具来分析。
函数:
GetTcpMss 获取TCP_MSS大小
SetTcpMss 设置TCP_MSS大小
/* TCP MSS values – what’s changed?
* https://blog.apnic.net/2019/07/31/tcp-mss-values-whats-changed/
*/
int Socket::GetTcpMss(int fd) noexcept {
if (fd == -1) {
return -1;
}
int mss = 0;
socklen_t mss_len = sizeof(mss);
if (::getsockopt(fd, IPPROTO_TCP, TCP_MAXSEG, (char*)&mss, &mss_len) < 0) {
return -1;
}
return mss;
}
bool Socket::SetTcpMss(int fd, int mss) noexcept {
static constexpr int TCP_MIN_MSS = 536;
static constexpr int TCP_MAX_MSS = 1460;
if (fd == -1) {
return false;
}
if (mss < TCP_MIN_MSS) {
mss = TCP_MIN_MSS;
}
elif(mss > TCP_MAX_MSS) {
mss = TCP_MAX_MSS;
}
int err = ::setsockopt(fd, IPPROTO_TCP, TCP_MAXSEG, (char*)&mss, sizeof(mss));
return err == 0;
}