zoukankan      html  css  js  c++  java
  • linux: socket编程知识点

    tcp的半连接与完全连接队列


    tcp-sync-queue-and-accept-queue-small.jpg

    server端的半连接队列(syn队列)

    在三次握手协议中,服务器维护一个半连接队列,该队列为每个客户端的SYN包开设一个条目(服务端在接收到SYN包的时候,就已经创建了request_sock结构,存储在半连接队列中),该条目表明服务器已收到SYN包,并向客户发出确认,正在等待客户的确认包(会进行第二次握手发送SYN+ACK 的包加以确认)。这些条目所标识的连接在服务器处于Syn_RECV状态,当服务器收到客户的确认包时,删除该条目,服务器进入ESTABLISHED状态。
    该队列为SYN 队列,长度为 max(64, /proc/sys/net/ipv4/tcp_max_syn_backlog) ,在机器的tcp_max_syn_backlog值在/proc/sys/net/ipv4/tcp_max_syn_backlog下配置。

    server端的完全连接队列(accpet队列)

    当第三次握手时,当server接收到ACK 报之后, 会进入一个新的叫 accept 的队列,该队列的长度为 min(backlog, somaxconn),默认情况下,somaxconn 的值为 128,表示最多有 129 的 ESTAB 的连接等待 accept(),而 backlog 的值则应该是由 int listen(int sockfd, int backlog) 中的第二个参数指定,listen 里面的 backlog 可以有我们的应用程序去定义的。

    当Client发送SYN包之后挂了(syn flood攻击)

    Client发送SYN包给Server后挂了,Server回给Client的SYN-ACK一直没收到Client的ACK确认,这个时候这个连接既没建立起来,也不能算失败。这就需要一个超时时间让Server将这个连接断开,否则这个连接就会一直占用Server的SYN连接队列中的一个位置,大量这样的连接就会将Server的SYN连接队列耗尽,让正常的连接无法得到处理。

    目前,Linux下默认会进行5次重发SYN-ACK包,重试的间隔时间从1s开始,下次的重试间隔时间是前一次的双倍,5次的重试时间间隔为1s, 2s, 4s, 8s, 16s,总共31s,第5次发出后还要等32s都知道第5次也超时了,所以,总共需要 1s + 2s + 4s+ 8s+ 16s + 32s = 63s,TCP才会把断开这个连接。由于,SYN超时需要63秒,那么就给攻击者一个攻击服务器的机会,攻击者在短时间内发送大量的SYN包给Server(俗称 SYN flood 攻击),用于耗尽Server的SYN队列。对于应对SYN 过多的问题,linux提供了几个TCP参数:tcp_syncookies、tcp_synack_retries、tcp_max_syn_backlog、tcp_abort_on_overflow 来调整应对。

    net.ipv4.tcp_synack_retries #内核放弃连接之前发送SYN+ACK包的数量
    net.ipv4.tcp_syn_retries #内核放弃建立连接之前发送SYN包的数量

    为了应对SYNflooding(即客户端只发送SYN包发起握手而不回应ACK完成连接建立,填满server端的半连接队列,让它无法处理正常的握手请求),Linux实现了一种称为SYNcookie的机制,通过net.ipv4.tcp_syncookies控制,设置为1表示开启。简单说SYNcookie就是将连接信息编码在ISN(initialsequencenumber)中返回给客户端,这时server不需要将半连接保存在队列中,而是利用客户端随后发来的ACK带回的ISN还原连接信息,以完成连接的建立,避免了半连接队列被攻击SYN包填满。

    当syn队列满的情况(tcp_abort_on_overflow)

    对于SYN半连接队列的大小是由(/proc/sys/net/ipv4/tcp_max_syn_backlog)这个内核参数控制的,有些内核似乎也受listen的backlog参数影响,取得是两个值的最小值。当这个队列满了,不开启syncookies的时候,Server会丢弃新来的SYN包,而Client端在多次重发SYN包得不到响应而返回(connection time out)错误。但是,当Server端开启了syncookies=1,那么SYN半连接队列就没有逻辑上的最大值了,并且/proc/sys/net/ipv4/tcp_max_syn_backlog设置的值也会被忽略。

    Client端在多次重发SYN包得不到响应而返回connection time out错误

    查看

    netstat -s | grep LISTEN
    4375 SYNs to LISTEN sockets dropped

    当accept队列满的情况

    当accept队列满了之后,即使client继续向server发送ACK的包,也会不被响应,此时ListenOverflows+1,同时server通过/proc/sys/net/ipv4/tcp_abort_on_overflow来决定如何返回,0表示直接丢弃该ACK,1表示发送RST通知client;相应的,client则会分别返回read timeout 或者 connection reset by peer

    client则会分别返回read timeout 或者 connection reset by peer

    查看

    root@b5dbe93bcb04:/opt# netstat -s | grep listen
    22438 times the listen queue of a socket overflowed

    accept队列满了,对 syn队列也有影响,在代码 net/ipv4/tcp_ipv4.c :

    int tcp_v4_conn_request(struct sock *sk, struct sk_buff *skb)
    {
        /*tcp_syncookies为2 进行syn cookie
          tcp_syncookies为1 且request队列满了 进行syn cookie处理
          tcp_syncookies为0 且request队列满了 将该syn报文drop掉
        */
        if ((sysctl_tcp_syncookies == 2 ||
             inet_csk_reqsk_queue_is_full(sk)) && !isn) {
            want_cookie = tcp_syn_flood_action(sk, skb, "TCP");
            if (!want_cookie)
                goto drop;
        }
    
        /* Accept backlog is full. If we have already queued enough
         * of warm entries in syn queue, drop request. 
         */
        if (sk_acceptq_is_full(sk) && inet_csk_reqsk_queue_young(sk) > 1) {
            NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_LISTENOVERFLOWS);
            goto drop;
    }

    accept队列大多数情况下会比较小,所以会出现SYN 队列没有满,而ACCEPT 队列满了的情况,此时会按照tcp_aborton_overflow来决定直接丢弃,还是返回拒绝RST。 而如果启用了syncookies,那么syncookies会开启,限制SYN包进入的速度。

    当系统丢弃最后的 ACK,而系统中还有一个 net.ipv4.tcp_synack_retries 设置时,Linux 会重新发送 SYN ACK 包。而客户端收到多个 SYN ACK 包,则会认为之前的 ACK 丢包了。于是促使客户端再次发送 ACK ,在 accept队列有空闲的时候最终完成连接。若 accept队列始终满员,则最终客户端收到 RST 包。

    btw 

    当服务器端发送SYN+ACK给客户端时,服务器端可能还处于半连接状态,没有创建描述连接的sock结构,但是我们知道客户端在接收到服务器端的SYN+ACK后,按照三次握手过程中的状态迁移这时会从SYN_SENT状态变为ESTABLISHED状态。所以在连接队列已满的情况下,客户端会在连接尚未完成的时候误认为连接已经建立,如果在这种情况下发送数据到服务器端是没有办法处理的。这种情况即使调用getsockopt()来检查SO_ERROR选项也是检测不到的。假设客户端在接收到第一个SYN+ACK包后,就发送数据给服务器段,服务器端并没有建立连接。当数据包传送到TCP层的接收函数tcp_v4_rcv()中处理时,因为没有找到sock实例,会直接丢掉数据包。但是在客户端调用write()发送数据时,将要发送的数据拷贝到内核缓冲区后就会返回成功,客户端依然发现不了连接其实尚未完全建立。当write返回后,TCP协议栈将数据发送到服务器端时不会受到ACK包,只能重传。因为服务器段不存在这个连接,即使重传无数次也没有用,当然服务器端的协议栈也不能允许客户端无限制地重复这样的过程,最后会以服务器端发送的RST包彻底结束这个没有正确建立的“连接”。也就是说在这种极限情况下,TCP协议的可靠性没法保证。

  • 相关阅读:
    HTML语义化
    OKAY take it away `electron-builder`一直提示这个
    gitbash选中不了自己想要的选择
    vue挂载
    vue关闭eslint
    第二天-5大浏览器内核和浏览器的组成
    第一天-JavaScript简介与历史
    bootstrap模态框遇到做复制的功能失效
    对象的key【键】和分别获取数组的key【键】和值
    AngularJS教程
  • 原文地址:https://www.cnblogs.com/sanmubai/p/6698745.html
Copyright © 2011-2022 走看看