欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

TCP之timestamps选项

程序员文章站 2024-02-24 11:24:10
...

默认情况下内核是开启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

相关标签: TCPIP协议 tcpip