注:内核版本4.14.119
一、问题
1. 超时重传的timeout设置和次数
1.1 RTO设置
超时重传RTO的值和rtt有关,即通过rtt可以计算出RTO。RTO的取值范围是[TCP_RTO_MIN(HZ/5), TCP_RTO_MAX(120HZ)],RTO的具体计算此处不展开。
1.2重传次数
1.2.1 三次握手的重传次数
在三次握手时,重传次数时可以确定的。
net.ipv4.tcp_syn_retries = 6 //for client, 用于在syn发送阶段
net.ipv4.tcp_synack_retries = 5 //for server, 用于在yn-ack发送阶段
1.2.2 在数据通信过程中,重传次数不是一个确定的值,有若干因素同时确定该值:a是以下这些值(sysctl -a), b是网络状况,网络状况影响RTT,继而影响重传的时间间隔。但是我们发现,如果tcp_retries2 = 15,在702.2秒内,如果没有重传成功,那么就认为链路已经断开。
net.ipv4.tcp_retries1 = 3 //暂时不明
net.ipv4.tcp_retries2 = 15 //在通信过程中。
static bool retransmits_timed_out(struct sock *sk,
unsigned int boundary,
unsigned int timeout)
{
const unsigned int rto_base = TCP_RTO_MIN;
unsigned int linear_backoff_thresh, start_ts;
if (!inet_csk(sk)->icsk_retransmits)
return false;
start_ts = tcp_sk(sk)->retrans_stamp;
if (unlikely(!start_ts))
start_ts = tcp_skb_timestamp(tcp_write_queue_head(sk));
//boundary就是tcp_retries2或tcp_retries1,可见这里和最终的可重传次数不是相等的
if (likely(timeout == 0)) {
linear_backoff_thresh = ilog2(TCP_RTO_MAX/rto_base); //linear_backoff_thresh = 9
if (boundary <= linear_backoff_thresh)
// (2^9 -1) * HZ / 5 = 511 * 0.2s = 102.2s (最大值 )
timeout = ((2 << boundary) - 1) * rto_base;
else
// 102.2s + 5 * 120s = 702.2s
timeout = ((2 << linear_backoff_thresh) - 1) * rto_base +
(boundary - linear_backoff_thresh) * TCP_RTO_MAX;
}
return (tcp_time_stamp(tcp_sk(sk)) - start_ts) >= jiffies_to_msecs(timeout);
}
static int tcp_write_timeout(struct sock *sk)
{
struct inet_connection_sock *icsk = inet_csk(sk);
struct tcp_sock *tp = tcp_sk(sk);
struct net *net = sock_net(sk);
bool expired, do_reset;
int retry_until;
//三次握手阶段
if ((1 << sk->sk_state) & (TCPF_SYN_SENT | TCPF_SYN_RECV)) {
if (icsk->icsk_retransmits) {
dst_negative_advice(sk);
if (tp->syn_fastopen || tp->syn_data)
tcp_fastopen_cache_set(sk, 0, NULL, true, 0);
if (tp->syn_data && icsk->icsk_retransmits == 1)
NET_INC_STATS(sock_net(sk),
LINUX_MIB_TCPFASTOPENACTIVEFAIL);
} else if (!tp->syn_data && !tp->syn_fastopen) {
sk_rethink_txhash(sk);
}
retry_until = icsk->icsk_syn_retries ? : net->ipv4.sysctl_tcp_syn_retries;
expired = icsk->icsk_retransmits >= retry_until;
} else {//正常通信阶段
//sysctl_tcp_retries1不太理解
if (retransmits_timed_out(sk, net->ipv4.sysctl_tcp_retries1, 0)) {
/* Some middle-boxes may black-hole Fast Open _after_
* the handshake. Therefore we conservatively disable
* Fast Open on this path on recurring timeouts after
* successful Fast Open.
*/
if (tp->syn_data_acked) {
tcp_fastopen_cache_set(sk, 0, NULL, true, 0);
if (icsk->icsk_retransmits == net->ipv4.sysctl_tcp_retries1)
NET_INC_STATS(sock_net(sk),
LINUX_MIB_TCPFASTOPENACTIVEFAIL);
}
/* Black hole detection */
tcp_mtu_probing(icsk, sk);
dst_negative_advice(sk);
} else {
sk_rethink_txhash(sk);
}
retry_until = net->ipv4.sysctl_tcp_retries2;
if (sock_flag(sk, SOCK_DEAD)) {
const bool alive = icsk->icsk_rto < TCP_RTO_MAX;
retry_until = tcp_orphan_retries(sk, alive);
do_reset = alive ||
!retransmits_timed_out(sk, retry_until, 0);
if (tcp_out_of_resources(sk, do_reset))
return 1;
}
expired = retransmits_timed_out(sk, retry_until,
icsk->icsk_user_timeout);
}
if (expired) {
/* Has it gone just too far? */
tcp_write_err(sk);
return 1;
}
return 0;
}
2. 超时重传的顺序,如果多个包都发生重传,如何重传。例如一次重传一个包还是多个包?
先说明一下,重传包每个都只有1mss,即不会使用gso,一次重传一个包,当然可以连续发送多个包(连续发送的包的数量由拥塞窗口控制). 默认情况下,每个发包最大就是1514(mss 1460). 当然收包处是可以gro(generic-receive-offload).
举个例子: 发包1~100, 收包 1-90,其中91-100丢失了. 此时发生超时了,sender重传91,receiver收到91. sender认为链路已正常,接下来sender会发送101之后的包,例如发101-102,很不幸receiver会发一个DACK报文(携带SACK option,表示收到101-102包),提示需要92号报文。sender只好把92-100的包先发了 (以上为实验测试所得,可能不适应各个情况)
3. 超时包重传的优先级和当前待发送包的关系?
这个很明显,当然是先发送超时包,因为ACK机制决定收包是有序的,因此需要先发送超时的包。
4. 重传的报文放置何处,如何区分正常包和重传包?
没有专门的重传队列。所有待发送或待重传的包都放在sock.sk_write_queue中。根据sock.sk_send_head和sock.sk_write_queue可以确认待重传的skb(待确认的skb)