]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
DOC: add doc/linux-syn-cookies.txt
authorWilly Tarreau <w@1wt.eu>
Wed, 20 Nov 2013 14:02:38 +0000 (15:02 +0100)
committerWilly Tarreau <w@1wt.eu>
Tue, 11 Aug 2015 10:17:41 +0000 (12:17 +0200)
This is an analysis of how kernel 3.10 decides to trigger SYN cookies.

doc/linux-syn-cookies.txt [new file with mode: 0644]

diff --git a/doc/linux-syn-cookies.txt b/doc/linux-syn-cookies.txt
new file mode 100644 (file)
index 0000000..ca13066
--- /dev/null
@@ -0,0 +1,106 @@
+SYN cookie analysis on 3.10
+
+include/net/request_sock.h:
+
+static inline int reqsk_queue_is_full(const struct request_sock_queue *queue)
+{
+       return queue->listen_opt->qlen >> queue->listen_opt->max_qlen_log;
+}
+
+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);
+}
+
+max_qlen_log is computed to equal log2(min(min(listen_backlog,somaxconn), sysctl_max_syn_backlog),
+and this is done this way following this path :
+
+ socket.c:listen(fd, backlog) :
+
+   backlog = min(backlog, somaxconn)
+   => af_inet.c:inet_listen(sock, backlog)
+
+     => inet_connection_sock.c:inet_csk_listen_start(sk, backlog)
+
+       sk_max_ack_backlog = backlog
+       => request_sock.c:reqsk_queue_alloc(sk, backlog (=nr_table_entries))
+
+         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);
+         for (lopt->max_qlen_log = 3;
+              (1 << lopt->max_qlen_log) < nr_table_entries;
+              lopt->max_qlen_log++);
+
+
+tcp_ipv4.c:tcp_v4_conn_request()
+   - inet_csk_reqsk_queue_is_full() returns true when the listening socket's
+     qlen is larger than 1 << max_qlen_log, so basically qlen >= min(backlog,max_backlog)
+
+   - tcp_syn_flood_action() returns true when sysctl_tcp_syncookies is set. It
+     also emits a warning once per listening socket when activating the feature.
+
+       if (inet_csk_reqsk_queue_is_full(sk) && !isn) {
+               want_cookie = tcp_syn_flood_action(sk, skb, "TCP");
+               if (!want_cookie)
+                       goto drop;
+       }
+
+   => when the socket's current backlog is >= min(backlog,max_backlog),
+      either tcp_syn_cookies is set so we set want_cookie to 1, or we 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.
+        */
+
+sock.h:sk_acceptq_is_full() = sk_ack_backlog > sk_max_ack_backlog
+                            = sk_ack_backlog > min(somaxconn, listen_backlog)
+
+       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;
+       }
+
+====> the following algorithm is applied in the reverse order but with these
+      priorities :
+
+      1) IF socket's accept queue >= min(somaxconn, listen_backlog) THEN drop
+
+      2) IF socket's SYN backlog < min(somaxconn, listen_backlog, tcp_max_syn_backlog) THEN accept
+
+      3) IF tcp_syn_cookies THEN send_syn_cookie
+
+      4) otherwise drop
+
+====> the problem is the accept queue being filled, but it's supposed to be
+      filled only with validated client requests (step 1).
+
+
+
+       req = inet_reqsk_alloc(&tcp_request_sock_ops);
+       if (!req)
+               goto drop;
+
+       ...
+               if (!sysctl_tcp_syncookies &&
+                        (sysctl_max_syn_backlog - inet_csk_reqsk_queue_len(sk) <
+                         (sysctl_max_syn_backlog >> 2)) &&
+                        !tcp_peer_is_proven(req, dst, false)) {
+                       /* Without syncookies last quarter of
+                        * backlog is filled with destinations,
+                        * proven to be alive.
+                        * It means that we continue to communicate
+                        * to destinations, already remembered
+                        * to the moment of synflood.
+                        */
+                       LIMIT_NETDEBUG(KERN_DEBUG pr_fmt("drop open request from %pI4/%u\n"),
+                                      &saddr, ntohs(tcp_hdr(skb)->source));
+                       goto drop_and_release;
+               }
+
+