メモのLinux 2.6.32カーネルSYN flooding警告メッセージ

13404 ワード

メモのLinux 2.6.32カーネルSYN flooding警告メッセージ


前言


新しく申請したサーバーカーネルは2.6.32で、元のTCPサーバーは直接新しいカーネルのLinxuサーバーの上で運行して、dmesgコマンドを実行して、大量のSYN flooding警告を見ることができます:
possible SYN flooding on port 8080. Sending cookies.
従来の2.6.18カーネルのパラメータは2.6.32カーネルバージョンの場合、単純に「net.ipv 4.tcp_max_syn_backlog」を調整しても機能しません.
どうすればいいですか.2.6.32ソースをもう一度読むしかありません.以下です.
最後に小さな結び目に直接結論があり、焦っているあなたは直接読んでまとめることができます.

linuxカーネル2.6.32 backlog値分析について


net/Socket.c:
SYSCALL_DEFINE2(listen, int, fd, int, backlog)
{
    struct socket *sock;
    int err, fput_needed;
    int somaxconn;

    sock = sockfd_lookup_light(fd, &err, &fput_needed);
    if (sock) {
        somaxconn = sock_net(sock->sk)->core.sysctl_somaxconn;
        if ((unsigned)backlog > somaxconn)
            backlog = somaxconn;

        err = security_socket_listen(sock, backlog);
        if (!err)
            err = sock->ops->listen(sock, backlog);

        fput_light(sock->file, fput_needed);
    }
    return err;
}

net/ipv4/Af_inet.c:
/*
 *  Move a socket into listening state.
 */
int inet_listen(struct socket *sock, int backlog)
{
    struct sock *sk = sock->sk;
    unsigned char old_state;
    int err;

    lock_sock(sk);

    err = -EINVAL;
    if (sock->state != SS_UNCONNECTED || sock->type != SOCK_STREAM)
        goto out;

    old_state = sk->sk_state;
    if (!((1 << old_state) & (TCPF_CLOSE | TCPF_LISTEN)))
        goto out;

    /* Really, if the socket is already in listen state
     * we can only allow the backlog to be adjusted.
     */
    if (old_state != TCP_LISTEN) {
        err = inet_csk_listen_start(sk, backlog);
        if (err)
            goto out;
    }
    sk->sk_max_ack_backlog = backlog;
    err = 0;

out:
    release_sock(sk);
    return err;
}

inet_Listen呼び出しinet_csk_listen_start関数は、入力されたbacklogパラメータが頭を変えて面を変え、修正不可能な定数nr_になります.table_entriesです.
net/ipv4/Inet_connection_sock.c:
int inet_csk_listen_start(struct sock *sk, const int nr_table_entries)
{
    struct inet_sock *inet = inet_sk(sk);
    struct inet_connection_sock *icsk = inet_csk(sk);
    int rc = reqsk_queue_alloc(&icsk->icsk_accept_queue, nr_table_entries);

    if (rc != 0)
        return rc;

    sk->sk_max_ack_backlog = 0;
    sk->sk_ack_backlog = 0;
    inet_csk_delack_init(sk);

    /* There is race window here: we announce ourselves listening,
     * but this transition is still not validated by get_port().
     * It is OK, because this socket enters to hash table only
     * after validation is complete.
     */
    sk->sk_state = TCP_LISTEN;
    if (!sk->sk_prot->get_port(sk, inet->num)) {
        inet->sport = htons(inet->num);

        sk_dst_reset(sk);
        sk->sk_prot->hash(sk);

        return 0;
    }

    sk->sk_state = TCP_CLOSE;
    __reqsk_queue_destroy(&icsk->icsk_accept_queue);
    return -EADDRINUSE;
}

以下の処理はTCP SYN_RECV状態の接続は,握手段階にあり,半接続ともいえる場合,接続側の3回目の握手を待っている.
/*
 * Maximum number of SYN_RECV sockets in queue per LISTEN socket.
 * One SYN_RECV socket costs about 80bytes on a 32bit machine.
 * It would be better to replace it with a global counter for all sockets
 * but then some measure against one socket starving all other sockets
 * would be needed.
 *
 * It was 128 by default. Experiments with real servers show, that
 * it is absolutely not enough even at 100conn/sec. 256 cures most
 * of problems. This value is adjusted to 128 for very small machines
 * (<=32Mb of memory) and to 1024 on normal or better ones (>=256Mb).
 * Note : Dont forget somaxconn that may limit backlog too.
 */
int reqsk_queue_alloc(struct request_sock_queue *queue,
              unsigned int nr_table_entries)
{
    size_t lopt_size = sizeof(struct listen_sock);
    struct listen_sock *lopt;
    nr_table_entries = min_t(u32, nr_table_entries, sysctl_max_syn_backlog);
    nr_table_entries = max_t(u32, nr_table_entries, 8);
    nr_table_entries = roundup_pow_of_two(nr_table_entries + 1);
    lopt_size += nr_table_entries * sizeof(struct request_sock *); 
    if (lopt_size > PAGE_SIZE)
        lopt = __vmalloc(lopt_size,
            GFP_KERNEL | __GFP_HIGHMEM | __GFP_ZERO,
            PAGE_KERNEL);
    else
        lopt = kzalloc(lopt_size, GFP_KERNEL);
    if (lopt == NULL)
        return -ENOMEM;

    for (lopt->max_qlen_log = 3;
         (1 << lopt->max_qlen_log) < nr_table_entries;
         lopt->max_qlen_log++);

    get_random_bytes(&lopt->hash_rnd, sizeof(lopt->hash_rnd));
    rwlock_init(&queue->syn_wait_lock);
    queue->rskq_accept_head = NULL;
    lopt->nr_table_entries = nr_table_entries;

    write_lock_bh(&queue->syn_wait_lock);
    queue->listen_opt = lopt;
    write_unlock_bh(&queue->syn_wait_lock);

    return 0;
}

肝心なのはnr_table_entries変数、reqsk_queue_alloc関数におけるnr_table_entriesは符号なし変数になり,修正可能で変化が制限される.
たとえば、実際のカーネルパラメータの値は次のとおりです.
net.ipv4.tcp_max_syn_backlog = 65535
受信されたbacklog(net.core.somaxconn=65535以下)は8102であり、
//  listen backlog sysctl_max_syn_backlog , 8102
nr_table_entries = min_t(u32, nr_table_entries, sysctl_max_syn_backlog);
//  nr_table_entries 8 , 8102
nr_table_entries = max_t(u32, nr_table_entries, 8);
//   nr_table_entries*2, 8102*2=16204
nr_table_entries = roundup_pow_of_two(nr_table_entries + 1);

 ,max_qlen_log = 14

2.6.18カーネルのmax_qlen_logの計算方法

for (lopt->max_qlen_log = 6;
     (1 << lopt->max_qlen_log) < sysctl_max_syn_backlog;
     lopt->max_qlen_log++);
  • 明らかにsysctl_max_syn_backlogは演算に関与し、sysctl_max_syn_backlog値が大きいとmax_qlen_log値の相対比も大きい
  • sysctl_max_syn_backlog=65535でmax_qlen_log=16
  • 2.6.18カーネルの半接続長さは2^16=65536
  • である.
    リストとしてsock構造は、処理が必要な処理半接続のキュー要素の個数をnr_と定義するtable_entries、この例では16204の長さです.
    /** struct listen_sock - listen state
     *
     * @max_qlen_log - log_2 of maximal queued SYNs/REQUESTs
     */
    struct listen_sock {
        u8          max_qlen_log;
        /* 3 bytes hole, try to use */
        int         qlen;
        int         qlen_young;
        int         clock_hand;
        u32         hash_rnd;
        u32         nr_table_entries;
        struct request_sock *syn_table[0];
    };
    

    説明したとおり、2^max_qlen_log=半接続キュー長qlen値.
    レポートSYN floodingの関数を振り返ってみましょう.
    net/ipv4/Tcp_ipv4.c
    #ifdef CONFIG_SYN_COOKIES
    static void syn_flood_warning(struct sk_buff *skb)
    {
        static unsigned long warntime;
    
        if (time_after(jiffies, (warntime + HZ * 60))) {
            warntime = jiffies;
            printk(KERN_INFO
                   "possible SYN flooding on port %d. Sending cookies.
    ", ntohs(tcp_hdr(skb)->dest)); } } #endif

    呼び出された場所では、いくつかのコードが簡略化されています.
    int tcp_v4_conn_request(struct sock *sk, struct sk_buff *skb)
    {
    ......
    #ifdef CONFIG_SYN_COOKIES
        int want_cookie = 0;
    #else
    #define want_cookie 0 /* Argh, why doesn't gcc optimize this :( */
    #endif
        ......
        /* TW buckets are converted to open requests without
         * limitations, they conserve resources and peer is
         * evidently real one.
         */
         //   && !0
        if (inet_csk_reqsk_queue_is_full(sk) && !isn) {
    #ifdef CONFIG_SYN_COOKIES
            if (sysctl_tcp_syncookies) {
                want_cookie = 1;
            } else
    #endif
            goto drop;
        }
    
        /* Accept backlog is full. If we have already queued enough
         * of warm entries in syn queue, drop request. It is better than
         * clogging syn queue with openreqs with exponentially increasing
         * timeout.
         */
        if (sk_acceptq_is_full(sk) && inet_csk_reqsk_queue_young(sk) > 1)
            goto drop;
    
        req = inet_reqsk_alloc(&tcp_request_sock_ops);
        if (!req)
            goto drop;
    
        ......
    
        if (!want_cookie)
            TCP_ECN_create_request(req, tcp_hdr(skb));
    
        if (want_cookie) {
    #ifdef CONFIG_SYN_COOKIES
            syn_flood_warning(skb);
            req->cookie_ts = tmp_opt.tstamp_ok;
    #endif
            isn = cookie_v4_init_sequence(sk, skb, &req->mss);
        } else if (!isn) {
            ......
        }       
        ......
    }
    

    半接続キューが満たされている関数を判断するには、アルゴリズムを参照してください.
    include/net/Inet_connection_sock.h:
    static inline int inet_csk_reqsk_queue_is_full(const struct sock *sk)
    {
        return reqsk_queue_is_full(&inet_csk(sk)->icsk_accept_queue);
    }
    

    include/net/Rquest_sock.h:
    static inline int reqsk_queue_is_full(const struct request_sock_queue *queue)
    {
        //  max_qlen_log 
        return queue->listen_opt->qlen >> queue->listen_opt->max_qlen_log;
    }
    

    1を返します.半接続キューがいっぱいであることを示します.
    以上は,半接続キューが満たされているという判断条件を解析しただけで,要するにアプリケーションからのbacklogは重要であり,値が小さすぎると容易に得られる.
    somaxconn=128の場合sysctl_max_syn_backlog=4096、backlog=511で最終nr_table_entries = 256,max_qlen_log = 8.では、256個を超える半接続のキューは、257>>8=1で、キューがいっぱいです.
    backlogを設定するには、listenメソッドを呼び出す必要がある特定のアプリケーションと組み合わせる必要があります.

    Netty backlog処理


    Tcp ServerはNetty 3.7バージョンを使用しています.バージョンは低く、backlogを処理しています.backlog値を手動で指定しないと、JDK 1.6は50と考えています.
    証明書は以下の通りです:java.net.ServerSocket:
    public void bind(SocketAddress endpoint, int backlog) throws IOException {
        if (isClosed())
            throw new SocketException("Socket is closed");
        if (!oldImpl && isBound())
            throw new SocketException("Already bound");
        if (endpoint == null)
            endpoint = new InetSocketAddress(0);
        if (!(endpoint instanceof InetSocketAddress))
            throw new IllegalArgumentException("Unsupported address type");
        InetSocketAddress epoint = (InetSocketAddress) endpoint;
        if (epoint.isUnresolved())
            throw new SocketException("Unresolved address");
        if (backlog < 1)
          backlog = 50;
        try {
            SecurityManager security = System.getSecurityManager();
            if (security != null)
            security.checkListen(epoint.getPort());
            getImpl().bind(epoint.getAddress(), epoint.getPort());
            getImpl().listen(backlog);
            bound = true;
        } catch(SecurityException e) {
            bound = false;
            throw e;
        } catch(IOException e) {
            bound = false;
            throw e;
        }
    }
    

    Nettyでbacklogを処理する場所:
    org/jboss/netty/channel/socket/DefaultServerSocketChannelConfig.java:
    @Override
    public boolean setOption(String key, Object value) {
        if (super.setOption(key, value)) {
            return true;
        }
    
        if ("receiveBufferSize".equals(key)) {
            setReceiveBufferSize(ConversionUtil.toInt(value));
        } else if ("reuseAddress".equals(key)) {
            setReuseAddress(ConversionUtil.toBoolean(value));
        } else if ("backlog".equals(key)) {
            setBacklog(ConversionUtil.toInt(value));
        } else {
            return false;
        }
        return true;
    }
    

    backlog値を手動で指定する必要がある場合は、次のようにします.
    bootstrap.setOption("backlog", 8102); //  , net.core.somaxconn , 
    

    Netty 4.0に比べて、スマートでないものもあります.参考にしてください.http://www.blogjava.net/yongboy/archive/2014/07/30/416373.html

    小結


    linuxカーネル2.6.32で、SYN flooding攻撃を受けていない場合は、適切に調整できます.
    sysctl -w net.core.somaxconn=32768
    sysctl -w net.ipv4.tcp_max_syn_backlog=65535
    sysctl -p
    また、TCPサーバのlistenインタフェースからのbacklog値を変更することを忘れないでください.設定しないか、小さすぎると、SYN floodingの警告情報をもたらす可能性があります.最初は1024に設定して、しばらく観察して実際の状況に応じてゆっくりと引き上げてみましょう.
    どのように設定しても、最終backlog値の範囲は次のとおりです.
    backlog <= net.core.somaxconn
    半接続キューの長さは、次のとおりです.
    半接続キュー長≒2*min(backlog,net.ipv 4.tcpmax_syn_backlog)
    また、SYN floodingが発生した場合、このときTCP SYN_RECV数は、半接続キューがいっぱいであることを示しています.
    ss -ant | awk 'NR>1 {++s[$1]} END {for(k in s) print k,s[k]}'
    

    運維書坤の若者が提供したほうがいいので、コマンドを見てください.