
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/">
    <channel>
        <title><![CDATA[ The Cloudflare Blog ]]></title>
        <description><![CDATA[ Get the latest news on how products at Cloudflare are built, technologies used, and join the teams helping to build a better Internet. ]]></description>
        <link>https://blog.cloudflare.com</link>
        <atom:link href="https://blog.cloudflare.com/" rel="self" type="application/rss+xml"/>
        <language>en-us</language>
        <image>
            <url>https://blog.cloudflare.com/favicon.png</url>
            <title>The Cloudflare Blog</title>
            <link>https://blog.cloudflare.com</link>
        </image>
        <lastBuildDate>Tue, 14 Apr 2026 11:31:37 GMT</lastBuildDate>
        <item>
            <title><![CDATA[Mitigating a 754 Million PPS DDoS Attack Automatically]]></title>
            <link>https://blog.cloudflare.com/mitigating-a-754-million-pps-ddos-attack-automatically/</link>
            <pubDate>Thu, 09 Jul 2020 11:00:00 GMT</pubDate>
            <description><![CDATA[ On June 20, Cloudflare automatically mitigated a highly volumetric DDoS attack that peaked above 754 million packets per second. The attack was part of an organized four day campaign starting on June 18 and ending on June 21. ]]></description>
            <content:encoded><![CDATA[ <p></p><p>On June 21, Cloudflare automatically mitigated a highly volumetric DDoS attack that peaked at 754 million packets per second. The attack was part of an organized four day campaign starting on June 18 and ending on June 21: attack traffic was sent from over 316,000 IP addresses towards a single Cloudflare IP address that was mostly used for websites on our <a href="https://www.cloudflare.com/plans/free/">Free plan</a>. No downtime or service degradation was reported during the attack, and no charges accrued to customers due to our <a href="/unmetered-mitigation/">unmetered mitigation guarantee</a>.</p><p>The attack was detected and handled automatically by <a href="/meet-gatebot-a-bot-that-allows-us-to-sleep/">Gatebot</a>, our global DDoS detection and mitigation system without any manual intervention by our teams. Notably, because our automated systems were able to mitigate the attack without issue, no alerts or pages were sent to our on-call teams and no humans were involved at all.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/5n37ZlU9nwh2KGUW5Hwctl/d1ec160e2ac256b5ededf7e4eff6842f/image2-3.png" />
            
            </figure><p>Attack Snapshot - Peaking at 754 Mpps. The two different colors in the graph represent two separate systems dropping packets. </p><p>During those four days, the attack utilized a combination of three attack vectors over the <a href="https://www.cloudflare.com/learning/ddos/glossary/tcp-ip/">TCP</a> protocol: <a href="https://www.cloudflare.com/learning/ddos/syn-flood-ddos-attack/">SYN floods</a>, <a href="https://www.cloudflare.com/learning/ddos/what-is-an-ack-flood/">ACK floods</a> and SYN-ACK floods. The attack campaign sustained for multiple hours at rates exceeding 400-600 million packets per second and peaked multiple times above 700 million packets per second, with a top peak of 754 million packets per second. Despite the high and sustained packet rates, our edge continued serving our customers during the attack without impacting performance at all</p>
    <div>
      <h3>The Three Types of DDoS: Bits, Packets &amp; Requests</h3>
      <a href="#the-three-types-of-ddos-bits-packets-requests">
        
      </a>
    </div>
    <p>Attacks with high bits per second rates aim to saturate the Internet link by sending more bandwidth per second than the link can handle. Mitigating a bit-intensive flood is similar to a dam blocking gushing water in a canal with limited capacity, allowing just a portion through.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/4c5HPaNGni1tyvuJx67mnk/bbe3d855e355eded524119e6c82d1dab/GBPS-river--copy_2x.png" />
            
            </figure><p>Bit Intensive DDoS Attacks as a Gushing River Blocked By Gatebot</p><p>In such cases, the Internet service provider may block or throttle the traffic above the allowance resulting in denial of service for legitimate users that are trying to connect to the website but are blocked by the service provider. In other cases, the link is simply saturated and everything behind that connection is offline.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/5HRDL05jkCa9jJfvbbPfe3/a61b1019bab1fcf7cf7b8b040ec3b72a/MBPS-Mosquitos.png" />
            
            </figure><p>Swarm of Mosquitoes as a Packet Intensive DDoS Attack</p><p>However in this DDoS campaign, the attack peaked at a mere 250 Gbps (I say, mere, but ¼ Tbps is enough to knock pretty much anything offline if it isn’t behind some DDoS mitigation service) so it does not seem as the attacker intended to saturate our Internet links, perhaps because they know that our global capacity exceeds 37 Tbps. Instead, it appears the attacker attempted (and failed) to overwhelm our routers and data center appliances with high packet rates reaching 754 million packets per second. As opposed to water rushing towards a dam, flood of packets can be thought of as a swarm of millions of mosquitoes that you need to zap one by one.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/2CT7G4c2uLASjHOw6GWuXM/00e5c2ec980ffcd802a5a38b8bcd4932/gatebot-zapper_2x.png" />
            
            </figure><p>Zapping Mosquitoes with Gatebot</p><p>Depending on the ‘weakest link’ in a data center, a packet intensive DDoS attack may impact the routers, switches, web servers, firewalls, DDoS mitigation devices or any other appliance that is used in-line. Typically, a high packet rate may cause the memory buffer to overflow and thus voiding the router’s ability to process additional packets. This is because there’s a small fixed CPU cost of handing each packet and so if you can send a lot of small packets you can block an Internet connection not by filling it but by causing the hardware that handles the connection to be overwhelmed with processing.</p><p>Another form of DDoS attack is one with a high HTTP request per second rate. An HTTP request intensive DDoS attack aims to overwhelm a web server’s resources with more HTTP requests per second than the server can handle. The goal of a DDoS attack with a high request per second rate is to max out the CPU and memory utilization of the server in order to crash it or prevent it from being able to respond to legitimate requests. Request intensive DDoS attacks allow the attacker to generate much less bandwidth, as opposed to bit intensive attacks, and still cause a denial of service.</p>
    <div>
      <h3>Automated DDoS Detection &amp; Mitigation</h3>
      <a href="#automated-ddos-detection-mitigation">
        
      </a>
    </div>
    <p>So how did we handle 754 million packets per second? First, Cloudflare’s network utilizes BGP Anycast to spread attack traffic globally across our fleet of data centers. Second, we built our own <a href="https://www.cloudflare.com/ddos/">DDoS protection</a> systems, Gatebot and dosd, which <a href="/l4drop-xdp-ebpf-based-ddos-mitigations/">drop packets inside the Linux kernel</a> for maximum efficiency in order to handle massive floods of packets. And third, we built our own L4 load-balancer, Unimog, which uses our appliances' health and other various metrics to load-balance traffic intelligently within a data center.</p><p>In 2017, we published a blog introducing Gatebot, one of our two DDoS protection systems. The blog was titled <a href="/meet-gatebot-a-bot-that-allows-us-to-sleep/">Meet Gatebot - a bot that allows us to sleep</a>, and that’s exactly what happened during this attack. The <a href="https://www.cloudflare.com/learning/security/what-is-an-attack-surface/">attack surface</a> was spread out globally by our Anycast, then Gatebot detected and mitigated the attack automatically without human intervention. And traffic inside each datacenter was load-balanced intelligently to avoid overwhelming any one machine. And as promised in the blog title, the attack peak did in fact occur while our London team was asleep.</p><p>So how does Gatebot work? Gatebot asynchronously samples traffic from every one of our data centers in over 200 locations around the world. It also monitors our <a href="/rolling-with-the-punches-shifting-attack-tactics-dropping-packets-faster-cheaper-at-the-edge/">customers’ origin server health</a>. It then analyzes the samples to identify patterns and traffic anomalies that can indicate attacks. Once an attack is detected, Gatebot sends mitigation instructions to the edge data centers.</p><p>To complement Gatebot, last year we released a new system codenamed dosd (denial of service daemon) which runs in every one of our data centers around the world in over 200 cities. Similarly to Gatebot, dosd detects and mitigates attacks autonomously but in the scope of a single server or data center. You can read more about dosd in our <a href="/rolling-with-the-punches-shifting-attack-tactics-dropping-packets-faster-cheaper-at-the-edge/">recent blog</a>.</p>
    <div>
      <h3>The DDoS Landscape</h3>
      <a href="#the-ddos-landscape">
        
      </a>
    </div>
    <p>While in recent months we’ve observed a <a href="/network-layer-ddos-attack-trends-for-q1-2020/">decrease in the size and duration of DDoS attacks</a>, highly volumetric and globally distributed DDoS attacks such as this one still persist. Regardless of the size, type or sophistication of the attack, Cloudflare offers <a href="/unmetered-mitigation/">unmetered DDoS protection</a> to all customers and plan levels—including the Free plans.</p> ]]></content:encoded>
            <category><![CDATA[DDoS]]></category>
            <category><![CDATA[Security]]></category>
            <category><![CDATA[Gatebot]]></category>
            <category><![CDATA[Attacks]]></category>
            <category><![CDATA[SYN]]></category>
            <guid isPermaLink="false">4PrPrO1u2DnN6fu86Hbko9</guid>
            <dc:creator>Omer Yoachimik</dc:creator>
        </item>
        <item>
            <title><![CDATA[When TCP sockets refuse to die]]></title>
            <link>https://blog.cloudflare.com/when-tcp-sockets-refuse-to-die/</link>
            <pubDate>Fri, 20 Sep 2019 15:53:33 GMT</pubDate>
            <description><![CDATA[ We noticed something weird - the TCP sockets which we thought should have been closed - were lingering around. We realized we don't really understand when TCP sockets are supposed to time out!

We naively thought enabling TCP keepalives would be enough... but it isn't! ]]></description>
            <content:encoded><![CDATA[ <p>While working on our <a href="https://www.cloudflare.com/products/cloudflare-spectrum/">Spectrum server</a>, we noticed something weird: the TCP sockets which we thought should have been closed were lingering around. We realized we don't really understand when TCP sockets are supposed to time out!</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/4J7NyByY5rLMwGCjildxuX/a80e344de39529860fe89230fff4259c/Tcp_state_diagram_fixed_new.svga.png" />
            
            </figure><p><a href="https://commons.wikimedia.org/wiki/File:Tcp_state_diagram_fixed_new.svg">Image</a> by Sergiodc2 CC BY SA 3.0</p><p>In our code, we wanted to make sure we don't hold connections to dead hosts. In our early code we naively thought enabling TCP keepalives would be enough... but it isn't. It turns out a fairly modern <a href="https://tools.ietf.org/html/rfc5482">TCP_USER_TIMEOUT</a> socket option is equally important. Furthermore, it interacts with TCP keepalives in subtle ways. <a href="http://codearcana.com/posts/2015/08/28/tcp-keepalive-is-a-lie.html">Many people</a> are confused by this.</p><p>In this blog post, we'll try to show how these options work. We'll show how a TCP socket can time out during various stages of its lifetime, and how TCP keepalives and user timeout influence that. To better illustrate the internals of TCP connections, we'll mix the outputs of the <code>tcpdump</code> and the <code>ss -o</code> commands. This nicely shows the transmitted packets and the changing parameters of the TCP connections.</p>
    <div>
      <h2>SYN-SENT</h2>
      <a href="#syn-sent">
        
      </a>
    </div>
    <p>Let's start from the simplest case - what happens when one attempts to establish a connection to a server which discards inbound SYN packets?</p><p>The scripts used here <a href="https://github.com/cloudflare/cloudflare-blog/tree/master/2019-09-tcp-keepalives">are available on our GitHub</a>.</p><p><code>$ sudo ./test-syn-sent.py
# all packets dropped
00:00.000 IP host.2 &gt; host.1: Flags [S] # initial SYN

State    Recv-Q Send-Q Local:Port Peer:Port
SYN-SENT 0      1      host:2     host:1    timer:(on,940ms,0)

00:01.028 IP host.2 &gt; host.1: Flags [S] # first retry
00:03.044 IP host.2 &gt; host.1: Flags [S] # second retry
00:07.236 IP host.2 &gt; host.1: Flags [S] # third retry
00:15.427 IP host.2 &gt; host.1: Flags [S] # fourth retry
00:31.560 IP host.2 &gt; host.1: Flags [S] # fifth retry
01:04.324 IP host.2 &gt; host.1: Flags [S] # sixth retry
02:10.000 connect ETIMEDOUT</code></p><p>Ok, this was easy. After the <code>connect()</code> syscall, the operating system sends a SYN packet. Since it didn't get any response the OS will by default retry sending it 6 times. This can be tweaked by the sysctl:</p><p><code>$ sysctl net.ipv4.tcp_syn_retries
net.ipv4.tcp_syn_retries = 6</code></p><p>It's possible to overwrite this setting per-socket with the TCP_SYNCNT setsockopt:</p><p><code>setsockopt(sd, IPPROTO_TCP, TCP_SYNCNT, 6);</code></p><p>The retries are staggered at 1s, 3s, 7s, 15s, 31s, 63s marks (the inter-retry time starts at 2s and then doubles each time). By default, the whole process takes 130 seconds, until the kernel gives up with the ETIMEDOUT errno. At this moment in the lifetime of a connection, SO_KEEPALIVE settings are ignored, but TCP_USER_TIMEOUT is not. For example, setting it to 5000ms, will cause the following interaction:</p><p><code>$ sudo ./test-syn-sent.py 5000
# all packets dropped
00:00.000 IP host.2 &gt; host.1: Flags [S] # initial SYN

State    Recv-Q Send-Q Local:Port Peer:Port
SYN-SENT 0      1      host:2     host:1    timer:(on,996ms,0)

00:01.016 IP host.2 &gt; host.1: Flags [S] # first retry
00:03.032 IP host.2 &gt; host.1: Flags [S] # second retry
00:05.016 IP host.2 &gt; host.1: Flags [S] # what is this?
00:05.024 IP host.2 &gt; host.1: Flags [S] # what is this?
00:05.036 IP host.2 &gt; host.1: Flags [S] # what is this?
00:05.044 IP host.2 &gt; host.1: Flags [S] # what is this?
00:05.050 connect ETIMEDOUT</code></p><p>Even though we set user-timeout to 5s, we still saw the six SYN retries on the wire. This behaviour is probably a bug (as tested on 5.2 kernel): we would expect only two retries to be sent - at 1s and 3s marks and the socket to expire at 5s mark. Instead, we saw this, but also we saw further 4 retransmitted SYN packets aligned to 5s mark - which makes no sense. Anyhow, we learned a thing - the TCP_USER_TIMEOUT does affect the behaviour of <code>connect()</code>.</p>
    <div>
      <h2>SYN-RECV</h2>
      <a href="#syn-recv">
        
      </a>
    </div>
    <p>SYN-RECV sockets are usually hidden from the application. They live as mini-sockets on the SYN queue. We wrote about <a href="/syn-packet-handling-in-the-wild/">the SYN and Accept queues in the past</a>. Sometimes, when SYN cookies are enabled, the sockets may skip the SYN-RECV state altogether.</p><p>In SYN-RECV state, the socket will retry sending SYN+ACK 5 times as controlled by:</p><p><code>$ sysctl net.ipv4.tcp_synack_retries
net.ipv4.tcp_synack_retries = 5</code></p><p>Here is how it looks on the wire:</p><p><code>$ sudo ./test-syn-recv.py
00:00.000 IP host.2 &gt; host.1: Flags [S]
# all subsequent packets dropped
00:00.000 IP host.1 &gt; host.2: Flags [S.] # initial SYN+ACK

State    Recv-Q Send-Q Local:Port Peer:Port
SYN-RECV 0      0      host:1     host:2    timer:(on,996ms,0)

00:01.033 IP host.1 &gt; host.2: Flags [S.] # first retry
00:03.045 IP host.1 &gt; host.2: Flags [S.] # second retry
00:07.301 IP host.1 &gt; host.2: Flags [S.] # third retry
00:15.493 IP host.1 &gt; host.2: Flags [S.] # fourth retry
00:31.621 IP host.1 &gt; host.2: Flags [S.] # fifth retry
01:04:610 SYN-RECV disappears</code></p><p>With default settings, the SYN+ACK is re-transmitted at 1s, 3s, 7s, 15s, 31s marks, and the SYN-RECV socket disappears at the 64s mark.</p><p>Neither SO_KEEPALIVE nor TCP_USER_TIMEOUT affect the lifetime of SYN-RECV sockets.</p>
    <div>
      <h2>Final handshake ACK</h2>
      <a href="#final-handshake-ack">
        
      </a>
    </div>
    <p>After receiving the second packet in the TCP handshake - the SYN+ACK - the client socket moves to an ESTABLISHED state. The server socket remains in SYN-RECV until it receives the final ACK packet.</p><p>Losing this ACK doesn't change anything - the server socket will just take a bit longer to move from SYN-RECV to ESTAB. Here is how it looks:</p><p><code>00:00.000 IP host.2 &gt; host.1: Flags [S]
00:00.000 IP host.1 &gt; host.2: Flags [S.]
00:00.000 IP host.2 &gt; host.1: Flags [.] # initial ACK, dropped

State    Recv-Q Send-Q Local:Port  Peer:Port
SYN-RECV 0      0      host:1      host:2 timer:(on,1sec,0)
ESTAB    0      0      host:2      host:1

00:01.014 IP host.1 &gt; host.2: Flags [S.]
00:01.014 IP host.2 &gt; host.1: Flags [.]  # retried ACK, dropped

State    Recv-Q Send-Q Local:Port Peer:Port
SYN-RECV 0      0      host:1     host:2    timer:(on,1.012ms,1)
ESTAB    0      0      host:2     host:1</code></p><p>As you can see SYN-RECV, has the "on" timer, the same as in example before. We might argue this final ACK doesn't really carry much weight. This thinking lead to the development of TCP_DEFER_ACCEPT feature - it basically causes the third ACK to be silently dropped. With this flag set the socket remains in SYN-RECV state until it receives the first packet with actual data:</p><p><code>$ sudo ./test-syn-ack.py
00:00.000 IP host.2 &gt; host.1: Flags [S]
00:00.000 IP host.1 &gt; host.2: Flags [S.]
00:00.000 IP host.2 &gt; host.1: Flags [.] # delivered, but the socket stays as SYN-RECV

State    Recv-Q Send-Q Local:Port Peer:Port
SYN-RECV 0      0      host:1     host:2    timer:(on,7.192ms,0)
ESTAB    0      0      host:2     host:1

00:08.020 IP host.2 &gt; host.1: Flags [P.], length 11  # payload moves the socket to ESTAB

State Recv-Q Send-Q Local:Port Peer:Port
ESTAB 11     0      host:1     host:2
ESTAB 0      0      host:2     host:1</code></p><p>The server socket remained in the SYN-RECV state even after receiving the final TCP-handshake ACK. It has a funny "on" timer, with the counter stuck at 0 retries. It is converted to ESTAB - and moved from the SYN to the accept queue - after the client sends a data packet or after the TCP_DEFER_ACCEPT timer expires. Basically, with DEFER ACCEPT the SYN-RECV mini-socket <a href="https://marc.info/?l=linux-netdev&amp;m=118793048828251&amp;w=2">discards the data-less inbound ACK</a>.</p>
    <div>
      <h2>Idle ESTAB is forever</h2>
      <a href="#idle-estab-is-forever">
        
      </a>
    </div>
    <p>Let's move on and discuss a fully-established socket connected to an unhealthy (dead) peer. After completion of the handshake, the sockets on both sides move to the ESTABLISHED state, like:</p><p><code>State Recv-Q Send-Q Local:Port Peer:Port
ESTAB 0      0      host:2     host:1
ESTAB 0      0      host:1     host:2</code></p><p>These sockets have no running timer by default - they will remain in that state forever, even if the communication is broken. The TCP stack will notice problems only when one side attempts to send something. This raises a question - what to do if you don't plan on sending any data over a connection? How do you make sure an idle connection is healthy, without sending any data over it?</p><p>This is where TCP keepalives come in. Let's see it in action - in this example we used the following toggles:</p><ul><li><p>SO_KEEPALIVE = 1 - Let's enable keepalives.</p></li><li><p>TCP_KEEPIDLE = 5 - Send first keepalive probe after 5 seconds of idleness.</p></li><li><p>TCP_KEEPINTVL = 3 - Send subsequent keepalive probes after 3 seconds.</p></li><li><p>TCP_KEEPCNT = 3 - Time out after three failed probes.</p></li></ul><p><code>$ sudo ./test-idle.py
00:00.000 IP host.2 &gt; host.1: Flags [S]
00:00.000 IP host.1 &gt; host.2: Flags [S.]
00:00.000 IP host.2 &gt; host.1: Flags [.]

State Recv-Q Send-Q Local:Port Peer:Port
ESTAB 0      0      host:1     host:2
ESTAB 0      0      host:2     host:1  timer:(keepalive,2.992ms,0)

# all subsequent packets dropped
00:05.083 IP host.2 &gt; host.1: Flags [.], ack 1 # first keepalive probe
00:08.155 IP host.2 &gt; host.1: Flags [.], ack 1 # second keepalive probe
00:11.231 IP host.2 &gt; host.1: Flags [.], ack 1 # third keepalive probe
00:14.299 IP host.2 &gt; host.1: Flags [R.], seq 1, ack 1</code></p><p>Indeed! We can clearly see the first probe sent at the 5s mark, two remaining probes 3s apart - exactly as we specified. After a total of three sent probes, and a further three seconds of delay, the connection dies with ETIMEDOUT, and final the RST is transmitted.</p><p>For keepalives to work, the send buffer must be empty. You can notice the keepalive timer active in the "timer:(keepalive)" line.</p>
    <div>
      <h2>Keepalives with TCP_USER_TIMEOUT are confusing</h2>
      <a href="#keepalives-with-tcp_user_timeout-are-confusing">
        
      </a>
    </div>
    <p>We mentioned the TCP_USER_TIMEOUT option before. It sets the maximum amount of time that transmitted data may remain unacknowledged before the kernel forcefully closes the connection. On its own, it doesn't do much in the case of idle connections. The sockets will remain ESTABLISHED even if the connectivity is dropped. However, this socket option does change the semantics of TCP keepalives. <a href="https://linux.die.net/man/7/tcp">The tcp(7) manpage</a> is somewhat confusing:</p><p><i>Moreover, when used with the TCP keepalive (SO_KEEPALIVE) option, TCP_USER_TIMEOUT will override keepalive to determine when to close a connection due to keepalive failure.</i></p><p>The original commit message has slightly more detail:</p><ul><li><p><a href="https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=dca43c75e7e545694a9dd6288553f55c53e2a3a3">tcp: Add TCP_USER_TIMEOUT socket option</a></p></li></ul><p>To understand the semantics, we need to look at the <a href="https://github.com/torvalds/linux/blob/b41dae061bbd722b9d7fa828f35d22035b218e18/net/ipv4/tcp_timer.c#L693-L697">kernel code in linux/net/ipv4/tcp_timer.c:693</a>:</p><p><code>if ((icsk-&gt;icsk_user_timeout != 0 &amp;&amp;
elapsed &gt;= msecs_to_jiffies(icsk-&gt;icsk_user_timeout) &amp;&amp;
icsk-&gt;icsk_probes_out &gt; 0) ||</code></p><p>For the user timeout to have any effect, the <code>icsk_probes_out</code> must not be zero. The check for user timeout is done only <i>after</i> the first probe went out. Let's check it out. Our connection settings:</p><ul><li><p>TCP_USER_TIMEOUT = 5*1000 - 5 seconds</p></li><li><p>SO_KEEPALIVE = 1 - enable keepalives</p></li><li><p>TCP_KEEPIDLE = 1 - send first probe quickly - 1 second idle</p></li><li><p>TCP_KEEPINTVL = 11 - subsequent probes every 11 seconds</p></li><li><p>TCP_KEEPCNT = 3 - send three probes before timing out</p></li></ul><p><code>00:00.000 IP host.2 &gt; host.1: Flags [S]
00:00.000 IP host.1 &gt; host.2: Flags [S.]
00:00.000 IP host.2 &gt; host.1: Flags [.]

# all subsequent packets dropped
00:01.001 IP host.2 &gt; host.1: Flags [.], ack 1 # first probe
00:12.233 IP host.2 &gt; host.1: Flags [R.] # timer for second probe fired, socket aborted due to TCP_USER_TIMEOUT</code></p><p>So what happened? The connection sent the first keepalive probe at the 1s mark. Seeing no response the TCP stack then woke up 11 seconds later to send a second probe. This time though, it executed the USER_TIMEOUT code path, which decided to terminate the connection immediately.</p><p>What if we bump TCP_USER_TIMEOUT to larger values, say between the second and third probe? Then, the connection will be closed on the third probe timer. With TCP_USER_TIMEOUT set to 12.5s:</p><p><code>00:01.022 IP host.2 &gt; host.1: Flags [.] # first probe
00:12.094 IP host.2 &gt; host.1: Flags [.] # second probe
00:23.102 IP host.2 &gt; host.1: Flags [R.] # timer for third probe fired, socket aborted due to TCP_USER_TIMEOUT</code></p><p>We’ve shown how TCP_USER_TIMEOUT interacts with keepalives for small and medium values. The last case is when TCP_USER_TIMEOUT is extraordinarily large. Say we set it to 30s:</p><p><code>00:01.027 IP host.2 &gt; host.1: Flags [.], ack 1 # first probe
00:12.195 IP host.2 &gt; host.1: Flags [.], ack 1 # second probe
00:23.207 IP host.2 &gt; host.1: Flags [.], ack 1 # third probe
00:34.211 IP host.2 &gt; host.1: Flags [.], ack 1 # fourth probe! But TCP_KEEPCNT was only 3!
00:45.219 IP host.2 &gt; host.1: Flags [.], ack 1 # fifth probe!
00:56.227 IP host.2 &gt; host.1: Flags [.], ack 1 # sixth probe!
01:07.235 IP host.2 &gt; host.1: Flags [R.], seq 1 # TCP_USER_TIMEOUT aborts conn on 7th probe timer</code></p><p>We saw six keepalive probes on the wire! With TCP_USER_TIMEOUT set, the TCP_KEEPCNT is totally ignored. If you want TCP_KEEPCNT to make sense, the only sensible USER_TIMEOUT value is slightly smaller than:</p>
            <pre><code>TCP_KEEPIDLE + TCP_KEEPINTVL * TCP_KEEPCNT</code></pre>
            
    <div>
      <h2>Busy ESTAB socket is not forever</h2>
      <a href="#busy-estab-socket-is-not-forever">
        
      </a>
    </div>
    <p>Thus far we have discussed the case where the connection is idle. Different rules apply when the connection has unacknowledged data in a send buffer.</p><p>Let's prepare another experiment - after the three-way handshake, let's set up a firewall to drop all packets. Then, let's do a <code>send</code> on one end to have some dropped packets in-flight. An experiment shows the sending socket dies after ~16 minutes:</p><p><code>00:00.000 IP host.2 &gt; host.1: Flags [S]
00:00.000 IP host.1 &gt; host.2: Flags [S.]
00:00.000 IP host.2 &gt; host.1: Flags [.]

# All subsequent packets dropped
00:00.206 IP host.2 &gt; host.1: Flags [P.], length 11 # first data packet
00:00.412 IP host.2 &gt; host.1: Flags [P.], length 11 # early retransmit, doesn't count
00:00.620 IP host.2 &gt; host.1: Flags [P.], length 11 # 1nd retry
00:01.048 IP host.2 &gt; host.1: Flags [P.], length 11 # 2rd retry
00:01.880 IP host.2 &gt; host.1: Flags [P.], length 11 # 3th retry

State Recv-Q Send-Q Local:Port Peer:Port
ESTAB 0      0      host:1     host:2
ESTAB 0      11     host:2     host:1    timer:(on,1.304ms,3)

00:03.543 IP host.2 &gt; host.1: Flags [P.], length 11 # 4th
00:07.000 IP host.2 &gt; host.1: Flags [P.], length 11 # 5th
00:13.656 IP host.2 &gt; host.1: Flags [P.], length 11 # 6th
00:26.968 IP host.2 &gt; host.1: Flags [P.], length 11 # 7th
00:54.616 IP host.2 &gt; host.1: Flags [P.], length 11 # 8th
01:47.868 IP host.2 &gt; host.1: Flags [P.], length 11 # 9th
03:34.360 IP host.2 &gt; host.1: Flags [P.], length 11 # 10th
05:35.192 IP host.2 &gt; host.1: Flags [P.], length 11 # 11th
07:36.024 IP host.2 &gt; host.1: Flags [P.], length 11 # 12th
09:36.855 IP host.2 &gt; host.1: Flags [P.], length 11 # 13th
11:37.692 IP host.2 &gt; host.1: Flags [P.], length 11 # 14th
13:38.524 IP host.2 &gt; host.1: Flags [P.], length 11 # 15th
15:39.500 connection ETIMEDOUT</code></p><p>The data packet is retransmitted 15 times, as controlled by:</p><p><code>$ sysctl net.ipv4.tcp_retries2
net.ipv4.tcp_retries2 = 15</code></p><p>From the <a href="https://www.kernel.org/doc/Documentation/networking/ip-sysctl.txt"><code>ip-sysctl.txt</code></a> documentation:</p><p><i>The default value of 15 yields a hypothetical timeout of 924.6 seconds and is a lower bound for the effective timeout. TCP will effectively time out at the first RTO which exceeds the hypothetical timeout.</i></p><p>The connection indeed died at ~940 seconds. Notice the socket has the "on" timer running. It doesn't matter at all if we set SO_KEEPALIVE - when the "on" timer is running, keepalives are not engaged.</p><p>TCP_USER_TIMEOUT keeps on working though. The connection will be aborted <i>exactly</i> after user-timeout specified time since the last received packet. With the user timeout set the <code>tcp_retries2</code> value is ignored.</p>
    <div>
      <h2>Zero window ESTAB is... forever?</h2>
      <a href="#zero-window-estab-is-forever">
        
      </a>
    </div>
    <p>There is one final case worth mentioning. If the sender has plenty of data, and the receiver is slow, then TCP flow control kicks in. At some point the receiver will ask the sender to stop transmitting new data. This is a slightly different condition than the one described above.</p><p>In this case, with flow control engaged, there is no in-flight or unacknowledged data. Instead the receiver throttles the sender with a "zero window" notification. Then the sender periodically checks if the condition is still valid with "window probes". In this experiment we reduced the receive buffer size for simplicity. Here's how it looks on the wire:</p><p><code>00:00.000 IP host.2 &gt; host.1: Flags [S]
00:00.000 IP host.1 &gt; host.2: Flags [S.], win 1152
00:00.000 IP host.2 &gt; host.1: Flags [.]</code></p><p><code>00:00.202 IP host.2 &gt; host.1: Flags [.], length 576 # first data packet
00:00.202 IP host.1 &gt; host.2: Flags [.], ack 577, win 576
00:00.202 IP host.2 &gt; host.1: Flags [P.], length 576 # second data packet
00:00.244 IP host.1 &gt; host.2: Flags [.], ack 1153, win 0 # throttle it! zero-window</code></p><p><code>00:00.456 IP host.2 &gt; host.1: Flags [.], ack 1 # zero-window probe
00:00.456 IP host.1 &gt; host.2: Flags [.], ack 1153, win 0 # nope, still zero-window</code></p><p><code>State Recv-Q Send-Q Local:Port Peer:Port
ESTAB 1152   0      host:1     host:2
ESTAB 0      129920 host:2     host:1  timer:(persist,048ms,0)</code></p><p>The packet capture shows a couple of things. First, we can see two packets with data, each 576 bytes long. They both were immediately acknowledged. The second ACK had "win 0" notification: the sender was told to stop sending data.</p><p>But the sender is eager to send more! The last two packets show a first "window probe": the sender will periodically send payload-less "ack" packets to check if the window size had changed. As long as the receiver keeps on answering, the sender will keep on sending such probes forever.</p><p>The socket information shows three important things:</p><ul><li><p>The read buffer of the reader is filled - thus the "zero window" throttling is expected.</p></li><li><p>The write buffer of the sender is filled - we have more data to send.</p></li><li><p>The sender has a "persist" timer running, counting the time until the next "window probe".</p></li></ul><p>In this blog post we are interested in timeouts - what will happen if the window probes are lost? Will the sender notice?</p><p>By default, the window probe is retried 15 times - adhering to the usual <code>tcp_retries2</code> setting.</p><p>The tcp timer is in <code>persist</code> state, so the TCP keepalives will <i>not</i> be running. The SO_KEEPALIVE settings don't make any difference when window probing is engaged.</p><p>As expected, the TCP_USER_TIMEOUT toggle keeps on working. A slight difference is that similarly to user-timeout on keepalives, it's engaged only when the retransmission timer fires. During such an event, if more than user-timeout seconds since the last good packet passed, the connection will be aborted.</p>
    <div>
      <h2>Note about using application timeouts</h2>
      <a href="#note-about-using-application-timeouts">
        
      </a>
    </div>
    <p>In the past we have shared an interesting war story:</p><ul><li><p><a href="/the-curious-case-of-slow-downloads/">The curious case of slow downloads</a></p></li></ul><p>Our HTTP server gave up on the connection after an application-managed timeout fired. This was a bug - a slow connection might have correctly slowly drained the send buffer, but the application server didn't notice that.</p><p>We abruptly dropped slow downloads, even though this wasn't our intention. We just wanted to make sure the client connection was still healthy. It would be better to use TCP_USER_TIMEOUT than rely on application-managed timeouts.</p><p>But this is not sufficient. We also wanted to guard against a situation where a client stream is valid, but is stuck and doesn't drain the connection. The only way to achieve this is to periodically check the amount of unsent data in the send buffer, and see if it shrinks at a desired pace.</p><p>For typical applications sending data to the Internet, I would recommend:</p><ol><li><p>Enable TCP keepalives. This is needed to keep some data flowing in the idle-connection case.</p></li><li><p>Set TCP_USER_TIMEOUT to <code>TCP_KEEPIDLE + TCP_KEEPINTVL * TCP_KEEPCNT</code>.</p></li><li><p>Be careful when using application-managed timeouts. To detect TCP failures use TCP keepalives and user-timeout. If you want to spare resources and make sure sockets don't stay alive for too long, consider periodically checking if the socket is draining at the desired pace. You can use <code>ioctl(TIOCOUTQ)</code> for that, but it counts both data buffered (notsent) on the socket and in-flight (unacknowledged) bytes. A better way is to use TCP_INFO tcpi_notsent_bytes parameter, which reports only the former counter.</p></li></ol><p>An example of checking the draining pace:</p><p><code>while True:
notsent1 = get_tcp_info(c).tcpi_notsent_bytes
notsent1_ts = time.time()
...
poll.poll(POLL_PERIOD)
...
notsent2 = get_tcp_info(c).tcpi_notsent_bytes
notsent2_ts = time.time()
pace_in_bytes_per_second = (notsent1 - notsent2) / (notsent2_ts - notsent1_ts)
if pace_in_bytes_per_second &gt; 12000:
# pace is above effective rate of 96Kbps, ok!
else:
# socket is too slow...</code></p><p>There are ways to further improve this logic. We could use <a href="https://lwn.net/Articles/560082/"><code>TCP_NOTSENT_LOWAT</code></a>, although it's generally only useful for situations where the send buffer is relatively empty. Then we could use the <a href="https://www.kernel.org/doc/Documentation/networking/timestamping.txt"><code>SO_TIMESTAMPING</code></a> interface for notifications about when data gets delivered. Finally, if we are done sending the data to the socket, it's possible to just call <code>close()</code> and defer handling of the socket to the operating system. Such a socket will be stuck in FIN-WAIT-1 or LAST-ACK state until it correctly drains.</p>
    <div>
      <h2>Summary</h2>
      <a href="#summary">
        
      </a>
    </div>
    <p>In this post we discussed five cases where the TCP connection may notice the other party going away:</p><ul><li><p>SYN-SENT: The duration of this state can be controlled by <code>TCP_SYNCNT</code> or <code>tcp_syn_retries</code>.</p></li><li><p>SYN-RECV: It's usually hidden from application. It is tuned by <code>tcp_synack_retries</code>.</p></li><li><p>Idling ESTABLISHED connection, will never notice any issues. A solution is to use TCP keepalives.</p></li><li><p>Busy ESTABLISHED connection, adheres to <code>tcp_retries2</code> setting, and ignores TCP keepalives.</p></li><li><p>Zero-window ESTABLISHED connection, adheres to <code>tcp_retries2</code> setting, and ignores TCP keepalives.</p></li></ul><p>Especially the last two ESTABLISHED cases can be customized with TCP_USER_TIMEOUT, but this setting also affects other situations. Generally speaking, it can be thought of as a hint to the kernel to abort the connection after so-many seconds since the last good packet. This is a dangerous setting though, and if used in conjunction with TCP keepalives should be set to a value slightly lower than <code>TCP_KEEPIDLE + TCP_KEEPINTVL * TCP_KEEPCNT</code>. Otherwise it will affect, and potentially cancel out, the TCP_KEEPCNT value.</p><p>In this post we presented scripts showing the effects of timeout-related socket options under various network conditions. Interleaving the <code>tcpdump</code> packet capture with the output of <code>ss -o</code> is a great way of understanding the networking stack. We were able to create reproducible test cases showing the "on", "keepalive" and "persist" timers in action. This is a very useful framework for further experimentation.</p><p>Finally, it's surprisingly hard to tune a TCP connection to be confident that the remote host is actually up. During our debugging we found that looking at the send buffer size and currently active TCP timer can be very helpful in understanding whether the socket is actually healthy. The bug in our Spectrum application turned out to be a wrong TCP_USER_TIMEOUT setting - without it sockets with large send buffers were lingering around for way longer than we intended.</p><p>The scripts used in this article <a href="https://github.com/cloudflare/cloudflare-blog/tree/master/2019-09-tcp-keepalives">can be found on our GitHub</a>.</p><p>Figuring this out has been a collaboration across three Cloudflare offices. Thanks to <a href="https://twitter.com/Hirenpanchasara">Hiren Panchasara</a> from San Jose, <a href="https://twitter.com/warrncn">Warren Nelson</a> from Austin and <a href="https://twitter.com/jkbs0">Jakub Sitnicki</a> from Warsaw. Fancy joining the team? <a href="https://www.cloudflare.com/careers/departments/?utm_referrer=blog">Apply here!</a></p> ]]></content:encoded>
            <category><![CDATA[SYN]]></category>
            <category><![CDATA[TCP]]></category>
            <category><![CDATA[Spectrum]]></category>
            <category><![CDATA[Tech Talks]]></category>
            <guid isPermaLink="false">PTYUwpDIf4wDZ50CejAvL</guid>
            <dc:creator>Marek Majkowski</dc:creator>
        </item>
        <item>
            <title><![CDATA[SYN packet handling in the wild]]></title>
            <link>https://blog.cloudflare.com/syn-packet-handling-in-the-wild/</link>
            <pubDate>Mon, 15 Jan 2018 13:49:09 GMT</pubDate>
            <description><![CDATA[ Here at Cloudflare, we have a lot of experience of operating servers on the wild Internet. But we are always improving our mastery of this black art. On this very blog we have touched on multiple dark corners of the Internet protocols: like understanding FIN-WAIT-2 or receive buffer tuning. ]]></description>
            <content:encoded><![CDATA[ <p>Here at Cloudflare, we have a lot of experience of operating servers on the wild Internet. But we are always improving our mastery of this black art. On this very blog we have touched on multiple dark corners of the Internet protocols: like <a href="/this-is-strictly-a-violation-of-the-tcp-specification/">understanding FIN-WAIT-2</a> or <a href="/the-story-of-one-latency-spike/">receive buffer tuning</a>.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/GBoY12EMNx75snoQtVcGI/46163cd4b532746e1a9603f57c4b044f/1471936772_652043cb6c_b.jpg" />
            
            </figure><p><a href="https://creativecommons.org/licenses/by/2.0/">CC BY 2.0</a> <a href="https://www.flickr.com/photos/isaimoreno/1471936772/in/photolist-3f54wd-6mweJG-maUn5T-2tgqad-6YuCuM-pZ7r8T-Sa3LQ9-adTFS2-qSLQzk-sJ66Lq-71cJPS-oFU9rf-mbom12-23fVpJW-71ciCN-718DHR-j4VCQQ-71chKo-5DMBr4-5DLQFK-71cG4s-qQFjhZ-2RMBP6-718KWR-71cAFA-fAr8Ri-pe5zev-8TtDbQ-b6p5gk-qAdMqQ-qSBvUZ-qyg7oz-o5yof6-adTGvc-718xp4-5XQgJZ-bgGiwk-kf7aMc-qAjY14-718uti-smXfxF-8Kdnpx-nVVy8a-cmMJGb-puizaG-qP18i9-71cu1E-nYNfjq-718CjH-qyQM72">image</a> by <a href="https://www.flickr.com/photos/isaimoreno/">Isaí Moreno</a></p><p>One subject hasn't had enough attention though - SYN floods. We use Linux and it turns out that SYN packet handling in Linux is truly complex. In this post we'll shine some light on this subject.</p>
    <div>
      <h3>The tale of two queues</h3>
      <a href="#the-tale-of-two-queues">
        
      </a>
    </div>
    
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/X9oaRfcffxKbxYg4VvWuc/8a2277a49d7e092794d581d499a6083c/all-1.jpeg.jpeg" />
            
            </figure><p>First we must understand that each bound socket, in the "LISTENING" TCP state has two separate queues:</p><ul><li><p>The SYN Queue</p></li><li><p>The Accept Queue</p></li></ul><p>In the literature these queues are often given other names such as "reqsk_queue", "ACK backlog", "listen backlog" or even "TCP backlog", but I'll stick to the names above to avoid confusion.</p>
    <div>
      <h3>SYN Queue</h3>
      <a href="#syn-queue">
        
      </a>
    </div>
    <p>The SYN Queue stores inbound SYN packets<a href="#fn1">[1]</a> (specifically: <a href="https://elixir.free-electrons.com/linux/v4.14.12/source/include/net/inet_sock.h#L73"><code>struct inet_request_sock</code></a>). It's responsible for sending out SYN+ACK packets and retrying them on timeout. On Linux the number of retries is configured with:</p>
            <pre><code>$ sysctl net.ipv4.tcp_synack_retries
net.ipv4.tcp_synack_retries = 5</code></pre>
            <p>The <a href="https://www.kernel.org/doc/Documentation/networking/ip-sysctl.txt">docs describe this toggle</a>:</p>
            <pre><code>tcp_synack_retries - INTEGER

	Number of times SYNACKs for a passive TCP connection attempt
	will be retransmitted. Should not be higher than 255. Default
	value is 5, which corresponds to 31 seconds till the last
	retransmission with the current initial RTO of 1second. With
	this the final timeout for a passive TCP connection will
	happen after 63 seconds.
</code></pre>
            <p>After transmitting the SYN+ACK, the SYN Queue waits for an ACK packet from the client - the last packet in the three-way-handshake. All received ACK packets must first be matched against the fully established connection table, and only then against data in the relevant SYN Queue. On SYN Queue match, the kernel removes the item from the SYN Queue, happily creates a fully fledged connection (specifically: <a href="https://elixir.free-electrons.com/linux/v4.14.12/source/include/net/inet_sock.h#L183"><code>struct inet_sock</code></a>), and adds it to the Accept Queue.</p>
    <div>
      <h3>Accept Queue</h3>
      <a href="#accept-queue">
        
      </a>
    </div>
    <p>The Accept Queue contains fully established connections: ready to be picked up by the application. When a process calls <code>accept()</code>, the sockets are de-queued and passed to the application.</p><p>This is a rather simplified view of SYN packet handling on Linux. With socket toggles like <code>TCP_DEFER_ACCEPT</code><a href="#fn2">[2]</a> and <code>TCP_FASTOPEN</code> things work slightly differently.</p>
    <div>
      <h3>Queue size limits</h3>
      <a href="#queue-size-limits">
        
      </a>
    </div>
    <p>The maximum allowed length of both the Accept and SYN Queues is taken from the <code>backlog</code> parameter passed to the <code>listen(2)</code> syscall by the application. For example, this sets the Accept and SYN Queue sizes to 1,024:</p>
            <pre><code>listen(sfd, 1024)</code></pre>
            <p>Note: In kernels before 4.3 the <a href="https://github.com/torvalds/linux/commit/ef547f2ac16bd9d77a780a0e7c70857e69e8f23f#diff-56ecfd3cd70d57cde321f395f0d8d743L43">SYN Queue length was counted differently</a>.</p><p>This SYN Queue cap used to be configured by the <code>net.ipv4.tcp_max_syn_backlog</code> toggle, but this isn't the case anymore. Nowadays <code>net.core.somaxconn</code> caps both queue sizes. On our servers we set it to 16k:</p>
            <pre><code>$ sysctl net.core.somaxconn
net.core.somaxconn = 16384</code></pre>
            
    <div>
      <h3>Perfect backlog value</h3>
      <a href="#perfect-backlog-value">
        
      </a>
    </div>
    <p>Knowing all that, we might ask the question - what is the ideal <code>backlog</code> parameter value?</p><p>The answer is: it depends. For the majority of trivial TCP Servers it doesn't really matter. For example, before version 1.11 <a href="https://github.com/golang/go/issues/6079">Golang famously didn't support customizing backlog value</a>. There are valid reasons to increase this value though:</p><ul><li><p>When the rate of incoming connections is really large, even with a performant application, the inbound SYN Queue may need a larger number of slots.</p></li><li><p>The <code>backlog</code> value controls the SYN Queue size. This effectively can be read as "ACK packets in flight". The larger the average round trip time to the client, the more slots are going to be used. In the case of many clients far away from the server, hundreds of milliseconds away, it makes sense to increase the backlog value.</p></li><li><p>The <code>TCP_DEFER_ACCEPT</code> option causes sockets to remain in the SYN-RECV state longer and contribute to the queue limits.</p></li></ul><p>Overshooting the <code>backlog</code> is bad as well:</p><ul><li><p>Each slot in SYN Queue uses some memory. During a SYN Flood it makes no sense to waste resources on storing attack packets. Each <code>struct inet_request_sock</code> entry in SYN Queue takes 256 bytes of memory on kernel 4.14.</p></li></ul><p>To peek into the SYN Queue on Linux we can use the <code>ss</code> command and look for <code>SYN-RECV</code> sockets. For example, on one of Cloudflare's servers we can see 119 slots used in tcp/80 SYN Queue and 78 on tcp/443.</p>
            <pre><code>$ ss -n state syn-recv sport = :80 | wc -l
119
$ ss -n state syn-recv sport = :443 | wc -l
78</code></pre>
            <p>Similar data can be shown with our <a href="https://github.com/cloudflare/cloudflare-blog/blob/master/2018-01-syn-floods/resq.stp">overenginered SystemTap script: <code>resq.stp</code></a>.</p>
    <div>
      <h3>Slow application</h3>
      <a href="#slow-application">
        
      </a>
    </div>
    
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/M8OtXMLU8ocN9jg1du8p1/57edd7ac7c6340c44e21970f27a0596d/full-accept-1.jpeg.jpeg" />
            
            </figure><p>What happens if the application can't keep up with calling <code>accept()</code> fast enough?</p><p>This is when the magic happens! When the Accept Queue gets full (is of a size of <code>backlog</code>+1) then:</p><ul><li><p>Inbound SYN packets to the SYN Queue are dropped.</p></li><li><p>Inbound ACK packets to the SYN Queue are dropped.</p></li><li><p>The TcpExtListenOverflows / <code>LINUX_MIB_LISTENOVERFLOWS</code> counter is incremented.</p></li><li><p>The TcpExtListenDrops / <code>LINUX_MIB_LISTENDROPS</code> counter is incremented.</p></li></ul><p>There is a strong rationale for dropping <i>inbound</i> packets: it's a push-back mechanism. The other party will sooner or later resend the SYN or ACK packets by which point, the hope is, the slow application will have recovered.</p><p>This is a desirable behavior for almost all servers. For completeness: it can be adjusted with the global <code>net.ipv4.tcp_abort_on_overflow</code> toggle, but better not touch it.</p><p>If your server needs to handle a large number of inbound connections and is struggling with <code>accept()</code> throughput, consider reading our <a href="/the-sad-state-of-linux-socket-balancing/">Nginx tuning / Epoll work distribution</a> post and a <a href="/perfect-locality-and-three-epic-systemtap-scripts/">follow up showing useful SystemTap scripts</a>.</p><p>You can trace the Accept Queue overflow stats by looking at <code>nstat</code> counters:</p>
            <pre><code>$ nstat -az TcpExtListenDrops
TcpExtListenDrops     49199     0.0</code></pre>
            <p>This is a global counter. It's not ideal - sometimes we saw it increasing, while all applications looked healthy! The first step should always be to print the Accept Queue sizes with <code>ss</code>:</p>
            <pre><code>$ ss -plnt sport = :6443|cat
State   Recv-Q Send-Q  Local Address:Port  Peer Address:Port
LISTEN  0      1024                *:6443             *:*</code></pre>
            <p>The column <code>Recv-Q</code> shows the number of sockets in the Accept Queue, and <code>Send-Q</code> shows the backlog parameter. In this case we see there are no outstanding sockets to be <code>accept()</code>ed, but we still saw the ListenDrops counter increasing.</p><p>It turns out our application was stuck for fraction of a second. This was sufficient to let the Accept Queue overflow for a very brief period of time. Moments later it would recover. Cases like this are hard to debug with <code>ss</code>, so we wrote <a href="https://github.com/cloudflare/cloudflare-blog/blob/master/2018-01-syn-floods/acceptq.stp">an <code>acceptq.stp</code> SystemTap script</a> to help us. It hooks into kernel and prints the SYN packets which are being dropped:</p>
            <pre><code>$ sudo stap -v acceptq.stp
time (us)        acceptq qmax  local addr    remote_addr
1495634198449075  1025   1024  0.0.0.0:6443  10.0.1.92:28585
1495634198449253  1025   1024  0.0.0.0:6443  10.0.1.92:50500
1495634198450062  1025   1024  0.0.0.0:6443  10.0.1.92:65434
...</code></pre>
            <p>Here you can see precisely which SYN packets were affected by the ListenDrops. With this script it's trivial to understand which application is dropping connections.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/3y08wVb2ROvhucECXpWm8x/f9ce889bf44d01ee2fd34c6a8d74b9cb/3713965419_20388fb368_b.jpg" />
            
            </figure><p><a href="https://creativecommons.org/licenses/by/2.0/">CC BY 2.0</a> <a href="https://www.flickr.com/photos/16339684@N00/3713965419/in/photolist-6Ec3wx-5jhnwn-bfyTRX-5jhnCa-phYcey-dxZ95n-egkTN-kwT1YH-k22LWZ-5jBUiy-bzvDWx-5jBV31-5jhnr8-5jBTkq-5jxzHk-4K3cbP-9EePyg-4e5XNt-4e5XNn-dxZ8Tn-dy5A89-dxZ6GH-cztXcJ-gF7oY-dxZ9jv-dxZ7qM-ZvSPCv-dxZ6YV-5jBTqs-5jxzaP-MvuyK-nmVwP1-5jBRhY-dxZ7YF-5jxAc2-5jBU9U-5jBTEy-ejbWe6-5jxBc6-99ENZW-99KUsi-9bWScw-5jBRow-5jxzmx-5jBTfw-r6HcW-dy5zXE-5jxzg4-5jxBYR-5jxA2B">image</a> by <a href="https://www.flickr.com/photos/16339684@N00/">internets_dairy</a></p>
    <div>
      <h3>SYN Flood</h3>
      <a href="#syn-flood">
        
      </a>
    </div>
    
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/7KIPBW1tsmG1nRfI3Guvmn/33abe68f9be1e5838ba472624c99be8d/full-syn-1.jpeg.jpeg" />
            
            </figure><p>If it's possible to overflow the Accept Queue, it must be possible to overflow the SYN Queue as well. What happens in that case?</p><p>This is what <a href="https://en.wikipedia.org/wiki/SYN_flood">SYN Flood attacks</a> are all about. In the past flooding the SYN Queue with bogus spoofed SYN packets was a real problem. Before 1996 it was possible to successfully deny the service of almost any TCP server with very little bandwidth, just by filling the SYN Queues.</p><p>The solution is <a href="https://lwn.net/Articles/277146/">SYN Cookies</a>. SYN Cookies are a construct that allows the SYN+ACK to be generated statelessly, without actually saving the inbound SYN and wasting system memory. SYN Cookies don't break legitimate traffic. When the other party is real, it will respond with a valid ACK packet including the reflected sequence number, which can be cryptographically verified.</p><p>By default SYN Cookies are enabled when needed - for sockets with a filled up SYN Queue. Linux updates a couple of counters on SYN Cookies. When a SYN cookie is being sent out:</p><ul><li><p>TcpExtTCPReqQFullDoCookies / <code>LINUX_MIB_TCPREQQFULLDOCOOKIES</code> is incremented.</p></li><li><p>TcpExtSyncookiesSent / <code>LINUX_MIB_SYNCOOKIESSENT</code> is incremented.</p></li><li><p>Linux used to increment <code>TcpExtListenDrops</code> but <a href="https://github.com/torvalds/linux/commit/9caad864151e525929d323de96cad382da49c3b2">doesn't from kernel 4.7</a>.</p></li></ul><p>When an inbound ACK is heading into the SYN Queue with SYN cookies engaged:</p><ul><li><p>TcpExtSyncookiesRecv / <code>LINUX_MIB_SYNCOOKIESRECV</code> is incremented when crypto validation succeeds.</p></li><li><p>TcpExtSyncookiesFailed / <code>LINUX_MIB_SYNCOOKIESFAILED</code> is incremented when crypto fails.</p></li></ul><p>A sysctl <code>net.ipv4.tcp_syncookies</code> can disable SYN Cookies or force-enable them. Default is good, don't change it.</p>
    <div>
      <h3>SYN Cookies and TCP Timestamps</h3>
      <a href="#syn-cookies-and-tcp-timestamps">
        
      </a>
    </div>
    <p>The SYN Cookies magic works, but isn't without disadvantages. The main problem is that there is very little data that can be saved in a SYN Cookie. Specifically, only 32 bits of the sequence number are returned in the ACK. These bits are used as follows:</p>
            <pre><code>+----------+--------+-------------------+
|  6 bits  | 2 bits |     24 bits       |
| t mod 32 |  MSS   | hash(ip, port, t) |
+----------+--------+-------------------+</code></pre>
            <p>With the MSS setting <a href="https://github.com/torvalds/linux/blob/5bbcc0f595fadb4cac0eddc4401035ec0bd95b09/net/ipv4/syncookies.c#L142">truncated to only 4 distinct values</a>, Linux doesn't know any optional TCP parameters of the other party. Information about Timestamps, ECN, Selective ACK, or Window Scaling is lost, and can lead to degraded TCP session performance.</p><p>Fortunately Linux has a work around. If TCP Timestamps are enabled, the kernel can reuse another slot of 32 bits in the Timestamp field. It contains:</p>
            <pre><code>+-----------+-------+-------+--------+
|  26 bits  | 1 bit | 1 bit | 4 bits |
| Timestamp |  ECN  | SACK  | WScale |
+-----------+-------+-------+--------+</code></pre>
            <p>TCP Timestamps should be enabled by default, to verify see the sysctl:</p>
            <pre><code>$ sysctl net.ipv4.tcp_timestamps
net.ipv4.tcp_timestamps = 1</code></pre>
            <p>Historically there was plenty of discussion about the usefulness of TCP Timestamps.</p><ul><li><p>In the past timestamps leaked server uptime (whether that matters is another discussion). This <a href="https://github.com/torvalds/linux/commit/95a22caee396cef0bb2ca8fafdd82966a49367bb">was fixed 8 months ago</a>.</p></li><li><p>TCP Timestamps use <a href="http://highscalability.com/blog/2015/10/14/save-some-bandwidth-by-turning-off-tcp-timestamps.html">non-trivial amount of of bandwidth</a> - 12 bytes on each packet.</p></li><li><p>They can add additional randomness to packet checksums which <a href="https://www.snellman.net/blog/archive/2017-07-20-s3-mystery/">can help with certain broken hardware</a>.</p></li><li><p>As mentioned above TCP Timestamps can boost the performance of TCP connections if SYN Cookies are engaged.</p></li></ul><p>Currently at Cloudflare, we have TCP Timestamps disabled.</p><p>Finally, with SYN Cookies engaged some cool features won't work - things like <a href="https://lwn.net/Articles/645128/"><code>TCP_SAVED_SYN</code></a>, <code>TCP_DEFER_ACCEPT</code> or <code>TCP_FAST_OPEN</code>.</p>
    <div>
      <h3>SYN Floods at Cloudflare scale</h3>
      <a href="#syn-floods-at-cloudflare-scale">
        
      </a>
    </div>
    
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/1nw81MpcuH9JCz80nnq4du/4737ceb4a755113713159ee7b454370a/Screen-Shot-2016-12-02-at-10.53.27-1.png" />
            
            </figure><p>SYN Cookies are a great invention and solve the problem of smaller SYN Floods. At Cloudflare though, we try to avoid them if possible. While sending out a couple of thousand of cryptographically verifiable SYN+ACK packets per second is okay, we see <a href="/the-daily-ddos-ten-days-of-massive-attacks/">attacks of more than 200 Million packets per second</a>. At this scale, our SYN+ACK responses would just litter the internet, bringing absolutely no benefit.</p><p>Instead, we attempt to drop the malicious SYN packets on the firewall layer. We use the <code>p0f</code> SYN fingerprints compiled to BPF. Read more in this blog post <a href="/introducing-the-p0f-bpf-compiler/">Introducing the p0f BPF compiler</a>. To detect and deploy the mitigations we developed an automation system we call "Gatebot". We described that here <a href="/meet-gatebot-a-bot-that-allows-us-to-sleep/">Meet Gatebot - the bot that allows us to sleep</a></p>
    <div>
      <h3>Evolving landscape</h3>
      <a href="#evolving-landscape">
        
      </a>
    </div>
    <p>For more - slightly outdated - data on the subject read on <a href="https://veithen.github.io/2014/01/01/how-tcp-backlog-works-in-linux.html">an excellent explanation by Andreas Veithen from 2015</a> and <a href="https://www.giac.org/paper/gsec/2013/syn-cookies-exploration/103486">a comprehensive paper by Gerald W. Gordon from 2013</a>.</p><p>The Linux SYN packet handling landscape is constantly evolving. Until recently SYN Cookies were slow, due to an old fashioned lock in the kernel. This was fixed in 4.4 and now you can rely on the kernel to be able to send millions of SYN Cookies per second, practically solving the SYN Flood problem for most users. With proper tuning it's possible to mitigate even the most annoying SYN Floods without affecting the performance of legitimate connections.</p><p>Application performance is also getting significant attention. Recent ideas like <code>SO_ATTACH_REUSEPORT_EBPF</code> introduce a whole new layer of programmability into the network stack.</p><p>It's great to see innovations and fresh thinking funneled into the networking stack, in the otherwise stagnant world of operating systems.</p><p><i>Thanks to Binh Le for helping with this post.</i></p><hr /><p><i>Dealing with the internals of Linux and NGINX sound interesting? Join our </i><a href="https://boards.greenhouse.io/cloudflare/jobs/589572"><i>world famous team</i></a><i> in London, Austin, San Francisco and our elite office in Warsaw, Poland.</i></p><hr /><ol><li><p>I'm simplifying, technically speaking the SYN Queue stores not yet ESTABLISHED connections, not SYN packets themselves. With <code>TCP_SAVE_SYN</code> it gets close enough though. <a href="#fnref1">↩︎</a></p></li><li><p>If <a href="http://man7.org/linux/man-pages/man7/tcp.7.html"><code>TCP_DEFER_ACCEPT</code></a> is new to you, definitely check FreeBSD's version of it - <a href="http://www.freebsd.org/cgi/man.cgi?query=accf_http&amp;sektion=9">accept filters</a>. <a href="#fnref2">↩︎</a></p></li></ol> ]]></content:encoded>
            <category><![CDATA[SYN]]></category>
            <category><![CDATA[TCP]]></category>
            <category><![CDATA[Programming]]></category>
            <guid isPermaLink="false">6G40dgfhgCyPSDplLMR9DO</guid>
            <dc:creator>Marek Majkowski</dc:creator>
        </item>
        <item>
            <title><![CDATA[How the CloudFlare Team Got Into Bondage (It's Not What You Think)]]></title>
            <link>https://blog.cloudflare.com/how-the-cloudflare-team-got-into-bondage-its/</link>
            <pubDate>Mon, 08 Apr 2013 07:18:00 GMT</pubDate>
            <description><![CDATA[ At CloudFlare, we're always looking for ways to eliminate bottlenecks. We're only able to deal with the very large amount of traffic that we handle because we've built a network that can efficiently handle an extremely high volume of network requests.  ]]></description>
            <content:encoded><![CDATA[ <p></p><p>(Imagecourtesy of ferelswirl)At CloudFlare, we're always looking for ways to eliminate bottlenecks. We're only able to deal with the very large amount of traffic that we handle, especially during large denial of service attacks, because we've built a network that can efficiently handle an extremely high volume of network requests. This post is about the nitty gritty of port bonding, one of the technologies we use, and how it allows us to get the maximum possible network throughput out of our servers.</p>
    <div>
      <h3>Generation Three</h3>
      <a href="#generation-three">
        
      </a>
    </div>
    <p>A rack of equipment in CloudFlare's network has three core components: routers, switches, and servers. We own and install all our own equipment because it's impossible to have the flexibility and efficiency you need to do what we do running on someone else's gear. Over time, we've adjusted the specs of the gear we use based on the needs of our network and what we are able to cost effectively source from vendors.</p><p>Most of the equipment in our network today is based on our Generation 3 (G3) spec, which we deployed throughout 2012. Focusing just on the network connectivity for our G3 gear, our routers have multiple 10Gbps ports which connect out to the Internet as well as in to our switches. Our switches have a handful of 10Gbps ports that we use to connect to our routers and then 48 1Gbps ports that connect to the servers. Finally, our servers have 6 1Gbps ports, two on the motherboard (using Intel's chipset) and four on an Intel PCI network card. (There's an additional IPMI management port as well, but it doesn't figure into this discussion.)</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/45d2KU77a3AmxdK67bKfQf/c89532e9a3fe07c03c9d97ef58e28b97/cloudflare_servers.jpg.scaled500.jpg" />
            
            </figure><p>To get high levels of utilization and keep our server spec consistent and flexible, each of the servers in our network can perform any of the key CloudFlare functions: DNS, front-line, caching, and logging. Cache, for example, is spread across multiple machines in a facility. This means if we add another drive to one of the servers in a data center, then the total available storage space for the cache increases for all the servers in that data center. What's good about this is that, as we need to, we can add more servers and linearly scale capacity across storage, CPU, and, in some applications, RAM. The challenge is that in order to pull this off there needs to be a significant amount of communication between servers across our local area network (LAN).</p><p>When we originally started deploying our G3 servers in early 2012, we treated each 1Gbps port on the switches and routers discretely. While each server could, in theory, handle 6Gbps of traffic, each port could only handle 1Gbps. Usually this was no big deal because we load balanced customers across multiple servers in multiple data centers so on no individual server port was a customer likely to burst over 1Gbps. However, we found that, from time to time, when a customer would come under attack, traffic to individual machines could exceed 1Gbps and overwhelm a port.</p>
    <div>
      <h3>When A Problem Comes Along...</h3>
      <a href="#when-a-problem-comes-along">
        
      </a>
    </div>
    <p>The goal of a denial of service attack is to find a bottleneck and then send enough garbage requests to fill it up and prevent legitimate requests from getting through. At the same time, our goal when mitigating such an attack is first to ensure the attack doesn't harm other customers and then to stop the attack from hurting the actual target.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/5nK3bhH8RhZTplMpH38xhS/a350c165fa9650f1ed579f1bd5519824/tumblr_m54e8tjzdQ1qfj10wo1_500.gif" />
            
            </figure><p>For the most part, the biggest attacks by volume we see are Layer 3 attacks. In these, packets are stopped at the edge of our network and never reach our server infrastructure. As the <a href="/the-ddos-that-almost-broke-the-internet">very large attack against Spamhaus</a>showed, we have a significant amount of network capacity at our edge and are therefore able to stop these Layer 3 attacks very effectively.</p><p>While the big Layer 3 attacks get the most attention, an attack doesn't need to be so large if it can affect another, narrower bottleneck. For example, switches and routers are largely blind to Layer 7 attacks, meaning our servers need to process the requests. That means the requests associated with the attack need to pass across the smaller, 1Gbps port on the server. From time to time, we've found that these attacks reached a large enough scale to overwhelm a 1Gbps port on one of our servers, making it a potential bottleneck.</p><p>Beyond raw bandwidth, the other bottleneck with some attacks centers on network interrupts. In most operating systems, every time a packet is received by a server, the network card generates an interrupt (known as an IRQ). An IRQ is effectively an instruction to the CPU to stop whatever else it's doing and deal with an event, in this case a packet arriving over the network. Each network adapter has multiple queues per port that receive these IRQs and then hands them to the server's CPU. The clock speed and driver efficiency in the network adapters, and message passing rate of the bus, effectively sets the maximum number of interrupts per second, and therefore packets per second, a server's network interface can handle.</p><p>In certain attacks, like large SYN floods which send a very high volume of very small packets, there can be plenty of bandwidth on a port but a CPU can be bottlenecked on IRQ handling. When this happens it can shut down a particular core on a CPU or, in the worst case if IRQs aren't properly balanced, shut down the whole CPU. To better deal with these attacks, we needed to find a way to more intelligently spread IRQs across more interfaces and, in turn, more CPU cores.</p><p>Both these problems are annoying if it affects the customer under attack, but it is unacceptable it spills over and affects customers who are not under attack. To ensure that would never happen, we needed to find a way to both increase network capacity and ensure that customer attacks were isolated from one another. To accomplish this we launched what we affectionately refer to in the office as "Project Bondage."</p>
    <div>
      <h3>Getting Into Bondage</h3>
      <a href="#getting-into-bondage">
        
      </a>
    </div>
    <p>To deal with these challenges we started by implementing what is known as port bonding. The idea of port bonding is simple: use the resources of multiple ports in aggregate in order to support more traffic than any one port can on its own. We use a custom operating system based on the Debian line of Linux. Like most Linux varieties, our OS supports seven different port bonding modes:</p><ul><li><p>[0] Round-robin: Packets are transmitted sequentially through list of connections</p></li><li><p>[1] Active-backup: Only one connection is active, when it fails another is activated</p></li><li><p>[2] Balance-xor: This will ensure packets to a given destination from a given source will be the same over multiple connections</p></li><li><p>[3] Broadcast: Transmits everything over every active connection</p></li><li><p>[4] 802.3ad Dynamic Link Aggregation: Creates aggregation groups that share the same speed and duplex settings. Switches upstream must support 802.3ad.</p></li><li><p>[5] Balance-tlb: Adaptive transmit load balancing — outgoing traffic is balanced based on total amount being transmitted</p></li><li><p>[6] Balance-alb: Adaptive load balancing — includes balance-tlb and balances incoming traffic by using ARP negotiation to dynamically change the source MAC addresses of outgoing packets</p></li></ul><p>We use mode 4, 802.3ad Dynamic Link Aggregation. This requires switches that support 802.3ad (our workhorse switch is a Juniper 4200, which does). Our switches are configured to send packets from each stream to the same network interface. If you want to experiment with port bonding yourself, the next section covers the technical details of exactly how we set it up.</p>
    <div>
      <h3>The Nitty Gritty</h3>
      <a href="#the-nitty-gritty">
        
      </a>
    </div>
    <p>Port bonding is configured on each server. It requires two Linux components that you can apt-get (assuming you're using a Debian-based Linux) if they're not already installed: ifenslave and ethtool. To initialize the bonding driver we use the following command:</p><p>modprobe bonding mode=4 miimon=100 xmit_hash_policy=1 lacp_rate=1</p><p>Here's how that command breaks down:</p><ul><li><p><b>mode=4</b>: 802.3ad Dynamic Link Aggregation mode described above</p></li><li><p><b>miimon=100</b>: indicates that the devices are polled every 100ms to check for * connection changes, such as a link being down or a link duplex having changed.</p></li><li><p><b>xmit_hash_policy=1</b>: instructs the driver to spread the load over interfaces based on the source and destination IP address instead of MAC address</p></li><li><p><b>lacp_rate=1</b>: sets the rate for transmitting LACPDU packets, 0 is once every 30 seconds, 1 is every 1 second, which allows our network devices to automatically configure a single logical connection at the switch quickly</p></li></ul><p>After the bonding driver is initialized, we bring down the servers' network interfaces:</p><p>ifconfig eth0 downifconfig eth1 down</p><p>We then bring up the bonding interface:</p><p>ifconfig bond0 192.168.0.2/24 up</p><p>We then enslave (seriously, that's the term) the interfaces in the bond:</p><p>ifenslave bond0 eth0ifenslave bond0 eth1</p><p>Finally, we check the status of the bonded interface:</p><p>cat /proc/net/bonding/bond0</p><p>From an application perspective, bonded ports appear as a single logical network interface with a higher maximum throughput. Since our switch recognizes and supports 802.3ad Dynamic Link Aggregation, we don't have to make any changes to its configuration in order for port bonding to work. In our case, we aggregate three ports (3Gbps) for handling external traffic and the remaining three ports (3Gbps) for handling intra-server traffic across our LAN.</p>
    <div>
      <h3>Working Out the Kinks</h3>
      <a href="#working-out-the-kinks">
        
      </a>
    </div>
    <p>Expanding the maximum effective capacity of each logical interface is half the battle. The other half is ensuring that network interrupts (IRQs) don't become a bottleneck. By default most Linux distributions rely on a service called irqbalance to set the CPU affinity of each IRQ queue. Unfortunately, we found that irqbalance does not effectively isolate each queue from overwhelming another on the same CPU. The problem with this is, because of the traffic we need to send from machine to machine, external attack traffic risked disrupting internal LAN traffic and affecting site performance beyond the customer under attack.</p><p>To solve this, the first thing we did was disable irqbalance:</p><p>/etc/init.d/irqbalance stopupdate-rc.d irqbalance remove</p><p>Instead, we explicitly setup IRQ handling to isolate our external and internal (LAN) networks. Each of our servers has two physical CPUs (G3 hardware uses a low-watt version of Intel Westmere line of CPUs) with six physical cores each. We use Intel's hyperthreading technology which effectively doubles the number of logical CPU cores: 12 per CPU or 24 per server.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/18d512HGibCntbj2ic5VsL/c0639e640d7a45e8b1285e2f87089679/intel_x5645e.jpg.scaled500.jpg" />
            
            </figure><p>Each port on our NICs has a number of queues to handle incoming requests. These are known as RSS (Receive Side Scaling) queues. Each port has 8 RSS queues, we have 6 1Gbps NIC ports per server, so a total of 48 RSS queues. These 48 RSS queues are allocated to the 24 cores, with 2 RSS queues per core. We divide the RSS queues between internal (LAN) traffic and external traffic and bind each type of traffic to one of the two server CPUs. This ensures that even large SYN floods that may affect a machine's ability to handle more external requests won't keep it from handling requests from other servers in the data center.</p>
    <div>
      <h3>The Results</h3>
      <a href="#the-results">
        
      </a>
    </div>
    <p>The net effect of these changes allows us to handle 30% larger SYN floods per server and increases our maximum throughput per site per server by 300%. Equally importantly, by custom tuning our IRQ handling, it has allowed us to ensure that customers under attack are isolated from those who are not while still delivering the maximum performance by fully utilizing all the gear in our network.</p><p>Near the end of 2012, our ops and networking teams sat down to spec our next generation of gear, incorporating everything we've learned over the previous year. One of the biggest changes we're making with G4 is the jump from 1Gbps network interfaces up to 10Gbps network interfaces on our switches and servers. Even without bonding, our tests of the new G4 gear show that it significantly increases both maximum throughput and IRQ handling. Or, put more succinctly: this next generation of servers is smokin' fast.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/2ZEiqmp0eo4Ac5dOIUGm6I/eb5daef87ff3901a85cb0c9d91a71aca/next-generation.jpg.scaled500.jpg" />
            
            </figure><p>The first installations of the G4 gear is now in testing in a handful of our facilities. After testing, we plan to roll out worldwide over the coming months. We're already planning a detailed tour of the gear we chose, an explanation of the decisions we made, and performance benchmarks to show you how this next generation of gear is going to make CloudFlare's network even faster, safer, and smarter. That's a blog post I'm looking forward to writing. Stay tuned!</p> ]]></content:encoded>
            <category><![CDATA[Reliability]]></category>
            <category><![CDATA[Attacks]]></category>
            <category><![CDATA[SYN]]></category>
            <guid isPermaLink="false">12zTD9Yc0ZUsegDbEAm7hU</guid>
            <dc:creator>Matthew Prince</dc:creator>
        </item>
    </channel>
</rss>