因在工作中主要从事后台服务网络基础设施模块,特将遇到的tcp坑,做了深入的研究,并做了整理,纯自己研究手打,转载请标明出处!
一.现象:tcp在创建连接时,如果被连接的Server(socket)不存在,有几率会连接上自己。
二.验证:
输入命令:strace nc 127.0.0.1 10000 -p 10000 使用nc命令创建tcp client连接目标端口10000,并通过-p参数绑定client的端口,而不是系统选择client的端口号
三. strace结果:
四. tcpdump 抓包结果:
五. 具体分析:
这种叫作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 内核源码分析(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是一样的,数据包开始正常发送。因此会有意想不到的现象发生