zoukankan      html  css  js  c++  java
  • linux 协议栈tcp的rst报文中,seq的选取问题

    之前在《深入理解并行编程》的群里,有个小米的兄弟问了一个问题,服务器A发包给服务器B,Seq是1,但是在未能收到服务器B的报文回复的情况下,发送了
    rst,但是rst报文中,对应的seq是1461,一堆人都在猜测,为什么seq跳变了,由于当时只看到一半的图片,所以我让他发送完整报文出来之后,我

    发现其实rst的seq不是1的原因,并不是因为跳变,而是正常的,因为发送给B的报文,长度为1460,但是这个报文没有得到回复,所以在超时之后,应用程序关闭了这条连接,

    导致内核协议栈发送了一个rst报文,而rst报文选取seq的时候,并不是选取的确定已经发送的seq,而是当前连接已经用掉的seq,也就是当前seq,哪怕这个报文没有收到回复,也会使用。

    具体看代码:

    /* We get here when a process closes a file descriptor (either due to
     * an explicit close() or as a byproduct of exit()'ing) and there
     * was unread data in the receive queue.  This behavior is recommended
     * by RFC 2525, section 2.17.  -DaveM
     */
    void tcp_send_active_reset(struct sock *sk, gfp_t priority)
    {
    	struct sk_buff *skb;
    
    	/* NOTE: No TCP options attached and we never retransmit this. */
    	skb = alloc_skb(MAX_TCP_HEADER, priority);
    	if (!skb) {
    		NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPABORTFAILED);
    		return;
    	}
    
    	/* Reserve space for headers and prepare control bits. */
    	skb_reserve(skb, MAX_TCP_HEADER);
    	tcp_init_nondata_skb(skb, tcp_acceptable_seq(sk),
    			     TCPHDR_ACK | TCPHDR_RST);//注意传入的标志是rst,不是fin,可以具体参考tcp_send_fin 是怎么传参数的
    	/* Send it off. */
    	if (tcp_transmit_skb(sk, skb, 0, priority))
    		NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPABORTFAILED);
    
    	TCP_INC_STATS(sock_net(sk), TCP_MIB_OUTRSTS);
    }
    

      其中关注下报文的init过程:

    /* Constructs common control bits of non-data skb. If SYN/FIN is present,
     * auto increment end seqno.
     */
    static void tcp_init_nondata_skb(struct sk_buff *skb, u32 seq, u8 flags)
    {
    	struct skb_shared_info *shinfo = skb_shinfo(skb);
    
    	skb->ip_summed = CHECKSUM_PARTIAL;
    	skb->csum = 0;
    
    	TCP_SKB_CB(skb)->tcp_flags = flags;
    	TCP_SKB_CB(skb)->sacked = 0;
    
    	shinfo->gso_segs = 1;
    	shinfo->gso_size = 0;
    	shinfo->gso_type = 0;
    
    	TCP_SKB_CB(skb)->seq = seq;
    	if (flags & (TCPHDR_SYN | TCPHDR_FIN))//我们本文的标志是 TCPHDR_ACK | TCPHDR_RST ,
                seq++;//此处+1 ,但进不来
    TCP_SKB_CB(skb)->end_seq = seq;//所以本文应该是传入seq是多少就发送多少 }

      那么传入的seq是多少呢?

    /* SND.NXT, if window was not shrunk.
     * If window has been shrunk, what should we make? It is not clear at all.
     * Using SND.UNA we will fail to open window, SND.NXT is out of window. :-(
     * Anything in between SND.UNA...SND.UNA+SND.WND also can be already
     * invalid. OK, let's make this for now:
     */
    static inline __u32 tcp_acceptable_seq(const struct sock *sk)
    {
    	const struct tcp_sock *tp = tcp_sk(sk);
    
    	if (!before(tcp_wnd_end(tp), tp->snd_nxt))
    		return tp->snd_nxt;
    	else
    		return tcp_wnd_end(tp);
    }
    

      注释写得比较清楚,如果窗口没有shrunk,也就是tp->snd_nxt 没有out of window 的话,则取得就是tp->snd_nxt,而这个值,就是报文长度+1了,也就是1461.

     如果不是rst的方式结束,而是fin的方式结束,那么这个seq则应该为多少呢?
    我们来关注下 tcp_send_fin 函数,看看它怎么使用seq的,

    void tcp_send_fin(struct sock *sk)
    {
    	struct tcp_sock *tp = tcp_sk(sk);
    	struct sk_buff *skb = tcp_write_queue_tail(sk);
    	int mss_now;
    
    	/* Optimization, tack on the FIN if we have a queue of
    	 * unsent frames.  But be careful about outgoing SACKS
    	 * and IP options.
    	 */
    	mss_now = tcp_current_mss(sk);
    
    	if (tcp_send_head(sk) != NULL) {//说明还有空间,tcp_send_head返回值为sk->sk_send_head,
    		TCP_SKB_CB(skb)->tcp_flags |= TCPHDR_FIN;
    		TCP_SKB_CB(skb)->end_seq++;
    		tp->write_seq++;
    	} else {
    		/* Socket is locked, keep trying until memory is available. */
    		for (;;) {
    			skb = alloc_skb_fclone(MAX_TCP_HEADER,
    					       sk->sk_allocation);//申请一个skb
    			if (skb)
    				break;
    			yield();
    		}
    
    		/* Reserve space for headers and prepare control bits. */
    		skb_reserve(skb, MAX_TCP_HEADER);
    		/* FIN eats a sequence byte, write_seq advanced by tcp_queue_skb(). */
    		tcp_init_nondata_skb(skb, tp->write_seq,
    				     TCPHDR_ACK | TCPHDR_FIN);//fin包占用一个seq
    		tcp_queue_skb(sk, skb);
    	}
    	__tcp_push_pending_frames(sk, mss_now, TCP_NAGLE_OFF);
    }
    

      不管有没有空间,其实发送出去的序列号就是当前报文的下一个,因为fin也占用一个seq,所以这个seq也是上次发完的seq+报文长度+1.

    水平有限,如果有错误,请帮忙提醒我。如果您觉得本文对您有帮助,可以点击下面的 推荐 支持一下我。版权所有,需要转发请带上本文源地址,博客一直在更新,欢迎 关注 。
  • 相关阅读:
    如何选择RabbitMQ的消息保存方式?
    一次Flannel和Docker网络不通定位问题
    flannel
    vsftp设置不同用户登录ftp的根目录不同
    nginx反向代理服务器获取不到端口的问题的解决办法
    RabbitMQ 内存控制 硬盘控制
    -bash: fork: Cannot allocate memory
    redis info
    nginx第三方模块---nginx-sticky-module的使用(基于cookie的会话保持)
    Haproxy的三种保持客户端会话保持方式
  • 原文地址:https://www.cnblogs.com/10087622blog/p/9340712.html
Copyright © 2011-2022 走看看