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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章