Linux TCP Client会出现自己连接自己现象详解

因在工作中主要从事后台服务网络基础设施模块,特将遇到的tcp坑,做了深入的研究,并做了整理,纯自己研究手打,转载请标明出处!

一.现象:tcp在创建连接时,如果被连接的Server(socket)不存在,有几率会连接上自己。

二.验证

输入命令:strace nc 127.0.0.1 10000 -p 10000 使用nc命令创建tcp client连接目标端口10000,并通过-p参数绑定client的端口,而不是系统选择client的端口号

三. strace结果

Linux TCP Client会出现自己连接自己现象详解

四. tcpdump 抓包结果

Linux TCP Client会出现自己连接自己现象详解

五. 具体分析:

      这种叫作tcp的同时打开(simultaneous connect),两个不同port同时发SYN建立连接,例如:主机A通过本地的5555端口向主机B的6666端口发送一个主动打开请求,与此同时主机B通过本地的6666端口向主机A的5555端口发送主动打开请求。而这里是先创建了一个socket,然后socket bind到10000端口上(作为local port,因为nc指定了local port),然后执行connect, 连接到的目标也是127.0.0.1:10000,而这个目标正好是刚刚创建的socket,也就是自己连自己(连接双方总共只有一个socket)。因为一个socket充当了两个角色(client、server),握手的时候发SYN,自己收到自己发的SYN,就相当于两个角色同时打开了。

六. TCP的同时打开(simultaneous connect):(参见TCP/IP详解原书第二版第13章,426页)

tcp同时打开交换的报文段:

Linux TCP Client会出现自己连接自己现象详解

 

Linux 内核源码分析(linux-4.14.213),地址:Linux内核官网

5689 static int tcp_rcv_synsent_state_process(struct sock *sk, struct sk_buff *skb,
5690                      const struct tcphdr *th)
5691 {
5692     struct inet_connection_sock *icsk = inet_csk(sk);
5693     struct tcp_sock *tp = tcp_sk(sk);
5694     struct tcp_fastopen_cookie foc = { .len = -1 };
5695     int saved_clamp = tp->rx_opt.mss_clamp;
5696     bool fastopen_fail;
5697
5698     tcp_parse_options(sock_net(sk), skb, &tp->rx_opt, 0, &foc);
5699     if (tp->rx_opt.saw_tstamp && tp->rx_opt.rcv_tsecr)
5700         tp->rx_opt.rcv_tsecr -= tp->tsoffset;
5701
5702     if (th->ack) {
5703         /* rfc793:
5704          * "If the state is SYN-SENT then
5705          *    first check the ACK bit
5706          *      If the ACK bit is set
5707          *    If SEG.ACK =< ISS, or SEG.ACK > SND.NXT, send
5708          *        a reset (unless the RST bit is set, if so drop
5709          *        the segment and return)"
5710          */
5711         if (!after(TCP_SKB_CB(skb)->ack_seq, tp->snd_una) ||
5712             after(TCP_SKB_CB(skb)->ack_seq, tp->snd_nxt))
5713             goto reset_and_undo;
5714
5715         if (tp->rx_opt.saw_tstamp && tp->rx_opt.rcv_tsecr &&
5716             !between(tp->rx_opt.rcv_tsecr, tp->retrans_stamp,
5717                  tcp_time_stamp(tp))) {
5718             NET_INC_STATS(sock_net(sk),
5719                     LINUX_MIB_PAWSACTIVEREJECTED);
5720             goto reset_and_undo;
5721         }
5722
5723         /* Now ACK is acceptable.
5724          *
5725          * "If the RST bit is set
5726          *    If the ACK was acceptable then signal the user "error:
5727          *    connection reset", drop the segment, enter CLOSED state,
5728          *    delete TCB, and return."
5729          */
5730
5731         if (th->rst) {
5732             tcp_reset(sk);
5733             goto discard;
5734         }
5735
5736         /* rfc793:
5737          *   "fifth, if neither of the SYN or RST bits is set then
5738          *    drop the segment and return."
5739          *
5740          *    See note below!
5741          *                                        --ANK(990513)
5742          */
5743         if (!th->syn)
5744             goto discard_and_undo;
5745
5746         /* rfc793:
5747          *   "If the SYN bit is on ...
5748          *    are acceptable then ...
5749          *    (our SYN has been ACKed), change the connection
5750          *    state to ESTABLISHED..."
5751          */
5752
5753         tcp_ecn_rcv_synack(tp, th);
5754
5755         tcp_init_wl(tp, TCP_SKB_CB(skb)->seq);
5756         tcp_ack(sk, skb, FLAG_SLOWPATH);
5757
5758         /* Ok.. it's good. Set up sequence numbers and
5759          * move to established.
5760          */
5761         tp->rcv_nxt = TCP_SKB_CB(skb)->seq + 1;
5762         tp->rcv_wup = TCP_SKB_CB(skb)->seq + 1;
5763
5764         /* RFC1323: The window in SYN & SYN/ACK segments is
5765          * never scaled.
5766          */
5767         tp->snd_wnd = ntohs(th->window);
5768
5769         if (!tp->rx_opt.wscale_ok) {
5770             tp->rx_opt.snd_wscale = tp->rx_opt.rcv_wscale = 0;
5771             tp->window_clamp = min(tp->window_clamp, 65535U);
5772         }
5773
5774         if (tp->rx_opt.saw_tstamp) {
5775             tp->rx_opt.tstamp_ok       = 1;
5776             tp->tcp_header_len =
5777                 sizeof(struct tcphdr) + TCPOLEN_TSTAMP_ALIGNED;
5778             tp->advmss      -= TCPOLEN_TSTAMP_ALIGNED;
5779             tcp_store_ts_recent(tp);
5780         } else {
5781             tp->tcp_header_len = sizeof(struct tcphdr);
5782         }
5783
5784         if (tcp_is_sack(tp) && sysctl_tcp_fack)
5785             tcp_enable_fack(tp);
5786
5787         tcp_mtup_init(sk);
5788         tcp_sync_mss(sk, icsk->icsk_pmtu_cookie);
5789         tcp_initialize_rcv_mss(sk);
5790
5791         /* Remember, tcp_poll() does not lock socket!
5792          * Change state from SYN-SENT only after copied_seq
5793          * is initialized. */
5794         tp->copied_seq = tp->rcv_nxt;
5795
5796         smp_mb();
5797
5798         tcp_finish_connect(sk, skb);
5799
5800         fastopen_fail = (tp->syn_fastopen || tp->syn_data) &&
5801                 tcp_rcv_fastopen_synack(sk, skb, &foc);
5802
5803         if (!sock_flag(sk, SOCK_DEAD)) {
5804             sk->sk_state_change(sk);
5805             sk_wake_async(sk, SOCK_WAKE_IO, POLL_OUT);
5806         }
5807         if (fastopen_fail)
5808             return -1;
5809         if (sk->sk_write_pending ||
5810             icsk->icsk_accept_queue.rskq_defer_accept ||
5811             icsk->icsk_ack.pingpong) {
5812             /* Save one ACK. Data will be ready after
5813              * several ticks, if write_pending is set.
5814              *
5815              * It may be deleted, but with this feature tcpdumps
5816              * look so _wonderfully_ clever, that I was not able
5817              * to stand against the temptation 8)     --ANK
5818              */
5819             inet_csk_schedule_ack(sk);
5820             tcp_enter_quickack_mode(sk, TCP_MAX_QUICKACKS);
5821             inet_csk_reset_xmit_timer(sk, ICSK_TIME_DACK,
5822                           TCP_DELACK_MAX, TCP_RTO_MAX);
5823
5824 discard:
5825             tcp_drop(sk, skb);
5826             return 0;
5827         } else {
5828             tcp_send_ack(sk);
5829         }
5830         return -1;
5831     }
5832
5833     /* No ACK in the segment */
5834
5835     if (th->rst) {
5836         /* rfc793:
5837          * "If the RST bit is set
5838          *
5839          *      Otherwise (no ACK) drop the segment and return."
5840          */
5841
5842         goto discard_and_undo;
5843     }
5844
5845     /* PAWS check. */
5846     if (tp->rx_opt.ts_recent_stamp && tp->rx_opt.saw_tstamp &&
5847         tcp_paws_reject(&tp->rx_opt, 0))
5848         goto discard_and_undo;
5849
5850     if (th->syn) {
5851         /* We see SYN without ACK. It is attempt of
5852          * simultaneous connect with crossed SYNs.
5853          * Particularly, it can be connect to self.
5854          */
5855         tcp_set_state(sk, TCP_SYN_RECV);
5856
5857         if (tp->rx_opt.saw_tstamp) {
5858             tp->rx_opt.tstamp_ok = 1;
5859             tcp_store_ts_recent(tp);
5860             tp->tcp_header_len =
5861                 sizeof(struct tcphdr) + TCPOLEN_TSTAMP_ALIGNED;
5862         } else {
5863             tp->tcp_header_len = sizeof(struct tcphdr);
5864         }
5865
5866         tp->rcv_nxt = TCP_SKB_CB(skb)->seq + 1;
5867         tp->copied_seq = tp->rcv_nxt;
5868         tp->rcv_wup = TCP_SKB_CB(skb)->seq + 1;
5869
5870         /* RFC1323: The window in SYN & SYN/ACK segments is
5871          * never scaled.
5872          */
5873         tp->snd_wnd    = ntohs(th->window);
5874         tp->snd_wl1    = TCP_SKB_CB(skb)->seq;
5875         tp->max_window = tp->snd_wnd;
5876
5877         tcp_ecn_rcv_syn(tp, th);
5878
5879         tcp_mtup_init(sk);
5880         tcp_sync_mss(sk, icsk->icsk_pmtu_cookie);
5881         tcp_initialize_rcv_mss(sk);
5882
5883         tcp_send_synack(sk);
5884 #if 0
5885         /* Note, we could accept data and URG from this segment.
5886          * There are no obstacles to make this (except that we must
5887          * either change tcp_recvmsg() to prevent it from returning data
5888          * before 3WHS completes per RFC793, or employ TCP Fast Open).
5889          *
5890          * However, if we ignore data in ACKless segments sometimes,
5891          * we have no reasons to accept it sometimes.
5892          * Also, seems the code doing it in step6 of tcp_rcv_state_process
5893          * is not flawless. So, discard packet for sanity.
5894          * Uncomment this return to process the data.
5895          */
5896         return -1;
5897 #else
5898         goto discard;
5899 #endif
5900     }
5901     /* "fifth, if neither of the SYN or RST bits is set then
5902      * drop the segment and return."
5903      */
5904
5905 discard_and_undo:
5906     tcp_clear_options(&tp->rx_opt);
5907     tp->rx_opt.mss_clamp = saved_clamp;
5908     goto discard;
5909
5910 reset_and_undo:
5911     tcp_clear_options(&tp->rx_opt);
5912     tp->rx_opt.mss_clamp = saved_clamp;
5913     return 1;
5914 }

  可以清楚地看到这个连接建立用了四次握手,然后连接建立了,当然也有同时关闭(4次挥手成功关闭连接)。查阅内核代码(linux_4.14.213) net/ipv4/tcp_input.c文件中tcp_rcv_synsent_state_process()函数发现5851行就说明了允许这种自己连自己的连接(当然也允许同时打开). 也就是允许一个socket本来应该收到 SYN+ACK(发出SYN后), 结果收到了SYN的情况,而一个socket自己连自己又是这种情况的特例。也就是在发送SYN进入SYN_SENT状态之后,收到对端发来的SYN包后不会RST,而是处理流程如下,调用tcp_set_state(sk, TCP_SYN_RECV)进入SYN_RECV状态,以及调用tcp_send_synack(sk)向对端发送SYN+ACK。

六.原理解释:

tcp内核的同时打开:也就是说socket发送SYN后,本来应该收到一个SYN+ACK的,但是实际收到了一个SYN(没有ACK),这是允许的。TCP连接同时打开(同时给对方发SYN),四次握手然后建立连接成功。

自己连自己又是tcp同时打开的一个特例,特别在这个连接只有一个socket参与,发送、接收都是同一个socket,自然也会是发SYN后收到了自己的SYN(自己发给自己),然后依照同时打开连接也能创建成功。

根据nc命令的验证这个bind到10000(local port)的socket又要连接到10000的port上,而这个10000的socket已经bind到了socket(也就是自己),就形成了两个socket 的同时打开一样,内核又允许这种同时打开,所以就形成了自己连自己,也就是一个socket在自己给自己收发数据,所以tcpdump 看到收方和发放的seq是一样的,数据包开始正常发送。因此会有意想不到的现象发生

Mark 2021.01.09 晚上11:37刨坑结束 可以安心睡觉去了

上一篇:@2021SC@SDUSC 源码分析: 格加密模块的初窥


下一篇:摆脱JS框架,5年web组件开发经验总结