TCP之timestamps选项
默认情况下内核是开启timestamps选项的,如下tcp_sk_init函数中对sysctl_tcp_timestamps的初始化。
static int __net_init tcp_sk_init(struct net *net)
{
net->ipv4.sysctl_tcp_timestamps = 1;
也可通过PROC文件tcp_timestamps控制选项行为,tcp_timestamps值为0时,表示关闭timestamps选项;值为1时,表示使能RFC1323(最新-RFC7323)中定义的timestamps选项,并且使用随机偏移值与timesstamp叠加,以抵御攻击。最后,当tcp_timestamps值为2时,仅开启timestamps选项,不使用偏移值。
$ cat /proc/sys/net/ipv4/tcp_timestamps
1
TSopt定义
如下所示,TSopt选项包含2个4字节的时间戳值,其中TSval为发送端的当前时间戳;而TSecr为最近接收到的对端所发送报文的TSopt选项中包含的TSval时间戳值,并且只有在TCP头部ACK标志置位时,携带的TSecr值才有效,否则,将此字段清零。接收端在收到ACK标志未设置的报文时,将忽略TSopt选项中的TSecr字段。
TCP Timestamps option (TSopt):
Kind: 8
Length: 10 bytes
+-------+-------+---------------------+---------------------+
|Kind=8 | 10 | TS Value (TSval) |TS Echo Reply (TSecr)|
+-------+-------+---------------------+---------------------+
1 1 4 4
TSopt选项的支持能力在连接建立阶段进行协商,如果SYN报文中携带了TSopt,并且,SYN+ACK报文中也携带了TSopt,才能成功完成TSopt的协商。如果SYN报文中未携带TSopt,服务端不能在SYN+ACK回复报文中携带TSopt。TSopt协商成功之后,所有的报文都必须携带TSopt选项数据,如果接收到一个未携带TSopt的报文,应将其丢弃。连接复位RST报文,不强制要求携带TSopt选项数据。
如果TSopt能力未协商成功,但在后续接收到了携带有TSopt的报文,应忽略TSopt选项数据,报文正常处理。
SYN报文中的TSopt
TCP客户端使用tcp_v4_connect函数发起连接建立过程,这里,由secure_tcp_ts_off函数根据源地址和目的地址,以及随机生成的秘钥进行hash计算得到一个偏移值。如上所述,如果sysctl_tcp_timestamps值不等于1,tsoffset偏移值为零。
int tcp_v4_connect(struct sock *sk, struct sockaddr *uaddr, int addr_len)
{
if (likely(!tp->repair)) {
tp->tsoffset = secure_tcp_ts_off(sock_net(sk),
inet->inet_saddr, inet->inet_daddr);
}
err = tcp_connect(sk);
在函数tcp_connect中,使用tcp_mstamp_refresh函数,保存发送时间戳到tcp_mstamp(实际上其表示最近一次发送/接收的时间,单位时毫秒)。并且由变量retrans_stamp保存SYN报文的发送时间戳。
int tcp_connect(struct sock *sk)
{
struct tcp_sock *tp = tcp_sk(sk);
tcp_connect_init(sk);
tcp_init_nondata_skb(buff, tp->write_seq++, TCPHDR_SYN);
tcp_mstamp_refresh(tp);
tp->retrans_stamp = tcp_time_stamp(tp);
如下为函数tcp_mstamp_refresh,注意,tcp_mstamp时间戳使用的是微秒单位,以上的retrans_stamp时间戳使用的是毫秒为单位,即TCP时钟。但是两者的时间戳是相同的。另外,在tcp_closk_cahe变量中缓存了此刻的纳秒为单位的时间戳。tcp_mstamp_refresh函数中的if判断,保证了时间戳的值是单向递增的。
变量tcp_mstamp主要用于RTT的估算过程。
void tcp_mstamp_refresh(struct tcp_sock *tp)
{
u64 val = tcp_clock_ns();
if (val > tp->tcp_clock_cache)
tp->tcp_clock_cache = val;
val = div_u64(val, NSEC_PER_USEC);
if (val > tp->tcp_mstamp)
tp->tcp_mstamp = val;
}
static inline u32 tcp_time_stamp(const struct tcp_sock *tp)
{
return div_u64(tp->tcp_mstamp, USEC_PER_SEC / TCP_TS_HZ);
}
在SYN报文发送处理函数__tcp_transmit_skb中,由于SYN报文为首个报文,tcp_wstamp_ns为零,skb_mstamp_ns的取值为tcp_closk_cache的值,即在以上tcp_mstamp_refresh函数中缓存的以纳秒表示的时间戳。在函数tcp_syn_options中,将使用此时间戳为TSopt的TSval赋值。
static int __tcp_transmit_skb(struct sock *sk, struct sk_buff *skb, int clone_it, gfp_t gfp_mask, u32 rcv_nxt)
{
prior_wstamp = tp->tcp_wstamp_ns;
tp->tcp_wstamp_ns = max(tp->tcp_wstamp_ns, tp->tcp_clock_cache);
skb->skb_mstamp_ns = tp->tcp_wstamp_ns;
memset(&opts, 0, sizeof(opts));
if (unlikely(tcb->tcp_flags & TCPHDR_SYN))
tcp_options_size = tcp_syn_options(sk, skb, &opts, &md5);
err = icsk->icsk_af_ops->queue_xmit(sk, skb, &inet->cork.fl);
if (!err && oskb) {
tcp_update_skb_after_send(sk, oskb, prior_wstamp);
tcp_rate_skb_sent(sk, oskb);
}
如下函数tcp_syn_options为SYN报文添加选项数据,如果sysctl_tcp_timestamps为真,将添加TSopt数据。其中,TSval的值为时间戳加上偏移值,这里tcp_skb_timestamp返回的时间戳单位为毫秒(TCP时钟);而TSecr等于零,但是对于此套接口非首次发起连接的情况,TSecr的值有可能不为零。
static unsigned int tcp_syn_options(struct sock *sk, struct sk_buff *skb,
struct tcp_out_options *opts, struct tcp_md5sig_key **md5)
{
struct tcp_sock *tp = tcp_sk(sk);
if (likely(sock_net(sk)->ipv4.sysctl_tcp_timestamps && !*md5)) {
opts->options |= OPTION_TS;
opts->tsval = tcp_skb_timestamp(skb) + tp->tsoffset;
opts->tsecr = tp->rx_opt.ts_recent;
remaining -= TCPOLEN_TSTAMP_ALIGNED;
}
static inline u32 tcp_skb_timestamp(const struct sk_buff *skb)
{
return div_u64(skb->skb_mstamp_ns, NSEC_PER_SEC / TCP_TS_HZ);
}
SYN报文TSopt处理和SYNACK报文的TSopt
在接收到SYN报文之后,函数tcp_conn_request处理此请求,其中与TSopt相关的有三个部分:一是解析报文TCP选项数据;二是request_sock结构初始化;最后是初始化本地TSopt的偏移值。
int tcp_conn_request(struct request_sock_ops *rsk_ops, const struct tcp_request_sock_ops *af_ops,
struct sock *sk, struct sk_buff *skb)
{
tcp_parse_options(sock_net(sk), skb, &tmp_opt, 0, want_cookie ? NULL : &foc);
tmp_opt.tstamp_ok = tmp_opt.saw_tstamp;
tcp_openreq_init(req, &tmp_opt, skb, sk);
if (tmp_opt.tstamp_ok)
tcp_rsk(req)->ts_off = af_ops->init_ts_off(net, skb);
首先,看一下TSopt字段的解析,如下函数tcp_parse_options,在连接建立阶段,即estab为零时,如果本地的sysctl_tcp_timestamps设置为真,将对报文的TSopt进行解析,否则,本地不支持TSopt,无需解析对端的TSopt字段数据。
另外,在连接建立之后,根据tstamp_ok字段判断TSopt是否协商成功,参考以上函数tcp_conn_request,如果此处正确解析对端TSopt,将设置tstamp_ok标志。
void tcp_parse_options(const struct net *net, const struct sk_buff *skb,
struct tcp_options_received *opt_rx, int estab, struct tcp_fastopen_cookie *foc)
{
case TCPOPT_TIMESTAMP:
if ((opsize == TCPOLEN_TIMESTAMP) && ((estab && opt_rx->tstamp_ok) ||
(!estab && net->ipv4.sysctl_tcp_timestamps))) {
opt_rx->saw_tstamp = 1;
opt_rx->rcv_tsval = get_unaligned_be32(ptr);
opt_rx->rcv_tsecr = get_unaligned_be32(ptr + 4);
}
break;
其次,在inet_request_sock初始化过程中,将在其成员tstamp_ok中保存TSopt协商结果。在ts_recent中保存对端报文中的TSval时间戳值,用于之后在TSopt的TSecr中返回给客户端。
static void tcp_openreq_init(struct request_sock *req, const struct tcp_options_received *rx_opt,
struct sk_buff *skb, const struct sock *sk)
{
struct inet_request_sock *ireq = inet_rsk(req);
req->ts_recent = rx_opt->saw_tstamp ? rx_opt->rcv_tsval : 0;
ireq->tstamp_ok = rx_opt->tstamp_ok;
最后,如果TSopt协商成功,对于IPv4而言,函数tcp_v4_init_ts_off将初始化服务端的TS偏移值,但是如果sysctl_tcp_timestamps不等于1,偏移值赋值为零。
static u32 tcp_v4_init_ts_off(const struct net *net, const struct sk_buff *skb)
{
return secure_tcp_ts_off(net, ip_hdr(skb)->daddr, ip_hdr(skb)->saddr);
}
u32 secure_tcp_ts_off(const struct net *net, __be32 saddr, __be32 daddr)
{
if (net->ipv4.sysctl_tcp_timestamps != 1)
return 0;
ts_secret_init();
return siphash_2u32((__force u32)saddr, (__force u32)daddr, &ts_secret);
}
在回复SYN+ACK报文时,函数tcp_make_synack使用tcp_clock_ns获取到当前时刻的纳秒值,保存于skb_mstamp_ns变量中。之后,由函数tcp_synack_options初始化选项字段。
struct sk_buff *tcp_make_synack(const struct sock *sk, struct dst_entry *dst,
struct request_sock *req,...)
{
#ifdef CONFIG_SYN_COOKIES
if (unlikely(req->cookie_ts))
skb->skb_mstamp_ns = cookie_init_timestamp(req);
else
#endif
skb->skb_mstamp_ns = tcp_clock_ns();
tcp_header_size = tcp_synack_options(sk, req, mss, skb, &opts, md5, foc) + sizeof(*th);
此时,根据新创建的inet_request_sock结构中标示TSopt协商成功的标志tstamp_ok,将TSecr赋值为以上tcp_openreq_init函数中赋值的ts_recent变量的值,而本地的TSval值为tcp_skb_timestamp的返回值和ts_off的和。
static unsigned int tcp_synack_options(const struct sock *sk, struct request_sock *req,
unsigned int mss, struct sk_buff *skb,
struct tcp_out_options *opts,...)
{
struct inet_request_sock *ireq = inet_rsk(req);
if (likely(ireq->tstamp_ok)) {
opts->options |= OPTION_TS;
opts->tsval = tcp_skb_timestamp(skb) + tcp_rsk(req)->ts_off;
opts->tsecr = req->ts_recent;
remaining -= TCPOLEN_TSTAMP_ALIGNED;
}
如下函数tcp_skb_timestamp,将skb_mstamp_ns中的纳秒值转换为timestamps的毫秒值(TCP时钟)。
#define TCP_TS_HZ 1000
static inline u32 tcp_skb_timestamp(const struct sk_buff *skb)
{
return div_u64(skb->skb_mstamp_ns, NSEC_PER_SEC / TCP_TS_HZ);
}
SYNACK报文TSopt处理
TCP客户端在接收到SYNACK报文之后,由函数tcp_rcv_synsent_state_process进行处理,tcp_parse_options负责解析选项信息,如果TSopt选项解析完成,saw_tstamp为真,并且服务端返回的TSecr值不为零,减去偏移值tsoffset,即得到本端原始的时间戳值。
static int tcp_rcv_synsent_state_process(struct sock *sk, struct sk_buff *skb, const struct tcphdr *th)
{
...
tcp_parse_options(sock_net(sk), skb, &tp->rx_opt, 0, &foc);
if (tp->rx_opt.saw_tstamp && tp->rx_opt.rcv_tsecr)
tp->rx_opt.rcv_tsecr -= tp->tsoffset;
if (th->ack) {
if (tp->rx_opt.saw_tstamp && tp->rx_opt.rcv_tsecr &&
!between(tp->rx_opt.rcv_tsecr, tp->retrans_stamp, tcp_time_stamp(tp))) {
NET_INC_STATS(sock_net(sk), LINUX_MIB_PAWSACTIVEREJECTED);
goto reset_and_undo;
}
if (tp->rx_opt.saw_tstamp) {
tp->rx_opt.tstamp_ok = 1;
tp->tcp_header_len = sizeof(struct tcphdr) + TCPOLEN_TSTAMP_ALIGNED;
tp->advmss -= TCPOLEN_TSTAMP_ALIGNED;
tcp_store_ts_recent(tp);
}
在函数tcp_connect中使用retrans_stamp保存了SYN报文的时间戳,而tcp_time_stamp返回的为套接口最近发送或接收报文的时间戳,如在函数tcp_rcv_state_process中对此时间戳的更新(tcp_mstamp_refresh),因此,TSecr应当位于retrans_stamp和tcp_mstamp两者之间。此三个时间戳值的单位都是毫秒。函数tcp_mstamp_refresh也将更新tcp_clock_cache缓存值。
int tcp_rcv_state_process(struct sock *sk, struct sk_buff *skb)
{
switch (sk->sk_state) {
case TCP_SYN_SENT:
tp->rx_opt.saw_tstamp = 0;
tcp_mstamp_refresh(tp);
queued = tcp_rcv_synsent_state_process(sk, skb, th);
在处理函数tcp_rcv_synsent_state_process中,TSopt选项协商完成,设置标志位tstamp_ok,并且,在函数tcp_store_ts_recent中,将服务端SYNACK报文中的TSval值保存在ts_recent变量中,记录下ts_recent更新时刻的时间戳(ts_recent_stamp)。
static void tcp_store_ts_recent(struct tcp_sock *tp)
{
tp->rx_opt.ts_recent = tp->rx_opt.rcv_tsval;
tp->rx_opt.ts_recent_stamp = ktime_get_seconds();
}
TCP客户端回复的ACK报文(第三个握手报文)以SYN报文一样,由函数__tcp_transmit_skb负责发送。通常情况下skb_mstamp_ns的值由tcp_clock_cache决定,稍后介绍例外情况。由于不是SYN报文,此时使用函数tcp_established_options设置TCP选项数据。
static int __tcp_transmit_skb(struct sock *sk, struct sk_buff *skb,
int clone_it, gfp_t gfp_mask, u32 rcv_nxt)
{
const struct inet_connection_sock *icsk = inet_csk(sk);
tp = tcp_sk(sk);
prior_wstamp = tp->tcp_wstamp_ns;
tp->tcp_wstamp_ns = max(tp->tcp_wstamp_ns, tp->tcp_clock_cache);
skb->skb_mstamp_ns = tp->tcp_wstamp_ns;
memset(&opts, 0, sizeof(opts));
if (unlikely(tcb->tcp_flags & TCPHDR_SYN))
tcp_options_size = tcp_syn_options(sk, skb, &opts, &md5);
else
tcp_options_size = tcp_established_options(sk, skb, &opts, &md5);
以下函数tcp_established_options,首先判断TSopt是否协商成功;其次,将记录在ts_recent中的服务端时间戳赋值到TSecr字段,返回给服务端;之后,本端的时间戳值TSval设置为当前时间加上偏移值tsoffset。之前介绍了tcp_skb_timestamp函数的作用是将skb_mstamp_ns变量表示的纳秒值转换为毫秒值。
static unsigned int tcp_established_options(struct sock *sk, struct sk_buff *skb,
struct tcp_out_options *opts, struct tcp_md5sig_key **md5)
{
struct tcp_sock *tp = tcp_sk(sk);
if (likely(tp->rx_opt.tstamp_ok)) {
opts->options |= OPTION_TS;
opts->tsval = skb ? tcp_skb_timestamp(skb) + tp->tsoffset : 0;
opts->tsecr = tp->rx_opt.ts_recent;
size += TCPOLEN_TSTAMP_ALIGNED;
}
ACK报文TSopt选项
函数tcp_check_req首先对ACK报文进行相应的检查,这里仅关注TSopt相关的部分,如果ACK报文中携带有TSopt选项(saw_tstamp为真),将request_sock中保存的上次客户端通过的时间戳ts_recent赋值到tcp_options_received结构的tmp_opt变量的成员ts_recent中,随后的PAWS检查函数,将比较此时间戳与当前ACK报文中时间戳的大小,以便判断是否是重复的报文。
另外,在设置ts_recent的同时,设置了时间戳ts_recent_stamp,这里其单位为秒。num_timeout为SYNACK超时重传的次数。之后,根据报文中TSopt字段的TSval值更新ts_recent值,记录下客户端ACK报文中的时间戳。
struct sock *tcp_check_req(struct sock *sk, struct sk_buff *skb,
struct request_sock *req, bool fastopen, bool *req_stolen)
{
tmp_opt.saw_tstamp = 0;
if (th->doff > (sizeof(struct tcphdr)>>2)) {
tcp_parse_options(sock_net(sk), skb, &tmp_opt, 0, NULL);
if (tmp_opt.saw_tstamp) {
tmp_opt.ts_recent = req->ts_recent;
if (tmp_opt.rcv_tsecr)
tmp_opt.rcv_tsecr -= tcp_rsk(req)->ts_off;
/* We do not store true stamp, but it is not required,
* it can be estimated (approximately) from another data.
*/
tmp_opt.ts_recent_stamp = ktime_get_seconds() - ((TCP_TIMEOUT_INIT/HZ)<<req->num_timeout);
paws_reject = tcp_paws_reject(&tmp_opt, th->rst);
}
}
...
/* In sequence, PAWS is OK. */
if (tmp_opt.saw_tstamp && !after(TCP_SKB_CB(skb)->seq, tcp_rsk(req)->rcv_nxt))
req->ts_recent = tmp_opt.rcv_tsval;
child = inet_csk(sk)->icsk_af_ops->syn_recv_sock(sk, skb, req, NULL, req, &own_req);
在tcp_v4_syn_recv_sock函数中,将使用tcp_create_openreq_child创建子套接口,将TSopt相关的数据保存到新套接口中。比如,TSopt协商标志tstamp_ok,客户端的时间戳ts_recent,以及本端的偏移值tsoffset。
在函数inet_csk_clone_lock中,将子套接口的状态设置为了TCP_SYN_RECV,稍后将用到此状态。
struct sock *tcp_create_openreq_child(const struct sock *sk, struct request_sock *req, struct sk_buff *skb)
{
struct sock *newsk = inet_csk_clone_lock(sk, req, GFP_ATOMIC);
const struct inet_request_sock *ireq = inet_rsk(req);
struct tcp_request_sock *treq = tcp_rsk(req);
newtp->rx_opt.tstamp_ok = ireq->tstamp_ok;
if (newtp->rx_opt.tstamp_ok) {
newtp->rx_opt.ts_recent = req->ts_recent;
newtp->rx_opt.ts_recent_stamp = ktime_get_seconds();
newtp->tcp_header_len = sizeof(struct tcphdr) + TCPOLEN_TSTAMP_ALIGNED;
} else {
newtp->rx_opt.ts_recent_stamp = 0;
newtp->tcp_header_len = sizeof(struct tcphdr);
}
newtp->tsoffset = treq->ts_off;
在接收状态机函数tcp_rcv_state_process中,首先由函数tcp_mstamp_refresh更新套接口中的缓存时间戳tcp_clock_cache,之后由tcp_ack函数处理ACK报文,最后子套接口的状态已经设置为了TCP_SYN_RECV,此处将其更改为TCP_ESTABLISHED。
int tcp_rcv_state_process(struct sock *sk, struct sk_buff *skb)
{
tcp_mstamp_refresh(tp);
tp->rx_opt.saw_tstamp = 0;
if (!th->ack && !th->rst && !th->syn)
goto discard;
if (!tcp_validate_incoming(sk, skb, th, 0))
return 0;
/* step 5: check the ACK field */
acceptable = tcp_ack(sk, skb, FLAG_SLOWPATH |
FLAG_UPDATE_TS_RECENT | FLAG_NO_CHALLENGE_ACK) > 0;
switch (sk->sk_state) {
case TCP_SYN_RECV:
...
tcp_set_state(sk, TCP_ESTABLISHED);
}
由于在调用tcp_ack函数时,指定了FLAG_UPDATE_TS_RECENT选项,调用函数tcp_replace_ts_recent对其进行更新。
static int tcp_ack(struct sock *sk, const struct sk_buff *skb, int flag)
{
/* ts_recent update must be made after we are sure that the packet is in window.
*/
if (flag & FLAG_UPDATE_TS_RECENT)
tcp_replace_ts_recent(tp, TCP_SKB_CB(skb)->seq);
最终,函数tcp_store_ts_recent使用选项结构rx_opt中保存的TSval值更新ts_recent时间戳。
static void tcp_store_ts_recent(struct tcp_sock *tp)
{
tp->rx_opt.ts_recent = tp->rx_opt.rcv_tsval;
tp->rx_opt.ts_recent_stamp = ktime_get_seconds();
}
static void tcp_replace_ts_recent(struct tcp_sock *tp, u32 seq)
{
if (tp->rx_opt.saw_tstamp && !after(seq, tp->rcv_wup)) {
/* PAWS bug workaround wrt. ACK frames, the PAWS discard
* extra check below makes sure this can only happen for pure ACK frames. -DaveM
* Not only, also it occurs for expired timestamps.
*/
if (tcp_paws_check(&tp->rx_opt, 0))
tcp_store_ts_recent(tp);
}
}
TSval取值
在连接建立之后,函数tcp_established_options负责为TSopt选项赋值,以下可见TSval的值由skb_mstamp_ns和tsoffset偏移值组成。
static unsigned int tcp_established_options(struct sock *sk, struct sk_buff *skb,
struct tcp_out_options *opts,...)
{
if (likely(tp->rx_opt.tstamp_ok)) {
opts->options |= OPTION_TS;
opts->tsval = skb ? tcp_skb_timestamp(skb) + tp->tsoffset : 0;
opts->tsecr = tp->rx_opt.ts_recent;
size += TCPOLEN_TSTAMP_ALIGNED;
}
static inline u32 tcp_skb_timestamp(const struct sk_buff *skb)
{
return div_u64(skb->skb_mstamp_ns, NSEC_PER_SEC / TCP_TS_HZ);
}
在发送函数__tcp_transmit_skb中,skb_mstamp_ns的值等于tcp_wstamp_ns和tcp_clock_cache两者中的最大值。
static int __tcp_transmit_skb(struct sock *sk, struct sk_buff *skb, int clone_it, gfp_t gfp_mask, u32 rcv_nxt)
{
prior_wstamp = tp->tcp_wstamp_ns;
tp->tcp_wstamp_ns = max(tp->tcp_wstamp_ns, tp->tcp_clock_cache);
skb->skb_mstamp_ns = tp->tcp_wstamp_ns;
if (unlikely(tcb->tcp_flags & TCPHDR_SYN))
else
tcp_options_size = tcp_established_options(sk, skb, &opts, &md5);
err = icsk->icsk_af_ops->queue_xmit(sk, skb, &inet->cork.fl);
if (!err && oskb) {
tcp_update_skb_after_send(sk, oskb, prior_wstamp);
tcp_rate_skb_sent(sk, oskb);
}
由函数tcp_write_xmit可知,在发送之前,其使用tcp_mstamp_refresh函数,将tcp_clock_cache时间戳值更新到了当前时刻。所以,通过情况下,tcp_clock_cache的值大于tcp_wstamp_ns。
static bool tcp_write_xmit(struct sock *sk, unsigned int mss_now, int nonagle, int push_one, gfp_t gfp)
{
tcp_mstamp_refresh(tp);
while ((skb = tcp_send_head(sk))) {
...
if (unlikely(tcp_transmit_skb(sk, skb, 1, gfp)))
break;
如下函数tcp_update_skb_after_send,其在报文发送之后被调用,以确定下一个报文的发送时间戳。由于Pacing的存在,报文并不一定在tcp_write_xmit调用的时刻被发送出去。而函数tcp_update_skb_after_send根据Pacing速率和报文长度,估算出下一个报文的发送时刻,保存在tcp_wstamp_ns中,此种情况下,其有可能大于tcp_clock_cache的值。所以TSval的值取两者之间较大值。
static void tcp_update_skb_after_send(struct sock *sk, struct sk_buff *skb, u64 prior_wstamp)
{
struct tcp_sock *tp = tcp_sk(sk);
skb->skb_mstamp_ns = tp->tcp_wstamp_ns;
if (sk->sk_pacing_status != SK_PACING_NONE) {
unsigned long rate = sk->sk_pacing_rate;
/* Original sch_fq does not pace first 10 MSS Note that tp->data_segs_out overflows after 2^32 packets,
* this is a minor annoyance.
*/
if (rate != ~0UL && rate && tp->data_segs_out >= 10) {
u64 len_ns = div64_ul((u64)skb->len * NSEC_PER_SEC, rate);
u64 credit = tp->tcp_wstamp_ns - prior_wstamp;
/* take into account OS jitter */
len_ns -= min_t(u64, len_ns / 2, credit);
tp->tcp_wstamp_ns += len_ns;
}
}
list_move_tail(&skb->tcp_tsorted_anchor, &tp->tsorted_sent_queue);
TSecr值的选取
由以上的介绍可知,TSecr的值取自ts_recent变量。RFC7323中规定ts_recent的更新需满足以下公式,即接收报文中的TSval大于等于本地上次记录的ts_recent值,并且,接收报文的序号小于等于本地最近ACK字段请求的序号:
SEG.TSval >= TS.Recent and SEG.SEQ <= Last.ACK.sent
then SEG.TSval is copied to TS.Recent; otherwise, it is ignored.
在quickack模式下,一个报文对应一个ACK,必然满足以上的条件。在延迟ACK模式下,例如接收到两个数据报文之后,回复一个ACK,只有第一个数据报文满足以上条件,第二个数据报文的序号将大于Last.ACK.sent。另外,当接收到一个乱序报文时,其序号也将大于Last.ACK.sent的值,乱序报文的TSval将不用做更新ts_recent。
变量ts_recent的值通常情况下由函数tcp_store_ts_recent进行更新,并且记录下更新时间(ts_recent_stamp),更新时间由PAWS检查时使用。
static void tcp_store_ts_recent(struct tcp_sock *tp)
{
tp->rx_opt.ts_recent = tp->rx_opt.rcv_tsval;
tp->rx_opt.ts_recent_stamp = ktime_get_seconds();
}
在连接建立之后,函数tcp_rcv_established处理接收报文,首先看一下在TCP快速接收路径中ts_recent时间戳的更新。前提是报文中的TSval值必须大于当前记录的ts_recent值,再者看一下第二个条件SEG.SEQ <= Last.ACK.sent的判断,在内核中对应seq<=rcv_wup,而在此快速路径中,seq是等于rcv_nxt的,即rcv_nxt<=rcv_wup,最后rcv_wup变量表示的是上一次窗口更新时的rcv_nxt的值,必然有rcv_nxt>=rcv_wup,所以,仅需要判断rcv_nxt是否等于rcv_wup。
另外,对于报文长度len,小于tcp_header_len指定长度(其中包含TCPOLEN_TSTAMP_ALIGNED长度)的报文,说明其中未携带TSopt字段数据,将其丢弃。
void tcp_rcv_established(struct sock *sk, struct sk_buff *skb)
{
if ((tcp_flag_word(th) & TCP_HP_BITS) == tp->pred_flags &&
TCP_SKB_CB(skb)->seq == tp->rcv_nxt &&
!after(TCP_SKB_CB(skb)->ack_seq, tp->snd_nxt)) {
if (tcp_header_len == sizeof(struct tcphdr) + TCPOLEN_TSTAMP_ALIGNED) {
if (!tcp_parse_aligned_timestamp(tp, th)) /* No? Slow path! */
goto slow_path;
/* If PAWS failed, check it more carefully in slow path */
if ((s32)(tp->rx_opt.rcv_tsval - tp->rx_opt.ts_recent) < 0)
goto slow_path;
}
if (len <= tcp_header_len) { /* Bulk data transfer: sender */
if (len == tcp_header_len) {
/* Predicted packet is in window by definition.
* seq == rcv_nxt and rcv_wup <= rcv_nxt.
* Hence, check seq<=rcv_wup reduces to:
*/
if (tcp_header_len == (sizeof(struct tcphdr) + TCPOLEN_TSTAMP_ALIGNED) &&
tp->rcv_nxt == tp->rcv_wup)
tcp_store_ts_recent(tp);
} else { /* Header too small */
TCP_INC_STATS(sock_net(sk), TCP_MIB_INERRS);
goto discard;
}
对于TCP慢速路径,将在tcp_ack中更新ts_recent时间戳,以上也将进行了接收,其参数FLAG_UPDATE_TS_RECENT指明了这一点。
slow_path:
if (len < (th->doff << 2) || tcp_checksum_complete(skb))
goto csum_error;
if (!th->ack && !th->rst && !th->syn)
goto discard;
/* Standard slow path. */
if (!tcp_validate_incoming(sk, skb, th, 1))
return;
step5:
if (tcp_ack(sk, skb, FLAG_SLOWPATH | FLAG_UPDATE_TS_RECENT) < 0)
goto discard;
tcp_ack使用函数tcp_replace_ts_recent更新ts_recent的值,以下函数实现可见,两个if语句确保负荷RFC7323中的规定。
static void tcp_replace_ts_recent(struct tcp_sock *tp, u32 seq)
{
if (tp->rx_opt.saw_tstamp && !after(seq, tp->rcv_wup)) {
/* PAWS bug workaround wrt. ACK frames, the PAWS discard
* extra check below makes sure this can only happen for pure ACK frames. -DaveM
*
* Not only, also it occurs for expired timestamps.
*/
if (tcp_paws_check(&tp->rx_opt, 0))
tcp_store_ts_recent(tp);
}
RFC7323中对ts_recent的更新定义,目的再有帮助对端能够更好的计算连接RTT值。例如,对于延迟ACK的情况,ts_recent使用的是最早的未确认报文中携带的TSval的值,这将使得对端计算的RTT值变大,降低对端进行重传的风险。反之,如果使用最近一个接收到的报文中的TSval值,对端计算的RTT值将减小,进一步导致RTO值减小,容易造成对较早发送报文进行不必要的重传。
TIMEWAIT套接口TSopt
套接口在进入TIMEWAIT状态(子状态可能为TCP_FIN_WAIT2)之后,分配新的inet_timewait_sock结构套接口,随将TSopt相关记录:ts_recent、ts_recent_stamp和tsoffset赋值到新的套接口中。
void tcp_time_wait(struct sock *sk, int state, int timeo)
{
const struct inet_connection_sock *icsk = inet_csk(sk);
struct inet_timewait_sock *tw;
tw = inet_twsk_alloc(sk, tcp_death_row, state);
if (tw) {
struct tcp_timewait_sock *tcptw = tcp_twsk((struct sock *)tw);
const int rto = (icsk->icsk_rto << 2) - (icsk->icsk_rto >> 1);
struct inet_sock *inet = inet_sk(sk);
tcptw->tw_ts_recent = tp->rx_opt.ts_recent;
tcptw->tw_ts_recent_stamp = tp->rx_opt.ts_recent_stamp;
tcptw->tw_ts_offset = tp->tsoffset;
如下所示为TIMEWAIT套接口状态机处理函数tcp_timewait_state_process,其中对TSopt数据的处理与以上介绍的一致。
enum tcp_tw_status
tcp_timewait_state_process(struct inet_timewait_sock *tw, struct sk_buff *skb, const struct tcphdr *th)
{
struct tcp_options_received tmp_opt;
struct tcp_timewait_sock *tcptw = tcp_twsk((struct sock *)tw);
bool paws_reject = false;
tmp_opt.saw_tstamp = 0;
if (th->doff > (sizeof(*th) >> 2) && tcptw->tw_ts_recent_stamp) {
tcp_parse_options(twsk_net(tw), skb, &tmp_opt, 0, NULL);
if (tmp_opt.saw_tstamp) {
if (tmp_opt.rcv_tsecr)
tmp_opt.rcv_tsecr -= tcptw->tw_ts_offset;
tmp_opt.ts_recent = tcptw->tw_ts_recent;
tmp_opt.ts_recent_stamp = tcptw->tw_ts_recent_stamp;
paws_reject = tcp_paws_reject(&tmp_opt, th->rst);
}
}
内核版本 5.0