
<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>Sat, 04 Apr 2026 08:40:06 GMT</lastBuildDate>
        <item>
            <title><![CDATA[A QUICker SASE client: re-building Proxy Mode]]></title>
            <link>https://blog.cloudflare.com/faster-sase-proxy-mode-quic/</link>
            <pubDate>Thu, 05 Mar 2026 06:00:00 GMT</pubDate>
            <description><![CDATA[ By transitioning the Cloudflare One Client to use QUIC streams for Proxy Mode, we eliminated the overhead of user-space TCP stacks, resulting in a 2x increase in throughput and significant latency reduction for end users.  ]]></description>
            <content:encoded><![CDATA[ <p>When you need to use a <a href="https://blog.cloudflare.com/a-primer-on-proxies/"><u>proxy</u></a> to keep your zero trust environment secure, it often comes with a cost: poor performance for your users. Soon after deploying a client proxy, security teams are generally slammed with support tickets from users frustrated with sluggish browser speed, slow file transfers, and video calls glitching at just the wrong moment. After a while, you start to chalk it up to the proxy — potentially blinding yourself to other issues affecting performance. </p><p>We knew it didn’t have to be this way. We knew users could go faster, without sacrificing security, if we completely re-built our approach to <a href="https://developers.cloudflare.com/cloudflare-one/team-and-resources/devices/warp/configure-warp/warp-modes/#local-proxy-mode"><u>proxy mode</u></a>. So we did.</p><p>In the early days of developing the device client for our <a href="https://www.cloudflare.com/learning/access-management/what-is-sase/"><u>SASE</u></a> platform, <a href="https://www.cloudflare.com/sase/"><u>Cloudflare One</u></a>, we prioritized universal compatibility. When an admin enabled proxy mode, the Client acted as a local SOCKS5 or HTTP proxy. However, because our underlying tunnel architecture was built on WireGuard, a Layer 3 (L3) protocol, we faced a technical hurdle: how to get application-layer (L4) TCP traffic into an L3 tunnel. Moving from L4 to L3 was especially difficult because our desktop Client works across multiple platforms (Windows, macOS, Linux) so we couldn’t <a href="https://blog.cloudflare.com/from-ip-packets-to-http-the-many-faces-of-our-oxy-framework/#from-an-ip-flow-to-a-tcp-stream"><u>use the kernel </u></a>to achieve this.</p><p>To get over this hurdle, we used smoltcp, a Rust-based user-space TCP implementation. When a packet hit the local proxy, the Client had to perform a conversion, using smoltcp to convert the L4 stream into L3 packets for the WireGuard tunnel.</p><p>While this worked, it wasn't efficient. Smoltcp is optimized for embedded systems, and does not support modern TCP features. In addition, in the Cloudflare edge, we had to convert the L3 packets back into an L4 stream. For users, this manifested as a performance ceiling. On media-heavy sites where a browser might open dozens of concurrent connections for images and video, and the lack of a high performing TCP stack led to high latency and sluggish load times when even on high-speed fiber connections, proxy mode felt significantly slower than all the other device client modes.</p>
    <div>
      <h3>Introducing direct L4 proxying with QUIC</h3>
      <a href="#introducing-direct-l4-proxying-with-quic">
        
      </a>
    </div>
    <p>To solve this, we’ve re-built the Cloudflare One Client’s proxy mode from the ground up and deprecated the use of WireGuard for proxy mode, so we can capitalize on the capabilities of QUIC. We were already leveraging <a href="https://blog.cloudflare.com/zero-trust-warp-with-a-masque/"><u>MASQUE</u></a> (part of QUIC) for proxying IP packets, and added the usage of QUIC streams for direct L4 proxying.</p><p>By leveraging HTTP/3 (<a href="https://datatracker.ietf.org/doc/rfc9114"><u>RFC 9114</u></a>) with the CONNECT method, we can now keep traffic at Layer 4, where it belongs. When your browser sends a SOCKS5 or HTTP request to the Client, it is no longer broken down into L3 packets.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/w9mIuKa8usLgxDxVqaHax/9861604fc84508b7fc6666bf8b82a874/image1.png" />
          </figure><p>Instead, it is encapsulated directly into a QUIC stream.</p><p>This architectural shift provides three immediate technical advantages:</p><ul><li><p>Bypassing smoltcp: By removing the L3 translation layer, we eliminate IP packet handling and the limitations of smoltcp’s TCP implementation.</p></li><li><p>Native QUIC Benefits: We benefit from modern congestion control and flow control, which are handled natively by the transport layer.</p></li><li><p>Tuneability: The Client and Cloudflare’s edge can tune QUIC’s parameters to optimize performance.</p></li></ul><p>In our internal testing, the results were clear: <b>download and upload speeds doubled, and latency decreased significantly</b>.</p>
    <div>
      <h3>Who benefits the most</h3>
      <a href="#who-benefits-the-most">
        
      </a>
    </div>
    <p>While faster is always better, this update specifically unblocks three key common use cases.</p><p>First, in <b>coexistence with third-party VPNs </b>where a legacy VPN is still required for specific on-prem resources or where having a dual SASE setup is required for redundancy/compliance, the local proxy mode is the go-to solution for adding zero trust security to web traffic. This update ensures that "layering" security doesn't mean sacrificing the user experience.</p><p>Second, for <b>high-bandwidth application partitioning</b>, proxy mode is often used to steer specific browser traffic through Cloudflare Gateway while leaving the rest of the OS on the local network. Users can now stream high-definition content or handle large datasets without sacrificing performance.</p><p>Finally, <b>developers and power users</b> who rely on the SOCKS5 secondary listener for CLI tools or scripts will see immediate improvements. Remote API calls and data transfers through the proxy now benefit from the same low-latency connection as the rest of the Cloudflare global network.</p>
    <div>
      <h3>How to get started</h3>
      <a href="#how-to-get-started">
        
      </a>
    </div>
    <p>The proxy mode improvements are available with minimum client version 2025.8.779.0 for Windows, macOS, and Linux devices. To take advantage of these performance gains, ensure you are running the <a href="https://developers.cloudflare.com/cloudflare-one/team-and-resources/devices/warp/download-warp/"><u>latest version of the Cloudflare One Client</u></a>.</p><ol><li><p>Log in to the <b>Cloudflare One dashboard</b>.</p></li><li><p>Navigate to <b>Teams &amp; Resources &gt; Devices &gt; Device profiles &gt; General profiles</b>.</p></li><li><p>Select a profile to edit or create a new one and ensure the <b>Service mode</b> is set to <b>Local proxy mode</b> and the <b>Device tunnel protocol</b> is set to <b>MASQUE</b>.</p></li></ol><p>You can verify your active protocol on a client machine by running the following command in your terminal: </p>
            <pre><code>warp-cli settings | grep protocol</code></pre>
            <p>Visit our <a href="https://developers.cloudflare.com/cloudflare-one/team-and-resources/devices/warp/configure-warp/warp-modes/#set-up-local-proxy-mode"><u>documentation</u></a> for detailed guidance on enabling proxy mode for your devices.</p><p>If you haven't started your SASE journey yet, you can sign up for a<a href="https://dash.cloudflare.com/sign-up/zero-trust"><u> free Cloudflare One account</u></a> for up to 50 users today. Simply <a href="https://dash.cloudflare.com/sign-up/zero-trust"><u>create an account</u></a>, download the<a href="https://1.1.1.1/"> <u>Cloudflare One Client</u></a>, and follow our<a href="https://developers.cloudflare.com/cloudflare-one/connections/connect-devices/warp/"> <u>onboarding guide</u></a> to experience a faster, more stable connection for your entire team.</p> ]]></content:encoded>
            <category><![CDATA[SASE]]></category>
            <category><![CDATA[Proxying]]></category>
            <category><![CDATA[Cloudflare Zero Trust]]></category>
            <category><![CDATA[Zero Trust]]></category>
            <category><![CDATA[Cloudflare One]]></category>
            <category><![CDATA[Cloudflare One Client]]></category>
            <category><![CDATA[Connectivity]]></category>
            <category><![CDATA[TCP]]></category>
            <guid isPermaLink="false">11I7Snst3LH2T0tJC5HLbN</guid>
            <dc:creator>Koko Uko</dc:creator>
            <dc:creator>Logan Praneis</dc:creator>
            <dc:creator>Gregor Maier</dc:creator>
        </item>
        <item>
            <title><![CDATA[Measuring characteristics of TCP connections at Internet scale]]></title>
            <link>https://blog.cloudflare.com/measuring-network-connections-at-scale/</link>
            <pubDate>Wed, 29 Oct 2025 13:00:00 GMT</pubDate>
            <description><![CDATA[ Researchers and practitioners have been studying connections almost as long as the Internet that supports them. Today, Cloudflare’s global network receives millions of connections per second. We explore various characteristics of TCP connections, including lifetimes, sizes, and more. ]]></description>
            <content:encoded><![CDATA[ <p></p><p>Every interaction on the Internet—including loading a web page, streaming a video, or making an API call—starts with a connection. These fundamental logical connections consist of a stream of packets flowing back and forth between devices.</p><p>Various aspects of these network connections have captured the attention of researchers and practitioners for as long as the Internet has existed. The interest in connections even predates the label, as can be seen in the seminal 1991 paper, “<a href="https://dl.acm.org/doi/10.1145/115994.116003"><u>Characteristics of wide-area TCP/IP conversations</u></a>.” By any name, the Internet measurement community has been steeped in characterizations of Internet communication for <i>decades</i>, asking everything from “how long?” and “how big?” to “how often?” – and those are just to start.</p><p>Surprisingly, connection characteristics on the wider Internet are largely unavailable. While anyone can  use tools (e.g., <a href="https://www.wireshark.org/"><u>Wireshark</u></a>) to capture data locally, it’s virtually impossible to measure connections globally because of access and scale. Moreover, network operators generally do not share the characteristics they observe — assuming that non-trivial time and energy is taken to observe them.</p><p>In this blog post, we move in another direction by sharing aggregate insights about connections established through our global CDN. We present characteristics of <a href="https://developers.cloudflare.com/fundamentals/reference/tcp-connections/"><u>TCP</u></a> connections—which account for about <a href="https://radar.cloudflare.com/adoption-and-usage"><u>70% of HTTP requests</u></a> to Cloudflare—providing empirical insights that are difficult to obtain from client-side measurements alone.</p>
    <div>
      <h2>Why connection characteristics matter</h2>
      <a href="#why-connection-characteristics-matter">
        
      </a>
    </div>
    <p>Characterizing system behavior helps us predict the impact of changes. In the context of networks, consider a new routing algorithm or transport protocol: how can you measure its effects? One option is to deploy the change directly on live networks, but this is risky. Unexpected consequences could disrupt users or other parts of the network, making a “deploy-first” approach potentially unsafe or ethically questionable.</p><p>A safer alternative to live deployment as a first step is simulation. Using simulation, a designer can get important insights about their scheme without having to build a full version. But simulating the whole Internet is challenging, as described by another highly seminal work, “<a href="https://dl.acm.org/doi/10.1145/268437.268737"><u>Why we don't know how to simulate the Internet</u></a>”.</p><p>To run a useful simulation, we need it to behave like the real system we’re studying. That means generating synthetic data that mimics real-world behavior. Often, we do this by using statistical distributions — mathematical descriptions of how the real data behaves. But before we can create those distributions, we first need to characterize the data — to measure and understand its key properties. Only then can our simulation produce realistic results.</p>
    <div>
      <h2>Unpacking the dataset</h2>
      <a href="#unpacking-the-dataset">
        
      </a>
    </div>
    <p>The value of any data depends on its collection mechanism. Every dataset has blind spots, biases, and limitations, and ignoring these can lead to misleading conclusions. By examining the finer details — how the data was gathered, what it represents, and what it excludes — we can better understand its reliability and make informed decisions about how to use it. Let’s take a closer look at our collected telemetry.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/5ksUQ7xlzXPWp2hH7eX4dG/124456d20c6fd5e7e185d68865aee6fa/image5.png" />
          </figure><p><b>Dataset Overview</b>. The data describes TCP connections, labeled <i>Visitor to Cloudflare</i> in the above diagram, which serve requests via HTTP 1.0, 1.1, and 2.0 that make up <a href="https://radar.cloudflare.com/adoption-and-usage">about 70%</a> of all 84 million HTTP requests per second, on average, received at our global CDN servers.</p><p><b>Sampling.</b> The passively collected snapshot of data is drawn from a uniformly sampled 1% of all TCP connections to Cloudflare between October 7 and October 15, 2025. Sampling takes place at each individual client-facing server to mitigate biases that may appear by sampling at the datacenter level.</p><p><b>Diversity.</b> Unlike many large operators, whose traffic is primarily their own and dominated by a few services such as search, social media, or streaming video, the vast majority of Cloudflare’s workload comes from our customers, who choose to put Cloudflare in front of their websites to help protect, improve performance, and reduce costs. This diversity of customers brings a wide variety of web applications, services, and users from around the world. As a result, the connections we observe are shaped by a broad range of client devices and application-specific behaviors that are constantly evolving.</p><p><b>What we log.</b> Each entry in the log consists of socket-level metadata captured via the Linux kernel’s <a href="https://man7.org/linux/man-pages/man7/tcp.7.html"><u>TCP_INFO</u></a> struct, alongside the SNI and the number of requests made during the connection. The logs exclude individual HTTP requests, transactions, and details. We restrict our use of the logs to connection metadata statistics such as duration and number of packets transmitted, as well as the number of HTTP requests processed.</p><p><b>Data capture.</b> We have elected to represent ‘useful’ connections in our dataset that have been fully processed, by characterizing only those connections that close gracefully with <a href="https://blog.cloudflare.com/tcp-resets-timeouts/#tcp-connections-from-establishment-to-close"><u>a FIN packet</u></a>. This excludes connections intercepted by attack mitigations, or that timeout, or that abort because of a RST packet.</p><p>Since a graceful close does not in itself indicate a ‘useful’ connection, <b>we additionally require at least one successful HTTP request</b> during the connection to filter out idle or non-HTTP connections from this analysis — interestingly, these make up 11% of all TCP connections to Cloudflare that close with a FIN packet.</p><p>If you’re curious, we’ve also previously blogged about the details of Cloudflare’s <a href="https://blog.cloudflare.com/how-we-make-sense-of-too-much-data/"><u>overall logging mechanism</u></a> and <a href="https://blog.cloudflare.com/http-analytics-for-6m-requests-per-second-using-clickhouse/"><u>post-processing pipeline</u></a>.  </p>
    <div>
      <h2>Visualizing connection characteristics</h2>
      <a href="#visualizing-connection-characteristics">
        
      </a>
    </div>
    <p>Although networks are inherently dynamic and trends can change over time, the large-scale patterns we observe across our global infrastructure remain remarkably consistent over time. While our data offers a global view of connection characteristics, distributions can still vary according to regional traffic patterns.</p><p>In our visualizations we represent characteristics with <a href="https://en.wikipedia.org/wiki/Cumulative_distribution_function"><u>cumulative distribution function (CDF)</u></a> graphs, specifically their <a href="https://en.wikipedia.org/wiki/Empirical_distribution_function"><u>empirical equivalents</u></a>. CDFs are particularly useful for gaining a macroscopic view of the distribution. They give a clear picture of both common and extreme cases in a single view. We use them in the illustrations below to make sense of large-scale patterns. To better interpret the distributions, we also employ log-scaled axes to account for the presence of extreme values common to networking data.</p><p>A long-standing question about Internet connections relates to “<a href="https://en.wikipedia.org/wiki/Elephant_flow"><u>Elephants and Mice</u></a>”; practitioners and researchers are entirely aware that most flows are small and some are huge, yet little data exists to inform the lines that divide them. This is where our presentation begins.</p>
    <div>
      <h3>Packet Counts</h3>
      <a href="#packet-counts">
        
      </a>
    </div>
    <p>Let’s start by taking a look at the distribution of the number of <i>response</i> packets sent in connections by Cloudflare servers back to the clients.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/5qaPCul0l7bdOQfaxL1Wbn/d0ef9cc108ba35d49593029baed7cb86/image12.png" />
          </figure><p>On the graph, the x-axis represents the number of response packets sent in log-scale, while the y-axis shows the cumulative fraction of connections below each packet count. The average response consists of roughly 240 packets, but the distribution is highly skewed. The median is 12 packets, which indicates that 50% of Internet connections consist of <i>very few packets</i>.<i> </i>Extending further to<i> </i>the 90th percentile, connections carry only 107 packets.</p><p>This stark contrast highlights the heavy-tailed nature of Internet traffic: while a few connections transport massive amounts of data—like video streams or large file transfers—most interactions are tiny, delivering small web objects, microservice traffic, or API responses.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/3Mf6VwD2Xq8aBwQP1V9aX5/1a20d6fa2caab5c719591db8b232f6a1/image11.png" />
          </figure><p>The above plot breaks down the packet count distribution by HTTP protocol version. For HTTP/1.X (both HTTP 1.0 and 1.1 combined) connections, the median response consists of just 10 packets, and 90% of connections carry fewer than 63 response packets. In contrast, HTTP/2 connections show larger responses, with a median of 16 packets and a 90th percentile of 170 packets. This difference likely reflects how HTTP/2 multiplexes multiple streams over a single connection, often consolidating more requests and responses into fewer connections, which increases the total number of packets exchanged per connection. HTTP/2 connections also have additional control-plane frames and flow-control messages that increase response packet counts.</p><p>Despite these differences, the combined view displays the same heavy-tailed pattern: a small fraction of connections carry enormous volumes of data (<a href="https://en.wikipedia.org/wiki/Elephant_flow"><u>elephant flows</u></a>), extending to millions of packets, while most remain lightweight (<a href="https://en.wikipedia.org/wiki/Mouse_flow"><u>mice flows</u></a>).</p><p>So far, we’ve focused on the total number of packets sent from our servers to clients, but another important dimension of connection behavior is the balance between packets sent and received, illustrated below.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/5VZeU0d2EYLxPl3SaTPJBb/6b46a793d6eea178838c4f5b2572caf1/image2.png" />
          </figure><p>The x-axis shows the ratio of packets sent by our servers to packets received from clients, visualized as a CDF. Across all connections, the median ratio is 0.91, meaning that in half of connections, clients send slightly more packets than the server responds with. This excess of client-side packets primarily reflects <a href="https://www.cloudflare.com/learning/ssl/transport-layer-security-tls/"><u>TLS</u></a> handshake initiation (ClientHello), HTTP control request headers, and data acknowledgements (ACKs), causing the client to typically transmit more packets than the server returns with the content payload — particularly for low-volume connections that dominate the distribution.</p><p>The mean ratio is higher, at 1.28, due to a long tail of client-heavy connections, such as large downloads typical of CDN workloads. Most connections fall within a relatively narrow range: 10% of connections have a ratio below 0.67, and 90% are below 1.85. However, the long-tailed behavior highlights the diversity of Internet traffic: extreme values arise from both upload-heavy and download-heavy connections. The variance of 3.71 reflects these asymmetric flows, while the bulk of connections maintain a roughly balanced upload-to-download exchange.</p>
    <div>
      <h3>Bytes sent</h3>
      <a href="#bytes-sent">
        
      </a>
    </div>
    <p>Another dimension to look at the data is using bytes sent by our servers to clients, which captures the actual volume of data delivered over each connection. This metric is derived from tcpi_bytes_sent, also covering (re)transmitted segment payloads while excluding the TCP header, as defined in <a href="https://github.com/torvalds/linux/blob/v6.14/include/uapi/linux/tcp.h#L222-L312"><u>linux/tcp.h</u></a> and aligned with <a href="https://www.rfc-editor.org/rfc/rfc4898.html"><u>RFC 4898</u></a> (TCP Extended Statistics MIB).</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/1VZs6F65RQjyyEUUxZSP2L/b0edd986738e9128c16dcbecb7d83761/image3.png" />
          </figure><p>The plots above break down bytes sent by HTTP protocol version. The x-axis represents the total bytes sent by our servers over each connection. The patterns are generally consistent with what we observed in the packet count distributions.</p><p>For HTTP/1.X, the median response delivers 4.8 KB, and 90% of connections send fewer than 51 KB. In contrast, HTTP/2 connections show slightly larger responses, with a median of 6 KB and a 90th percentile of 146 KB. The mean is much higher—224 KB for HTTP/1.x and 390 KB for HTTP/2—reflecting a small number of very large transfers. These long-tailed extreme flows can reach tens of gigabytes per connection, while some very lightweight connections carry minimal payloads: the minimum for HTTP/1.X is 115 bytes and for HTTP/2 it is 202 bytes.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/2xRYaXYQbte6MszIT92uky/837ebdc842c9784a9c413ad886f7a5d6/image6.png" />
          </figure><p>By making use of the tcpi_bytes_received metric, we can now look at the ratio of bytes sent to bytes received per connection to better understand the balance of data exchange. This ratio captures how asymmetric each connection is — essentially, how much data our servers send compared to what they receive from clients. Across all connections, the median ratio is 3.78, meaning that in half of all cases, servers send nearly four times more data than they receive. The average is far higher at 81.06, showing a strong long tail driven by download-heavy flows. Again we see the heavy long-tailed distribution, a small fraction of extreme cases push the ratio into the millions, with more extreme values of data transfers towards clients.</p>
    <div>
      <h3>Connection duration</h3>
      <a href="#connection-duration">
        
      </a>
    </div>
    <p>While packet and byte counts capture how much data is exchanged, connection duration provides insight into how that exchange unfolds over time.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/5noP7Acqu2Ky4hCGtETH1F/92c7bd220d57232fb40440624d227a78/image8.png" />
          </figure><p>The CDF above shows the distribution of connection durations (lifetimes) in seconds. A reminder that the x-axis is log-scale. Across all connections, the median duration is just 4.7 seconds, meaning half of connections complete in under five seconds. The mean is much higher at 96 seconds, reflecting a small number of long-lived connections that skew the average. Most connections fall within a window of 0.1 seconds (10th percentile) to 300 seconds (90th percentile). We also observe some extremely long-lived connections lasting multiple days, possibly maintained via <a href="https://developers.cloudflare.com/fundamentals/reference/tcp-connections/#tcp-connections-and-keep-alives"><u>keep-alives</u></a> for connection reuse without hitting <a href="https://developers.cloudflare.com/fundamentals/reference/connection-limits/"><u>our default idle timeout limits</u></a>. These long-lived connections typically represent persistent sessions or multimedia traffic, while the majority of web traffic remains short, bursty, and transient.</p>
    <div>
      <h3>Request counts</h3>
      <a href="#request-counts">
        
      </a>
    </div>
    <p>A single connection can carry multiple HTTP requests for web traffic. This reveals patterns about connection multiplexing.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/4hsoigL4rFtIyRJpdSUXwh/5ef82b3c0cf5b25b8dc13ed38761f895/image7.png" />
          </figure><p>The above shows the number of HTTP requests (in log-scale) that we see on a single connection, broken down by HTTP protocol version. Right away, we can see that for both HTTP/1.X (mean 3 requests) and HTTP/2 (mean 8 requests) connections, the median number of requests is just 1, reinforcing the prevalence of limited connection reuse. However, because HTTP/2 supports multiplexing multiple streams over a single connection, the 90th percentile rises to 10 requests, with occasional extreme cases carrying thousands of requests, which can be amplified due to <a href="https://blog.cloudflare.com/connection-coalescing-experiments/"><u>connection coalescing</u></a>. In contrast, HTTP/1.X connections have much lower request counts. This aligns with protocol design: HTTP/1.0 followed a “one request per connection” philosophy, while HTTP/1.1 introduced persistent connections — even combining both versions, it’s rare to see HTTP/1.X connections carrying more than two requests at the 90th percentile.</p><p>The prevalence of short-lived connections can be partly explained by automated clients or scripts that tend to open new connections rather than maintaining long-lived sessions. To explore this intuition, we split the data between traffic originating from data centers (likely automated) and typical user traffic (user-driven), using client ASNs as a proxy.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/1DhUbNv8cjQVGOqKUai7KU/fecc8eaa488ec216bfb14084a518501b/image9.png" />
          </figure><p>The plot above shows that non-DC (user-driven) traffic has slightly higher request counts per connection, consistent with browsers or apps fetching multiple resources over a single persistent connection, with a mean of 5 requests and a 90th percentile of 5 requests per connection. In contrast, DC-originated traffic has a mean of roughly 3 requests and a 90th percentile of 2, validating our expectation. Despite these differences, the median number of requests remains 1 for both groups highlighting that, regardless of origin of connections, most are genuinely brief.</p>
    <div>
      <h2>Inferring path characteristics from connection-level data</h2>
      <a href="#inferring-path-characteristics-from-connection-level-data">
        
      </a>
    </div>
    <p>Connection-level measurements can also provide insights into underlying path characteristics. Let’s examine this in more detail.</p>
    <div>
      <h3>Path MTU</h3>
      <a href="#path-mtu">
        
      </a>
    </div>
    <p>The maximum transmission unit (<a href="https://www.cloudflare.com/learning/network-layer/what-is-mtu/"><u>MTU</u></a>) along the network path is often referred to as the Path MTU (PMTU). PMTU determines the largest packet size that can traverse a connection without fragmentation or packet drop, affecting throughput, efficiency, and latency. The Linux TCP stack on our servers tracks the largest segment size that can be sent without fragmentation along the path for a connection, as part of <a href="https://blog.cloudflare.com/path-mtu-discovery-in-practice/"><u>Path MTU discovery.</u></a></p><p>From that data we saw that the median (and the 90th percentile!) PMTU was 1500 bytes, which aligns with the typical Ethernet MTU and is <a href="https://en.wikipedia.org/wiki/Maximum_transmission_unit"><u>considered standard</u></a> for most Internet paths. Interestingly, the 10th percentile sits at 1,420 bytes, reflecting cases where paths include network links with slightly smaller MTUs—common in some <a href="https://blog.cloudflare.com/migrating-from-vpn-to-access/"><u>VPNs</u></a>, <a href="https://blog.cloudflare.com/increasing-ipv6-mtu/"><u>IPv6tov4 tunnels</u></a>, or older networking equipment that impose stricter limits to avoid fragmentation. At the extreme, we have seen MTU as small as 552 bytes for IPv4 connections which relates to the minimum allowed PMTU value <a href="https://www.kernel.org/doc/html/v6.5/networking/ip-sysctl.html#:~:text=Default%3A%20FALSE-,min_pmtu,-%2D%20INTEGER"><u>by the Linux kernel</u></a>.</p>
    <div>
      <h3>Initial congestion window</h3>
      <a href="#initial-congestion-window">
        
      </a>
    </div>
    <p>A key parameter in transport protocols is the congestion window (CWND), which is the number of packets that can be transmitted without waiting for an acknowledgement from the receiver. We call these packets or bytes “in-flight.” During a connection, the congestion window evolves dynamically throughout a connection.</p><p>However, the initial congestion window (ICWND) at the start of a data transfer can have an outsized impact, especially for short-lived connections, which dominate Internet traffic as we’ve seen above. If the ICWND is set too low, small and medium transfers take additional round-trip times to reach bottleneck bandwidth, slowing delivery. Conversely, if it’s too high, the sender risks overwhelming the network, causing unnecessary packet loss and retransmissions — potentially for all connections that share the bottleneck link.</p><p>A reasonable estimate of the ICWND can be taken as the congestion window size at the instant the TCP sender transitions out of <a href="https://www.rfc-editor.org/rfc/rfc5681#section-3.1"><u>slow start</u></a>. This transition marks the point at which the sender shifts from exponential growth to congestion-avoidance, having inferred that further growth may risk congestion. The figure below shows the distribution of congestion window sizes at the moment slow start exits — as calculated by <a href="https://blog.cloudflare.com/http-2-prioritization-with-nginx/#bbr-congestion-control"><u>BBR</u></a>. The median is roughly 464 KB, which corresponds to about 310 packets per connection with a typical 1,500-byte MTU, while extreme flows carry tens of megabytes in flight. This variance reflects the diversity of TCP connections and the dynamically evolving nature of the networks carrying traffic.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/7BzqE6HSQgkriWisqS3Yx3/de4dc12a453d162884e9a015ccb40348/image4.png" />
          </figure><p>It’s important to emphasize that these values reflect a mix of network paths, including not only paths between Cloudflare and end users, but also between Cloudflare and neighboring datacenters, which are typically well provisioned and offer higher bandwidth.</p><p>Our initial inspection of the above distribution left us doubtful, because the values seem very high. We then realized the numbers are an artifact of behaviour specific to BBR, in which it sets the congestion window higher than its estimate of the path’s available capacity, <a href="https://en.wikipedia.org/wiki/Bandwidth-delay_product"><u>bandwidth delay product (BDP)</u></a>. The inflated value is <a href="https://www.ietf.org/archive/id/draft-cardwell-iccrg-bbr-congestion-control-01.html#name-state-machine-operation"><u>by design</u></a>. To prove the hypothesis, we re-plot the distribution from above in the figure below alongside BBR’s estimate of BDP. The difference is clear between BBR’s congestion window of unacknowledged packets and its BDP estimate.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/34YFSv4Zdp82qszNM79XsH/3c147dfd5c5006fe55abb53dab47bef1/image10.png" />
          </figure><p>The above plot adds the computed BDP values in context with connection telemetry. The median BDP comes out to be roughly 77 KB, which is roughly 50 packets. If we compare this to the congestion window distribution taken from above, we see BDP estimations from recently closed connections are much more stable.</p><p>We are using these insights to help identify reasonable initial congestion window sizes and the circumstances for them. Our own experiments internally make clear that ICWND sizes can affect performance by as much as 30-40% for smaller connections. Such insights will potentially help to revisit efforts to find better initial congestion window values, which has been a default of <a href="https://datatracker.ietf.org/doc/html/rfc6928"><u>10 packets</u></a> for more than a decade.</p>
    <div>
      <h3>Deeper understanding, better performance</h3>
      <a href="#deeper-understanding-better-performance">
        
      </a>
    </div>
    <p>We observed that Internet connections are highly heterogeneous, confirming decades-long observations of strong heavy-tail characteristics consistent with “<a href="https://en.wikipedia.org/wiki/Elephant_flow"><u>elephants and mice</u></a>” phenomenon. Ratios of upload to download bytes are unsurprising for larger flows, but surprisingly small for short flows, highlighting the asymmetric nature of Internet traffic. Understanding these connection characteristics continues to inform ways to improve connection performance, reliability, and user experience.</p><p>We will continue to build on this work, and plan to publish connection-level statistics on <a href="https://radar.cloudflare.com/"><u>Cloudflare Radar</u></a> so that others can similarly benefit.</p><p>Our work on improving our network is ongoing, and we welcome researchers, academics, <a href="https://blog.cloudflare.com/cloudflare-1111-intern-program/"><u>interns</u></a>, and anyone interested in this space to reach out at <a><u>ask-research@cloudflare.com</u></a>. By sharing knowledge and working together, we all can continue to make the Internet faster, safer, and more reliable for everyone.</p> ]]></content:encoded>
            <category><![CDATA[Research]]></category>
            <category><![CDATA[Better Internet]]></category>
            <category><![CDATA[Insights]]></category>
            <category><![CDATA[TCP]]></category>
            <guid isPermaLink="false">5jyi6dhHiLQu3BVMVGKrVG</guid>
            <dc:creator>Suleman Ahmad</dc:creator>
            <dc:creator>Peter Wu</dc:creator>
        </item>
        <item>
            <title><![CDATA[Reducing double spend latency from 40 ms to < 1 ms on privacy proxy]]></title>
            <link>https://blog.cloudflare.com/reducing-double-spend-latency-from-40-ms-to-less-than-1-ms-on-privacy-proxy/</link>
            <pubDate>Tue, 05 Aug 2025 13:00:00 GMT</pubDate>
            <description><![CDATA[ We significantly sped up our privacy proxy service by fixing a 40ms delay in "double-spend" checks. ]]></description>
            <content:encoded><![CDATA[ <p>One of Cloudflare’s big focus areas is making the Internet faster for end users. Part of the way we do that is by looking at the "big rocks" or bottlenecks that might be slowing things down — particularly processes on the critical path. When we recently turned our attention to our privacy proxy product, we found a big opportunity for improvement.</p><p>What is our privacy proxy product? These proxies let users browse the web without exposing their personal information to the websites they’re visiting. Cloudflare runs infrastructure for privacy proxies like <a href="https://blog.cloudflare.com/icloud-private-relay/"><u>Apple’s Private Relay</u></a> and <a href="https://blog.cloudflare.com/cloudflare-now-powering-microsoft-edge-secure-network/"><u>Microsoft’s Edge Secure Network</u></a>.</p><p>Like any secure infrastructure, we make sure that users authenticate to these privacy proxies before we open up a connection to the website they’re visiting. In order to do this in a privacy-preserving way (so that Cloudflare collects the least possible information about end-users) we use an open Internet standard – <a href="https://www.rfc-editor.org/rfc/rfc9578.html">Privacy Pass </a>– to issue tokens that authenticate to our proxy service.</p><p>Every time a user visits a website via our Privacy Proxy, we check the validity of the Privacy Pass token which is included in the Proxy-Authorization header in their request. Before we cryptographically validate a user's token, we check if this token has already been spent. If the token is unspent, we let the user request through. Otherwise, it’s a "double-spend". From an access control perspective, double-spends are indicative of a problem. From a privacy perspective, double-spends can reduce the anonymity set and privacy characteristics. From a performance perspective, our privacy proxies see millions of requests per second – and any time spent authenticating delays people from accessing sites – so the check needs to be fast. Let’s see how we reduced the latency of these double-spend checks from ~40 ms to &lt;1 ms.</p>
    <div>
      <h2>How did we discover the issue?</h2>
      <a href="#how-did-we-discover-the-issue">
        
      </a>
    </div>
    <p>We use a tracing platform, <a href="https://www.jaegertracing.io/"><u>Jaeger</u></a>. It lets us see which paths our code took and how long functions took to run. When we looked into these traces, we saw latencies of ~ 40 ms. It was a good lead, but it alone was not enough to conclude it was an issue. The reason was we only sample a small percentage of our traces, so what we saw was not the whole picture. We needed to look at more data. We could’ve increased how many traces we sampled, but traces are large and heavy for our systems to process. Metrics are a lighter weight solution. We added metrics to get data on all double-spend checks.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/67v4incoE8gXu22EBSLnN0/3c5fbd6b44ccc25398c905889b61c05e/image4.png" />
          </figure><p>The lines in this graph are median latencies we saw for the slowest privacy proxies around the world. The metrics data gave us confidence that it was a problem affecting a large portion of requests… assuming that ~ 45 ms was longer than expected. But, was it expected? What numbers did we expect?</p>
    <div>
      <h2>The expected latency</h2>
      <a href="#the-expected-latency">
        
      </a>
    </div>
    <p>To understand what times are reasonable to expect, let’s go into detail on what makes up a “double-spend check”. When we do a double-spend check, we ask a backing data store if a Privacy Pass token exists. The data store we use is <a href="https://memcached.org/"><code><u>memcached</u></code></a>. We have many <code>memcached</code> instances running on servers around the world, so which server do we ask? For this, we use <a href="https://github.com/facebook/mcrouter"><code><u>mcrouter</u></code></a>. Instead of figuring out which <code>memcached</code> server to ask, we give our request to <code>mcrouter</code>, and it will handle choosing a good <code>memcached</code> server to use. We looked at the median time it took for <code>mcrouter</code> to process our request. This graph shows the average latencies per server over time. There are spikes, but most of the time the latency is &lt; 1 ms. </p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/7LHxvtd813oeu1DyFh7MOA/0126ceb6212b50e8deeeffabba57e3e5/image1.png" />
          </figure><p>By this point, we were confident that double-spend check latencies were longer than expected everywhere, and we started looking for the root cause.</p>
    <div>
      <h2>How did we investigate the issue?</h2>
      <a href="#how-did-we-investigate-the-issue">
        
      </a>
    </div>
    <p>We took inspiration from the scientific method. We analyzed our code, created theories for why sections of code caused latency, and used data to reject those theories. For any remaining theories, we implemented fixes and tested if they worked.</p><p>Let’s look at the code. At a high level, the double-spend checking logic is:</p><ol><li><p>Get a connection, which can be broken down into:</p><ol><li><p>Send a <code>memcached version</code> command. This serves as a health check for whether the connection is still good to send data on.</p></li><li><p>If the connection is still good, acquire it. Otherwise, establish a new connection.</p></li></ol></li><li><p>Send a <code>memcached get</code> command on the connection.</p></li></ol><p>Let’s go through the theories we had for each step listed above.</p>
    <div>
      <h2>Theory 1: health check takes long</h2>
      <a href="#theory-1-health-check-takes-long">
        
      </a>
    </div>
    <p>We measured the health check primarily as a sanity check. The version command is simple and fast to process, so it should not take long. And we remained sane. The median latency was &lt; 1 ms.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/6amdAWUKl3IvmGlvgwJhMP/57b6895aacf960b08ffc7d36d4569d25/image5.png" />
          </figure>
    <div>
      <h2>Theory 2: waiting to get a connection</h2>
      <a href="#theory-2-waiting-to-get-a-connection">
        
      </a>
    </div>
    <p>To understand why we may need to wait to get a connection, let’s go into more detail on how we get a connection. In our code, we use a connection pool. The pool is a set of ready-to-go connections to <code>mcrouter</code>. The benefit of having a pool is that we do not have to pay the overhead of establishing a connection every time we want to make a request. Pools have a size limit, though. Our limit was 20 per server, and this is where a potential problem lies. Imagine we have a server that processes 5,000 requests every second, and requests stay for 45 ms. We can use something called <a href="https://en.wikipedia.org/wiki/Little%27s_law"><u>Little’s Law</u></a> to estimate the average number of requests in our system: <code>5000 x 0.045 = 225</code>. Due to our pool size limits, we can only have 20 connections at a time, so we can only process 20 requests at any point in time. That means 205 requests are just waiting! When we do a double-spend check, maybe we’re waiting ~ 40 ms to get a connection?</p><p>We looked at the metrics of many different servers. No matter what the requests per second was, the latency was consistently ~ 40 ms, disproving the theory. For example, this graph shows data from a server that saw a maximum of 20 requests per second. It shows a histogram over time, and the large majority of requests fall in the 40 - 50 ms bucket.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/1EJ7SlTzqMVLTIOTvqH1HL/7d64c441e606ecbe1823585f4ff19086/image7.png" />
          </figure>
    <div>
      <h2>Theory 3: delays in Nagle’s algorithm and delayed acks</h2>
      <a href="#theory-3-delays-in-nagles-algorithm-and-delayed-acks">
        
      </a>
    </div>
    <p>We decided to chat with Gemini, giving it the observations we had so far. It suggested many things, but the most interesting was to check if <code>TCP_NODELAY</code> was set. If we had set this option in our code, it would’ve disabled something called <a href="https://en.wikipedia.org/wiki/Nagle%27s_algorithm"><u>Nagle’s algorithm</u></a>. Nagle’s algorithm itself was not a problem, but when enabled alongside another feature, <a href="https://en.wikipedia.org/wiki/TCP_delayed_acknowledgment"><u>delayed ACKs</u></a>, latencies could creep in. To explain why, let’s go through an analogy.</p><p>Suppose we run a group chat app. Normally, people type a full thought and send it in one message. But, we have a friend who sends one word at a time: "Hi". Send. "how". Send. "are". Send. “you”. Send. That’s a lot of notifications. Nagle’s algorithm aims to prevent this. Nagle says that if the friend wants to send one short message, that’s fine, but it only lets them do it once per turn. When they try to send more single words right after, Nagle will save the words in a draft message. Once the draft message hits a certain length, Nagle sends. But what if the draft message never hits that length? To manage this, delayed ACKs initiates a 40 ms timer whenever the friend sends a message. If the app gets no further input before the timer ends, the message is sent to the group.</p><p>I took a closer look at the code, both Cloudflare authored code and code from dependencies we rely on. We depended on the <a href="https://crates.io/crates/memcache-async"><code>memcache-async</code></a> crate for implementing the code that lets us send <code>memcache</code> commands. Here is the code for sending a <code>memcached version</code> command:</p>
            <pre><code>self.io.write_all(b"version\r\n").await?;
self.io.flush().await?;</code></pre>
            <p>Nothing out of the ordinary. Then, we looked inside the get function.</p>
            <pre><code>let writer = self.io.get_mut();
writer.write_all(b"get ").await?;
writer.write_all(key.as_ref()).await?;
writer.write_all(b"\r\n").await?;
writer.flush().await?;</code></pre>
            <p>In our code, we set <code>io</code> as a <code>TcpStream</code>, meaning that each <code>write_all</code> call resulted in sending a message. With Nagle’s algorithm enabled, the data flow looked like this:</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/6Vj6xfkbnIg2gmPeLy9g9I/2b003d8a4d81782148697fc83e793c6f/Screenshot_2025-07-24_at_13.16.05.png" />
          </figure><p>Oof. We tried to send all three small messages, but after we sent the “get “, the kernel put the token and <code>\r\n</code> in a buffer and started waiting. When <code>mcrouter</code> got the “get “, it could not do anything because it did not have the full command. So, it waited 40 ms. Then, it sent an ACK in response. We got the ACK, and sent the rest of the command in the buffer. <code>mcrouter</code> got the rest of the command, processed it, and returned a response telling us if the token exists. What would the data flow look like with Nagle’s algorithm disabled?</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/4O0s8hb64olT2PDDc5wTFL/3cfe500a1f235276502db9e608cef966/Screenshot_2025-07-24_at_13.17.11.png" />
          </figure><p>We would send all three small messages. <code>mcrouter</code> would have the full command, and return a response immediately. No waiting, whatsoever.</p>
    <div>
      <h2>Why 40 ms?</h2>
      <a href="#why-40-ms">
        
      </a>
    </div>
    <p>Our Linux servers have minimum bounds for the delay. Here is a snippet of Linux source code that defines those bounds.</p>
            <pre><code>#if HZ &gt;= 100
#define TCP_DELACK_MIN	((unsigned)(HZ/25))	/* minimal time to delay before sending an ACK */
#define TCP_ATO_MIN	((unsigned)(HZ/25))
#else
#define TCP_DELACK_MIN	4U
#define TCP_ATO_MIN	4U
#endif</code></pre>
            <p>The comment tells us that <code>TCP_DELACK_MIN</code> is the minimum time delayed ACKs will wait before sending an ACK. We spent some time digging through Cloudflare’s custom kernel settings and found this:</p>
            <pre><code>CONFIG_HZ=1000</code></pre>
            <p><code>CONFIG_HZ</code> eventually propagates to <code>HZ</code> and results in a 40 ms delay. That's where the number comes from!</p>
    <div>
      <h2>The fix</h2>
      <a href="#the-fix">
        
      </a>
    </div>
    <p>We were sending three separate messages for a single command when we only needed to send one. We captured what a <code>get</code> command looked like in Wireshark to verify we were sending three separate messages. (We captured this locally on MacOS. Interestingly, we got an ACK for every message.)</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/4B2qC70Dpeu25dTOP4V2hj/3720d8012f7d452696ca6cbe265d366e/image9.png" />
          </figure><p>The fix was to use <code>BufWriter&lt;TcpStream&gt;</code> so that <code>write_all</code> would buffer the small messages in a user-space memory buffer, and <code>flush</code> would send the entire <code>memcached</code> command in one message. The Wireshark capture looked much cleaner.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/5S6R7qAIad9pjKIfQYTWbA/c7bfe663b707ba4653977319a02e5e07/image3.png" />
          </figure>
    <div>
      <h2>Conclusion</h2>
      <a href="#conclusion">
        
      </a>
    </div>
    <p>After deploying the fix to production, we saw the median double-spend check latency drop to expected values everywhere.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/4kKCQFTw5wp0jEwdPcALb4/8425bcfe526c2eeb9570c7a98fc62c62/image8.png" />
          </figure><p>Our investigation followed a systematic, data-driven approach. We began by using observability tools to confirm the problem's scale. From there, we formed testable hypotheses and used data to systematically disprove them. This process ultimately led us to a subtle interaction between Nagle’s algorithm and delayed ACKs, caused by how we made use of a third-party dependency.</p><p>Ultimately, our mission is to help build a better Internet. Every millisecond saved contributes to a faster and more seamless, private browsing experience for end users. We're excited to have this rolled out and excited to continue to chase further performance improvements!</p> ]]></content:encoded>
            <category><![CDATA[Privacy]]></category>
            <category><![CDATA[Privacy Pass]]></category>
            <category><![CDATA[Performance]]></category>
            <category><![CDATA[TCP]]></category>
            <guid isPermaLink="false">29xmM9UQ1WEQlV0SiAuM2l</guid>
            <dc:creator>Ben Yang</dc:creator>
        </item>
        <item>
            <title><![CDATA[Multi-Path TCP: revolutionizing connectivity, one path at a time]]></title>
            <link>https://blog.cloudflare.com/multi-path-tcp-revolutionizing-connectivity-one-path-at-a-time/</link>
            <pubDate>Fri, 03 Jan 2025 14:00:00 GMT</pubDate>
            <description><![CDATA[ Multi-Path TCP (MPTCP) leverages multiple network interfaces, like Wi-Fi and cellular, to provide seamless mobility for more reliable connectivity. While promising, MPTCP is still in its early stages, ]]></description>
            <content:encoded><![CDATA[ <p></p><p>The Internet is designed to provide multiple paths between two endpoints. Attempts to exploit multi-path opportunities are almost as old as the Internet, culminating in <a href="https://datatracker.ietf.org/doc/html/rfc2991"><u>RFCs</u></a> documenting some of the challenges. Still, today, virtually all end-to-end communication uses only one available path at a time. Why? It turns out that in multi-path setups, even the smallest differences between paths can harm the connection quality due to <a href="https://en.wikipedia.org/wiki/Equal-cost_multi-path_routing#History"><u>packet reordering</u></a> and other issues. As a result, Internet devices usually use a single path and let the routers handle the path selection.</p><p>There is another way. Enter Multi-Path TCP (MPTCP), which exploits the presence of multiple interfaces on a device, such as a mobile phone that has both Wi-Fi and cellular antennas, to achieve multi-path connectivity.</p><p>MPTCP has had a long history — see the <a href="https://en.wikipedia.org/wiki/Multipath_TCP"><u>Wikipedia article</u></a> and the <a href="https://datatracker.ietf.org/doc/html/rfc8684"><u>spec (RFC 8684)</u></a> for details. It's a major extension to the TCP protocol, and historically most of the TCP changes failed to gain traction. However, MPTCP is supposed to be mostly an operating system feature, making it easy to enable. Applications should only need minor code changes to support it.</p><p>There is a caveat, however: MPTCP is still fairly immature, and while it can use multiple paths, giving it superpowers over regular TCP, it's not always strictly better than it. Whether MPTCP should be used over TCP is really a case-by-case basis.</p><p>In this blog post we show how to set up MPTCP to find out.</p>
    <div>
      <h2>Subflows</h2>
      <a href="#subflows">
        
      </a>
    </div>
    
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/3r8AP5BHbvtYEtXmYSXFwO/36e95cbac93cdecf2f5ee65945abf0b3/Screenshot_2024-12-23_at_3.07.37_PM.png" />
          </figure><p>Internally, MPTCP extends TCP by introducing "subflows". When everything is working, a single TCP connection can be backed by multiple MPTCP subflows, each using different paths. This is a big deal - a single TCP byte stream is now no longer identified by a single 5-tuple. On Linux you can see the subflows with <code>ss -M</code>, like:</p>
            <pre><code>marek$ ss -tMn dport = :443 | cat
tcp   ESTAB 0  	0 192.168.2.143%enx2800af081bee:57756 104.28.152.1:443
tcp   ESTAB 0  	0       192.168.1.149%wlp0s20f3:44719 104.28.152.1:443
mptcp ESTAB 0  	0                 192.168.2.143:57756 104.28.152.1:443</code></pre>
            <p>Here you can see a single MPTCP connection, composed of two underlying TCP flows.</p>
    <div>
      <h2>MPTCP aspirations</h2>
      <a href="#mptcp-aspirations">
        
      </a>
    </div>
    <p>Being able to separate the lifetime of a connection from the lifetime of a flow allows MPTCP to address two problems present in classical TCP: aggregation and mobility.</p><ul><li><p><b>Aggregation</b>: MPTCP can aggregate the bandwidth of many network interfaces. For example, in a data center scenario, it's common to use interface bonding. A single flow can make use of just one physical interface. MPTCP, by being able to launch many subflows, can expose greater overall bandwidth. I'm personally not convinced if this is a real problem. As we'll learn below, modern Linux has a <a href="https://dl.ifip.org/db/conf/networking/networking2016/1570234725.pdf"><u>BLESS-like MPTCP scheduler</u></a> and macOS stack has the "aggregation" mode, so aggregation should work, but I'm not sure how practical it is. However, there are <a href="https://www.openmptcprouter.com/"><u>certainly projects that are trying to do link aggregation</u></a> using MPTCP.</p></li><li><p><b>Mobility</b>: On a customer device, a TCP stream is typically broken if the underlying network interface goes away. This is not an uncommon occurrence — consider a smartphone dropping from Wi-Fi to cellular. MPTCP can fix this — it can create and destroy many subflows over the lifetime of a single connection and survive multiple network changes.</p></li></ul><p>Improving reliability for mobile clients is a big deal. While some software can use QUIC, which also works on <a href="https://www.ietf.org/archive/id/draft-ietf-quic-multipath-11.html"><u>Multipath Extensions</u></a>, a large number of classical services still use TCP. A great example is SSH: it would be very nice if you could walk around with a laptop and keep an SSH session open and switch Wi-Fi networks seamlessly, without breaking the connection.</p><p>MPTCP work was initially driven by <a href="https://uclouvain.be/fr/index.html"><u>UCLouvain in Belgium</u></a>. The first serious adoption was on the iPhone. Apparently, users have a tendency to use Siri while they are walking out of their home. It's very common to lose Wi-Fi connectivity while they are doing this. (<a href="https://youtu.be/BucQ1lfbtd4?t=533"><u>source</u></a>) </p>
    <div>
      <h2>Implementations</h2>
      <a href="#implementations">
        
      </a>
    </div>
    <p>Currently, there are only two major MPTCP implementations — Linux kernel support from v5.6, but realistically you need at least kernel v6.1 (<a href="https://oracle.github.io/kconfigs/?config=UTS_RELEASE&amp;config=MPTCP"><u>MPTCP is not supported on Android</u></a> yet) and iOS from version 7 / Mac OS X from 10.10.</p><p>Typically, Linux is used on the server side, and iOS/macOS as the client. It's possible to get Linux to work as a client-side, but it's not straightforward, as we'll learn soon. Beware — there is plenty of outdated Linux MPTCP documentation. The code has had a bumpy history and at least two different APIs were proposed. See the Linux kernel source for <a href="https://docs.kernel.org/networking/mptcp.html"><u>the mainline API</u></a> and the <a href="https://www.mptcp.dev/"><u>mptcp.dev</u></a> website.</p>
    <div>
      <h2>Linux as a server</h2>
      <a href="#linux-as-a-server">
        
      </a>
    </div>
    <p>Conceptually, the MPTCP design is pretty sensible. After the initial TCP handshake, each peer may announce additional addresses (and ports) on which it can be reached. There are two ways of doing this. First, in the handshake TCP packet each peer specifies the "<i>Do not attempt to establish new subflows to this address and port</i>" bit, also known as bit [C], in the MPTCP TCP extensions header.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/bT8oz3wxpw7alftvdYg5n/b7614a4d10b6c81e18027f6785391ede/BLOG-2637_3.png" />
          </figure><p><sup><i>Wireshark dissecting MPTCP flags from a SYN packet. </i></sup><a href="https://github.com/multipath-tcp/mptcp_net-next/issues/535"><sup><i><u>Tcpdump does not report</u></i></sup></a><sup><i> this flag yet.</i></sup></p><p>With this bit cleared, the other peer is free to assume the two-tuple is fine to be reconnected to. Typically, the <b>server allows</b> the client to reuse the server IP/port address. Usually, the <b>client is not listening</b> and disallows the server to connect back to it. There are caveats though. For example, in the context of Cloudflare, where our servers are using Anycast addressing, reconnecting to the server IP/port won't work. Going twice to the IP/port pair is unlikely to reach the same server. For us it makes sense to set this flag, disallowing clients from reconnecting to our server addresses. This can be done on Linux with:</p>
            <pre><code># Linux server sysctl - useful for ECMP or Anycast servers
$ sysctl -w net.mptcp.allow_join_initial_addr_port=0
</code></pre>
            <p>There is also a second way to advertise a listening IP/port. During the lifetime of a connection, a peer can send an ADD-ADDR MPTCP signal which advertises a listening IP/port. This can be managed on Linux by <code>ip mptcp endpoint ... signal</code>, like:</p>
            <pre><code># Linux server - extra listening address
$ ip mptcp endpoint add 192.51.100.1 dev eth0 port 4321 signal
</code></pre>
            <p>With such a config, a Linux peer (typically server) will report the additional IP/port with ADD-ADDR MPTCP signal in an ACK packet, like this:</p>
            <pre><code>host &gt; host: Flags [.], ack 1, win 8, options [mptcp 30 add-addr v1 id 1 192.51.100.1:4321 hmac 0x...,nop,nop], length 0
</code></pre>
            <p>It's important to realize that either peer can send ADD-ADDR messages. Unusual as it might sound, it's totally fine for the client to advertise extra listening addresses. The most common scenario though, consists of either nobody, or just a server, sending ADD-ADDR.</p><p>Technically, to launch an MPTCP socket on Linux, you just need to replace IPPROTO_TCP with IPPROTO_MPTCP in the application code:</p>
            <pre><code>IPPROTO_MPTCP = 262
sd = socket(AF_INET, SOCK_STREAM, IPPROTO_MPTCP)
</code></pre>
            <p>In practice, though, this introduces some changes to the sockets API. Currently not all setsockopt's work yet — like <code>TCP_USER_TIMEOUT</code>. Additionally, at this stage, MPTCP is incompatible with kTLS.</p>
    <div>
      <h2>Path manager / scheduler</h2>
      <a href="#path-manager-scheduler">
        
      </a>
    </div>
    <p>Once the peers have exchanged the address information, MPTCP is ready to kick in and perform the magic. There are two independent pieces of logic that MPTCP handles. First, given the address information, MPTCP must figure out if it should establish additional subflows. The component that decides on this is called "Path Manager". Then, another component called "scheduler" is responsible for choosing a specific subflow to transmit the data over.</p><p>Both peers have a path manager, but typically only the client uses it. A path manager has a hard task to launch enough subflows to get the benefits, but not too many subflows which could waste resources. This is where the MPTCP stacks get complicated. </p>
    <div>
      <h2>Linux as client</h2>
      <a href="#linux-as-client">
        
      </a>
    </div>
    <p>On Linux, path manager is an operating system feature, not an application feature. The in-kernel path manager requires some configuration — it must know which IP addresses and interfaces are okay to start new subflows. This is configured with <code>ip mptcp endpoint ... subflow</code>, like:</p>
            <pre><code>$ ip mptcp endpoint add dev wlp1s0 192.0.2.3 subflow  # Linux client
</code></pre>
            <p>This informs the path manager that we (typically a client) own a 192.0.2.3 IP address on interface wlp1s0, and that it's fine to use it as source of a new subflow. There are two additional flags that can be passed here: "backup" and "fullmesh". Maintaining these <code>ip mptcp endpoints</code> on a client is annoying. They need to be added and removed every time networks change. Fortunately, <a href="https://ubuntu.com/core/docs/networkmanager"><u>NetworkManager</u></a> from 1.40 supports managing these by default. If you want to customize the "backup" or "fullmesh" flags, you can do this here (see <a href="https://networkmanager.dev/docs/api/1.44.4/settings-connection.html#:~:text=mptcp-flags"><u>the documentation</u></a>):</p>
            <pre><code>ubuntu$ cat /etc/NetworkManager/conf.d/95-mptcp.conf
# set "subflow" on all managed "ip mptcp endpoints". 0x22 is the default.
[connection]
connection.mptcp-flags=0x22
</code></pre>
            <p>Path manager also takes a "limit" setting, to set a cap of additional subflows per MPTCP connection, and limit the received ADD-ADDR messages, like: </p>
            <pre><code>$ ip mptcp limits set subflow 4 add_addr_accepted 2  # Linux client
</code></pre>
            <p>I experimented with the "mobility" use case on my Ubuntu 22 Linux laptop. I repeatedly enabled and disabled Wi-Fi and Ethernet. On new kernels (v6.12), it works, and I was able to hold a reliable MPTCP connection over many interface changes. I was <a href="https://github.com/multipath-tcp/mptcp_net-next/issues/534"><u>less lucky with the Ubuntu v6.8</u></a> kernel. Unfortunately, the <a href="https://github.com/multipath-tcp/mptcp_net-next/issues/536"><u>default path manager on Linux</u></a> client only works when the flag "<i>Do not attempt to establish new subflows to this address and port</i>" is cleared on the server. Server-announced ADD-ADDR don't result in new subflows created, unless <code>ip mptcp endpoint</code> has a <code>fullmesh</code> flag.</p><p>It feels like the underlying MPTCP transport code works, but the path manager requires a bit more intelligence. With a new kernel, it's possible to get the "interactive" case working out of the box, but not for the ADD-ADDR case. </p>
    <div>
      <h2>Custom path manager</h2>
      <a href="#custom-path-manager">
        
      </a>
    </div>
    <p>Linux allows for two implementations of a path manager component. It can either use built-in kernel implementation (default), or userspace netlink daemon.</p>
            <pre><code>$ sysctl -w net.mptcp.pm_type=1 # use userspace path manager
</code></pre>
            <p>However, from what I found there is no serious implementation of configurable userspace path manager. The existing <a href="https://github.com/multipath-tcp/mptcpd/blob/main/plugins/path_managers/sspi.c"><u>implementations don't do much</u></a>, and the API <a href="https://github.com/multipath-tcp/mptcp_net-next/issues/533"><u>seems</u></a> <a href="https://github.com/multipath-tcp/mptcp_net-next/issues/532"><u>immature</u></a> yet.</p>
    <div>
      <h2>Scheduler and BPF extensions</h2>
      <a href="#scheduler-and-bpf-extensions">
        
      </a>
    </div>
    <p>Thus far we've covered Path Manager, but what about the scheduler that chooses which link to actually use? It seems that on Linux there is only one built-in "default" scheduler, and it can do basic failover on packet loss. The developers want to write <a href="https://github.com/multipath-tcp/mptcp_net-next/issues/75"><u>MPTCP schedulers in BPF</u></a>, and this work is in-progress.</p>
    <div>
      <h2>macOS</h2>
      <a href="#macos">
        
      </a>
    </div>
    <p>As opposed to Linux, macOS and iOS expose a raw MPTCP API. On those operating systems, path manager is not handled by the kernel, but instead can be an application responsibility. The exposed low-level API is based on <code>connectx()</code>. For example, <a href="https://github.com/apple-oss-distributions/network_cmds/blob/97bfa5b71464f1286b51104ba3e60db78cd832c9/mptcp_client/mptcp_client.c#L461"><u>here's an example of obscure code</u></a> that establishes one connection with two subflows:</p>
            <pre><code>int sock = socket(AF_MULTIPATH, SOCK_STREAM, 0);
connectx(sock, ..., &amp;cid1);
connectx(sock, ..., &amp;cid2);
</code></pre>
            <p>This powerful API is hard to use though, as it would require every application to listen for network changes. Fortunately, macOS and iOS also expose higher-level APIs. One <a href="https://github.com/mptcp-apps/mptcp-hello/blob/main/c/macOS/main.c"><u>example is nw_connection</u></a> in C, which uses nw_parameters_set_multipath_service.</p><p>Another, more common example is using <code>Network.framework</code>, and would <a href="https://gist.github.com/majek/cb54b537c74506164d2a7fa2d6601491"><u>look like this</u></a>:</p>
            <pre><code>let parameters = NWParameters.tcp
parameters.multipathServiceType = .interactive
let connection = NWConnection(host: host, port: port, using: parameters) 
</code></pre>
            <p>The API supports three MPTCP service type modes:</p><ul><li><p><i>Handover Mode</i>: Tries to minimize cellular. Uses only Wi-Fi. Uses cellular only when <a href="https://support.apple.com/en-us/102228"><u>Wi-Fi Assist</u></a> is enabled and makes such a decision.</p></li><li><p><i>Interactive Mode</i>: Used for Siri. Reduces latency. Only for low-bandwidth flows.</p></li><li><p><i>Aggregation Mode</i>: Enables resource pooling but it's only available for developer accounts and not deployable.</p></li></ul>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/47MukOs6bhCMOkO1JL15sP/7dd75417b855b681bde504122d5af01e/Screenshot_2024-12-23_at_2.59.51_PM.png" />
          </figure><p>The MPTCP API is nicely integrated with the <a href="https://support.apple.com/en-us/102228"><u>iPhone "Wi-Fi Assist" feature</u></a>. While the official documentation is lacking, it's possible to find <a href="https://youtu.be/BucQ1lfbtd4?t=533"><u>sources explaining</u></a> how it actually works. I was able to successfully test both the cleared "<i>Do not attempt to establish new subflows"</i> bit and ADD-ADDR scenarios. Hurray!</p>
    <div>
      <h2>IPv6 caveat</h2>
      <a href="#ipv6-caveat">
        
      </a>
    </div>
    <p>Sadly, MPTCP IPv6 has a caveat. Since IPv6 addresses are long, and MPTCP uses the space-constrained TCP Extensions field, there is <a href="https://github.com/multipath-tcp/mptcp_net-next/issues/448"><u>not enough room for ADD-ADDR messages</u></a> if TCP timestamps are enabled. If you want to use MPTCP and IPv6, it's something to consider.</p>
    <div>
      <h2>Summary</h2>
      <a href="#summary">
        
      </a>
    </div>
    <p>I find MPTCP very exciting, being one of a few deployable serious TCP extensions. However, current implementations are limited. My experimentation showed that the only practical scenario where currently MPTCP might be useful is:</p><ul><li><p>Linux as a server</p></li><li><p>macOS/iOS as a client</p></li><li><p>"interactive" use case</p></li></ul><p>With a bit of effort, Linux can be made to work as a client.</p><p>Don't get me wrong, <a href="https://netdevconf.info/0x14/pub/slides/59/mptcp-netdev0x14-final.pdf"><u>Linux developers did tremendous work</u></a> to get where we are, but, in my opinion for any serious out-of-the-box use case, we're not there yet. I'm optimistic that Linux can develop a good MPTCP client story relatively soon, and the possibility of implementing the Path manager and Scheduler in BPF is really enticing. </p><p>Time will tell if MPTCP succeeds — it's been 15 years in the making. In the meantime, <a href="https://datatracker.ietf.org/meeting/121/materials/slides-121-quic-multipath-quic-00"><u>Multi-Path QUIC</u></a> is under active development, but it's even further from being usable at this stage.</p><p>We're not quite sure if it makes sense for Cloudflare to support MPTCP. <a href="https://community.cloudflare.com/c/feedback/feature-request/30"><u>Reach out</u></a> if you have a use case in mind!</p><p><i>Shoutout to </i><a href="https://fosstodon.org/@matttbe"><i><u>Matthieu Baerts</u></i></a><i> for tremendous help with this blog post.</i></p> ]]></content:encoded>
            <category><![CDATA[TCP]]></category>
            <category><![CDATA[Network]]></category>
            <category><![CDATA[Linux]]></category>
            <category><![CDATA[Deep Dive]]></category>
            <guid isPermaLink="false">6ZxrGIedGqREgTs02vpt0t</guid>
            <dc:creator>Marek Majkowski</dc:creator>
        </item>
        <item>
            <title><![CDATA[A Socket API that works across JavaScript runtimes — announcing a WinterCG spec and Node.js implementation of connect()]]></title>
            <link>https://blog.cloudflare.com/socket-api-works-javascript-runtimes-wintercg-polyfill-connect/</link>
            <pubDate>Thu, 28 Sep 2023 13:00:37 GMT</pubDate>
            <description><![CDATA[ Engineers from Cloudflare and Vercel have published a specification of the connect() sockets API for review by the community, along with a Node.js compatible implementation of connect() that developers can start using today ]]></description>
            <content:encoded><![CDATA[ <p></p><p>Earlier this year, we <a href="/workers-tcp-socket-api-connect-databases/">announced a new API for creating outbound TCP sockets</a> — <a href="https://developers.cloudflare.com/workers/runtime-apis/tcp-sockets?cf_target_id=6F3FD2F2360D5526EEE56A7398DB7D9D">connect()</a>. From day one, we’ve been working with the <a href="https://wintercg.org/">Web-interoperable Runtimes Community Group (WinterCG) community</a> to chart a course toward making this API a standard, available across all runtimes and platforms — including Node.js.</p><p>Today, we’re sharing that we’ve reached a new milestone in the path to making this API available across runtimes — engineers from Cloudflare and Vercel have published <a href="https://sockets-api.proposal.wintercg.org/">a draft specification of the connect() sockets API</a> for review by the community, along with a Node.js compatible <a href="https://github.com/Ethan-Arrowood/socket">implementation of the connect() API</a> that developers can start using today.</p><p>This implementation helps both application developers and maintainers of libraries and frameworks:</p><ol><li><p>Maintainers of existing libraries that use the <a href="https://nodejs.org/api/net.html">node:net</a> and <a href="https://nodejs.org/api/tls.html">node:tls</a> APIs can use it to more easily add support for runtimes where node:net and node:tls are not available.</p></li><li><p>JavaScript frameworks can use it to make connect() available in local development, making it easier for application developers to target runtimes that provide connect().</p></li></ol>
    <div>
      <h3>Why create a new standard? Why connect()?</h3>
      <a href="#why-create-a-new-standard-why-connect">
        
      </a>
    </div>
    <p>As we <a href="/workers-tcp-socket-api-connect-databases/">described when we first announced connect()</a>, to-date there has not been a standard API across JavaScript runtimes for creating and working with TCP or UDP sockets. This makes it harder for maintainers of open-source libraries to ensure compatibility across runtimes, and ultimately creates friction for application developers who have to navigate which libraries work on which platforms.</p><p>While Node.js provides the <a href="https://nodejs.org/api/net.html">node:net</a> and <a href="https://nodejs.org/api/tls.html">node:tls</a> APIs, these APIs were designed over 10 years ago in the very early days of the Node.js project and remain callback-based. As a result, they can be hard to work with, and expose configuration in ways that don’t fit serverless platforms or web browsers.</p><p>The connect() API fills this gap by incorporating the best parts of existing socket APIs and <a href="https://github.com/WICG/direct-sockets/blob/main/docs/explainer.md">prior proposed standards</a>, based on feedback from the JavaScript community — including contributors to Node.js. Libraries like <a href="https://www.npmjs.com/package/pg">pg</a> (<a href="https://github.com/brianc/node-postgres">node-postgres</a> on Github) are already using the connect() API.</p>
    <div>
      <h3>The connect() specification</h3>
      <a href="#the-connect-specification">
        
      </a>
    </div>
    <p>At time of writing, the <a href="https://sockets-api.proposal.wintercg.org/">draft specification of the Sockets API</a> defines the following API:</p>
            <pre><code>dictionary SocketAddress {
  DOMString hostname;
  unsigned short port;
};

typedef (DOMString or SocketAddress) AnySocketAddress;

enum SecureTransportKind { "off", "on", "starttls" };

[Exposed=*]
dictionary SocketOptions {
  SecureTransportKind secureTransport = "off";
  boolean allowHalfOpen = false;
};

[Exposed=*]
interface Connect {
  Socket connect(AnySocketAddress address, optional SocketOptions opts);
};

interface Socket {
  readonly attribute ReadableStream readable;
  readonly attribute WritableStream writable;

  readonly attribute Promise&lt;undefined&gt; closed;
  Promise&lt;undefined&gt; close();

  Socket startTls();
};</code></pre>
            <p>The proposed API is Promise-based and reuses existing standards whenever possible. For example, <a href="https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream">ReadableStream</a> and <a href="https://developer.mozilla.org/en-US/docs/Web/API/WritableStream">WritableStream</a> are used for the read and write ends of the socket. This makes it easy to pipe data from a TCP socket to any other library or existing code that accepts a ReadableStream as input, or to write to a TCP socket via a WritableStream.</p><p>The entrypoint of the API is the connect() function, which takes a string containing both the hostname and port separated by a colon, or an object with discrete hostname and port fields. It returns a Socket object which represents a socket connection. An instance of this object exposes attributes and methods for working with the connection.</p><p>A connection can be established in plain-text or TLS mode, as well as a special “starttls” mode which allows the socket to be easily upgraded to TLS after some period of plain-text data transfer, by calling the startTls() method on the Socket object. No need to create a new socket or switch to using a separate set of APIs once the socket is upgraded to use TLS.</p><p>For example, to upgrade a socket using the startTLS pattern, you might do something like this:</p>
            <pre><code>import { connect } from "@arrowood.dev/socket"

const options = { secureTransport: "starttls" };
const socket = connect("address:port", options);
const secureSocket = socket.startTls();
// The socket is immediately writable
// Relies on web standard WritableStream
const writer = secureSocket.writable.getWriter();
const encoder = new TextEncoder();
const encoded = encoder.encode("hello");
await writer.write(encoded);</code></pre>
            <p>Equivalent code using the node:net and node:tls APIs:</p>
            <pre><code>import net from 'node:net'
import tls from 'node:tls'

const socket = new net.Socket(HOST, PORT);
socket.once('connect', () =&gt; {
  const options = { socket };
  const secureSocket = tls.connect(options, () =&gt; {
    // The socket can only be written to once the
    // connection is established.
    // Polymorphic API, uses Node.js streams
    secureSocket.write('hello');
  }
})</code></pre>
            
    <div>
      <h3>Use the Node.js implementation of connect() in your library</h3>
      <a href="#use-the-node-js-implementation-of-connect-in-your-library">
        
      </a>
    </div>
    <p>To make it easier for open-source library maintainers to adopt the connect() API, we’ve published an <a href="https://github.com/Ethan-Arrowood/socket">implementation of connect() in Node.js</a> that allows you to publish your library such that it works across JavaScript runtimes, without having to maintain any runtime-specific code.</p><p>To get started, install it as a dependency:</p>
            <pre><code>npm install --save @arrowood.dev/socket</code></pre>
            <p>And import it in your library or application:</p>
            <pre><code>import { connect } from "@arrowood.dev/socket"</code></pre>
            
    <div>
      <h3>What’s next for connect()?</h3>
      <a href="#whats-next-for-connect">
        
      </a>
    </div>
    <p>The <a href="https://github.com/wintercg/proposal-sockets-api/">wintercg/proposal-sockets-api</a> is published as a draft, and the next step is to solicit and incorporate feedback. We’d love your feedback, particularly if you maintain an open-source library or make direct use of the node:net or node:tls APIs.</p><p>Once feedback has been incorporated, engineers from Cloudflare, Vercel and beyond will be continuing to work towards contributing an implementation of the API directly to Node.js as a built-in API.</p> ]]></content:encoded>
            <category><![CDATA[Birthday Week]]></category>
            <category><![CDATA[Product News]]></category>
            <category><![CDATA[Cloudflare Workers]]></category>
            <category><![CDATA[Developers]]></category>
            <category><![CDATA[TCP]]></category>
            <category><![CDATA[Node.js]]></category>
            <category><![CDATA[Developer Platform]]></category>
            <guid isPermaLink="false">6LC7InDwR6gLWapyPtL3u5</guid>
            <dc:creator>Dominik Picheta</dc:creator>
            <dc:creator>James M Snell</dc:creator>
            <dc:creator>Ethan Arrowood (Guest Author)</dc:creator>
        </item>
        <item>
            <title><![CDATA[Unbounded memory usage by TCP for receive buffers, and how we fixed it]]></title>
            <link>https://blog.cloudflare.com/unbounded-memory-usage-by-tcp-for-receive-buffers-and-how-we-fixed-it/</link>
            <pubDate>Thu, 25 May 2023 15:31:46 GMT</pubDate>
            <description><![CDATA[ We are constantly monitoring and optimizing the performance and resource utilization of our systems. Recently, we noticed that some of our TCP sessions were allocating more memory than expected. This blog post describes in detail the root cause of the problem and shows the test results of a solution ]]></description>
            <content:encoded><![CDATA[ 
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/5cIE9yVmHZqySrtOvL5RZf/5562d6abf0227dacb18cd13509fbe3f3/image14-1.png" />
            
            </figure><p>At Cloudflare, we are constantly monitoring and optimizing the performance and resource utilization of our systems. Recently, we noticed that some of our TCP sessions were allocating more memory than expected.</p><p>The Linux kernel allows <a href="https://www.cloudflare.com/learning/ddos/glossary/tcp-ip/">TCP sessions</a> that match certain characteristics to ignore memory allocation limits set by autotuning and allocate excessive amounts of memory, all the way up to net.ipv4.tcp_rmem max (the per-session limit). On Cloudflare’s production network, there are often many such TCP sessions on a server, causing the total amount of allocated TCP memory to reach net.ipv4.tcp_mem thresholds (the server-wide limit). When that happens, the kernel imposes memory use constraints on all TCP sessions, not just the ones causing the problem. Those constraints have a negative impact on throughput and latency for the user. Internally within the kernel, the problematic sessions trigger TCP collapse processing, “OFO” pruning (dropping of packets already received and sitting in the out-of-order queue), and the dropping of newly arriving packets.</p><p>This blog post describes in detail the root cause of the problem and shows the test results of a solution.</p>
    <div>
      <h2>TCP receive buffers are excessively big for some sessions</h2>
      <a href="#tcp-receive-buffers-are-excessively-big-for-some-sessions">
        
      </a>
    </div>
    <p>Our journey began when we started noticing a lot of TCP sessions on some servers with large amounts of memory allocated for receive buffers.  Receive buffers are used by Linux to hold packets that have arrived from the network but have not yet been read by the local process.</p><p>Digging into the details, we observed that most of those TCP sessions had a <a href="https://www.cloudflare.com/learning/performance/glossary/what-is-latency/">latency</a> (RTT) of roughly 20ms. <a href="https://www.cloudflare.com/learning/cdn/glossary/round-trip-time-rtt/">RTT is the round trip time</a> between the endpoints, measured in milliseconds. At that latency, standard <a href="https://en.wikipedia.org/wiki/Bandwidth-delay_product">BDP</a> calculations tell us that a window size of 2.5 MB can accommodate up to 1 Gbps of throughput. We then counted the number of TCP sessions with an upper memory limit set by autotuning (skmem_rb) greater than 5 MB, which is double our calculated window size. The relationship between the window size and skmem_rb is described in more detail <a href="/optimizing-tcp-for-high-throughput-and-low-latency/">here</a>.  There were 558 such TCP sessions on one of our servers. Most of those sessions looked similar to this:</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/5E38uVpENXg60453V33B8z/036d2e40ab0ea1734ea6f19ef158a378/Screenshot-2023-05-25-at-15.28.57.png" />
            
            </figure><p>The key fields to focus on above are:</p><ul><li><p>recvq – the user payload bytes in the receive queue (waiting to be read by the local userspace process)</p></li><li><p>skmem “r” field – the actual amount of kernel memory allocated for the receive buffer (this is the same as the kernel variable sk_rmem_alloc)</p></li><li><p>skmem “rb” field – the limit for “r” (this is the same as the kernel variable sk_rcvbuf)</p></li><li><p>l7read – the user payload bytes read by the local userspace process</p></li></ul><p>Note the value of 256MiB for skmem_r and skmem_rb. That is the red flag that something is very wrong, because those values match the system-wide maximum value set by sysctl net.ipv4.tcp_rmem.  Linux autotuning should not permit the buffers to grow that large for these sessions.</p>
    <div>
      <h2>Memory limits are not being honored for some TCP sessions</h2>
      <a href="#memory-limits-are-not-being-honored-for-some-tcp-sessions">
        
      </a>
    </div>
    <p>TCP autotuning sets the maximum amount of memory that a session can use. More information about Linux autotuning can be found at <a href="/optimizing-tcp-for-high-throughput-and-low-latency/">Optimizing TCP for high WAN throughput while preserving low latency</a>.</p><p>Here is a graph of one of the problematic sessions, showing skmem_r (allocated memory) and skmem_rb (the limit for “r”) over time:</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/6frTdIWTmJblioLZM26wYy/783af9ffa5a5e12b8178583ec95baa51/download-17.png" />
            
            </figure><p>This graph is showing us that the limit being set by autotuning is being ignored, because every time skmem_r exceeds skmem_rb, skmem_rb is simply being raised to match it. So something is wrong with how skmem_rb is being handled. This explains the high memory usage. The question now is why.</p>
    <div>
      <h2>The reproducer</h2>
      <a href="#the-reproducer">
        
      </a>
    </div>
    <p>At this point, we had only observed this problem in our production environment. Because we couldn’t predict which TCP sessions would fall into this dysfunctional state, and because we wanted to see the session information for these dysfunctional sessions from the beginning of those sessions, we needed to collect a lot of TCP session data for all TCP sessions. This is challenging in a production environment running at the scale of <a href="https://www.cloudflare.com/network/">Cloudflare’s network</a>. We needed to be able to reproduce this in a controlled lab environment. To that end, we gathered more details about what distinguishes these problematic TCP sessions from others, and ran a large number of experiments in our lab environment to reproduce the problem.</p><p>After <i>a lot</i> of attempts, we finally got it.</p><p>We were left with some pretty dirty lab machines by the time we got to this point, meaning that a lot of settings had been changed. We didn’t believe that all of them were related to the problem, but we didn’t know which ones were and which were not. So we went through a further series of tests to get us to a minimal set up to reproduce the problem. It turned out that a number of factors that we originally thought were important (such as latency) were not important.</p><p>The minimal set up turned out to be surprisingly simple:</p><ol><li><p>At the sending host, run a TCP program with an infinite loop, sending 1500B packets, with a 1 ms delay between each send.</p></li><li><p>At the receiving host, run a TCP program with an infinite loop, reading 1B at a time, with a 1 ms delay between each read.</p></li></ol><p>That’s it. Run these programs and watch your receive queue grow unbounded until it hits net.ipv4.tcp_rmem max.</p><p><b>tcp_server_sender.py</b></p>
            <pre><code>import time
import socket
import errno

daemon_port = 2425
payload = b'a' * 1448

listen_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
listen_sock.bind(('0.0.0.0', daemon_port))

# listen backlog
listen_sock.listen(32)
listen_sock.setblocking(True)

while True:
    mysock, _ = listen_sock.accept()
    mysock.setblocking(True)
    
    # do forever (until client disconnects)
    while True:
        try:
            mysock.send(payload)
            time.sleep(0.001)
        except Exception as e:
            print(e)
            mysock.close()
            break</code></pre>
            <p><b>tcp_client_receiver.py</b></p>
            <pre><code>import socket
import time

def do_read(bytes_to_read):
    total_bytes_read = 0
    while True:
        bytes_read = client_sock.recv(bytes_to_read)
        total_bytes_read += len(bytes_read)
        if total_bytes_read &gt;= bytes_to_read:
            break

server_ip = “192.168.2.139”
server_port = 2425

client_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client_sock.connect((server_ip, server_port))
client_sock.setblocking(True)

while True:
    do_read(1)
    time.sleep(0.001)</code></pre>
            
    <div>
      <h2>Reproducing the problem</h2>
      <a href="#reproducing-the-problem">
        
      </a>
    </div>
    <p>First, we ran the above programs with these settings:</p><ul><li><p>Kernel 6.1.14 vanilla</p></li><li><p>net.ipv4.tcp_rmem max = 256 MiB (window scale factor 13, or 8192 bytes)</p></li><li><p>net.ipv4.tcp_adv_win_scale = -2</p></li></ul><p>Here is what this TCP session is doing:</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/4a1rsgh4WSUwmMi6lBj6P3/967eb34bdceab5dd6752d308befdef18/download--1--11.png" />
            
            </figure><p>At second 189 of the run, we see these packets being exchanged:</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/kyCivGZSURqg8rQc1Rl32/e447480e0679472ef93d86068fc4697b/download--2--9.png" />
            
            </figure><p>This is a significant failure because the memory limits are being ignored, and memory usage is unbounded until net.ipv4.tcp_rmem max is reached.</p><p>When net.ipv4.tcp_rmem max is reached:</p><ul><li><p>The kernel drops incoming packets.</p></li><li><p>A ZeroWindow is never sent.  A ZeroWindow is a packet sent by the receiver to the sender telling the sender to stop sending packets.  This is normal and expected behavior when the receiver buffers are full.</p></li><li><p>The sender retransmits, with exponential backoff.</p></li><li><p>Eventually (~15 minutes, depending on system settings) the session times out and the connection is broken (“Errno 110 Connection timed out”).</p></li></ul><p>Note that there is a range of packet sizes that can be sent, and a range of intervals which can be used for the delays, to cause this abnormal condition. This first reproduction is intentionally defined to grow the receive buffer quickly. These rates and delays do not reflect exactly what we see in production.</p>
    <div>
      <h2>A closer look at real traffic in production</h2>
      <a href="#a-closer-look-at-real-traffic-in-production">
        
      </a>
    </div>
    <p>The prior section describes what is happening in our lab systems. Is that consistent with what we see in our production streams? Let’s take a look, now that we know more about what we are looking for.</p><p>We did find similar TCP sessions on our production network, which provided confirmation. But we also found this one, which, although it looks a little different, is actually the same root cause:</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/77M3L6t408cvgtJm9H4bFw/419901429ff9e1298659c3635d96b59b/download--3--6.png" />
            
            </figure><p>During this TCP session, the rate at which the userspace process is reading from the socket (the L7read rate line) after second 411 is zero. That is, L7 stops reading entirely at that point.</p><p>Notice that the bottom two graphs have a log scale on their y-axis to show that throughput and window size are never zero, even after L7 stops reading.</p><p>Here is the pattern of packet exchange that repeats itself during the erroneous “growth phase” after L7 stopped reading at the 411 second mark:</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/680HeUb2d1KD6usPg0lC2/ac04168db28094115cca11807834f3c1/download--4--6.png" />
            
            </figure><p>This variation of the problem is addressed below in the section called “Reader never reads”.</p>
    <div>
      <h2>Getting to the root cause</h2>
      <a href="#getting-to-the-root-cause">
        
      </a>
    </div>
    <p>sk_rcvbuf is being increased inappropriately. Somewhere. Let’s review the code to narrow down the possibilities.</p><p>sk_rcvbuf only gets updated in three places (that are relevant to this issue):</p><ul><li><p><a href="https://elixir.bootlin.com/linux/v6.1.14/source/net/ipv4/tcp_input.c#L572">tcp_clamp_window</a></p></li><li><p><a href="https://elixir.bootlin.com/linux/v6.1.14/source/net/ipv4/tcp_input.c#L701">tcp_rcv_space_adjust</a></p></li><li><p><a href="https://elixir.bootlin.com/linux/v6.1.14/source/net/ipv4/tcp.c#L1845">tcp_set_rcvlowat</a></p></li></ul><p>Actually, we are not calling tcp_set_rcvlowat, which eliminates that one. Next we used bpftrace scripts to figure out if it’s in tcp_clamp_window or tcp_rcv_space_adjust.   After bpftracing, the answer is: It’s tcp_clamp_window.</p>
    <div>
      <h2>Summarizing what we know so far,part I</h2>
      <a href="#summarizing-what-we-know-so-far-part-i">
        
      </a>
    </div>
    <p><a href="https://elixir.bootlin.com/linux/v6.1.14/source/net/ipv4/tcp_input.c#L4770">tcp_try_rmem_schedule</a> is being called as usual.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/aohoByJQqypdNrDLfoiDB/1ec5444d6bada18fbb955e9cf6ac40d0/download--5--6.png" />
            
            </figure><p>Sometimes rmem_alloc &gt; sk_rcvbuf. When that happens, prune is called, which calls tcp_clamp_window. tcp_clamp_window <i>increases</i> sk_rcvbuf to match rmem_alloc. That is unexpected.</p><p>The key question is: Why is rmem_alloc &gt; sk_rcvbuf?</p>
    <div>
      <h2>Why is rmem_alloc &gt; sk_rcvbuf?</h2>
      <a href="#why-is-rmem_alloc-sk_rcvbuf">
        
      </a>
    </div>
    <p>More kernel code review ensued, reviewing all the places where rmem_alloc is increased, and looking to see where rmem_alloc could be exceeding sk_rcvbuf. After more bpftracing, watching netstats, etc., the answer is: TCP coalescing.</p>
    <div>
      <h2>TCP coalescing</h2>
      <a href="#tcp-coalescing">
        
      </a>
    </div>
    <p>Coalescing is where the kernel will combine packets as they are being received.</p><p>Note that this is not <a href="https://lwn.net/Articles/358910/">Generic Receive Offload</a> (GRO).  This is specific to TCP for packets on the INPUT path. Coalesce is a L4 feature that appends user payload from an incoming packet to an already existing packet, if possible. This saves memory (header space).</p><p>tcp_rcv_established calls tcp_queue_rcv, which calls tcp_try_coalesce. If the incoming packet can be coalesced, then it will be, and rmem_alloc is raised to reflect that. Here’s the important part: rmem_alloc can and does go above sk_rcvbuf because of the logic in that routine.</p>
    <div>
      <h2>Summarizing what we know so far,part II</h2>
      <a href="#summarizing-what-we-know-so-far-part-ii">
        
      </a>
    </div>
    <ol><li><p>Data packets are being received</p></li><li><p>tcp_rcv_established will coalesce, raising rmem_alloc above sk_rcvbuf</p></li><li><p>tcp_try_rmem_schedule -&gt; tcp_prune_queue -&gt; tcp_clamp_window will raise sk_rcvbuf to match rmem_alloc</p></li><li><p>The kernel then increases the window size based upon the new sk_rcvbuf value</p></li></ol><p>In step 2, in order for rmem_alloc to exceed sk_rcvbuf, it has to be near sk_rcvbuf in the first place. We use tcp_adv_win_scale of -2, which means the window size will be 25% of the available buffer size, so we would not expect rmem_alloc to even be close to sk_rcvbuf. In our tests, the truesize ratio is not close to 4, so something unexpected is happening.</p><p>Why is rmem_alloc even close to sk_rcvbuf?</p>
    <div>
      <h2>Why is rmem_alloc close to sk_rcvbuf?</h2>
      <a href="#why-is-rmem_alloc-close-to-sk_rcvbuf">
        
      </a>
    </div>
    <p>Sending a ZeroWindow (a packet advertising a window size of zero) is how a TCP receiver tells a TCP sender to stop sending when the receive window is full. This is the mechanism that should keep rmem_alloc well below sk_rcvbuf.</p><p>During our tests, we happened to notice that the SNMP metric <a href="https://elixir.bootlin.com/linux/v6.1.14/source/net/ipv4/proc.c#L270">TCPWantZeroWindowAdv</a> was increasing. The receiver was not sending ZeroWindows when it should have been.  So our attention fell on the window calculation logic, and we arrived at the root cause of all of our problems.</p>
    <div>
      <h2>The root cause</h2>
      <a href="#the-root-cause">
        
      </a>
    </div>
    <p>The problem has to do with how the receive window size is calculated. This is the value in the TCP header that the receiver sends to the sender. Together with the ACK value, it communicates to the sender what the right edge of the window is.</p><p>The way TCP’s sliding window works is described in Stevens, “TCP/IP Illustrated, Volume 1”, section 20.3.  Visually, the receive window looks like this:</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/3T4BEKeOU8Vaa7rzuec8OI/641a64303988fd717fbcc1bbc61dadd9/download--6--5.png" />
            
            </figure><p>In the early days of the Internet, wide-area communications links offered low bandwidths (relative to today), so the 16 bits in the TCP header was more than enough to express the size of the receive window needed to achieve optimal throughput. Then the future happened, and now those 16-bit window values are scaled based upon a multiplier set during the TCP 3-way handshake.</p><p>The window scaling factor allows us to reach high throughputs on modern networks, but it also introduced an issue that we must now discuss.</p><p>The granularity of the receive window size that can be set in the TCP header is larger than the granularity of the actual changes we sometimes want to make to the size of the receive window.</p><p>When window scaling is in effect, every time the receiver ACKs some data, the receiver has to move the right edge of the window either left or right. The only exception would be if the amount of ACKed data is exactly a multiple of the window scale factor, and the receive window size specified in the ACK packet was reduced by the same multiple. This is rare.</p><p>So the right edge has to move. Most of the time, the receive window size does not change and the right edge moves to the right in lockstep with the ACK (the left edge), which always moves to the right.</p><p>The receiver can decide to increase the size of the receive window, based on its normal criteria, and that’s fine. It just means the right edge moves farther to the right. No problems.</p><p>But what happens when we approach a window full condition? Keeping the right edge unchanged is not an option.  We are forced to make a decision. Our choices are:</p><ul><li><p>Move the right edge to the right</p></li><li><p>Move the right edge to the left</p></li></ul><p>But if we have arrived at the upper limit, then moving the right edge to the right requires us to ignore the upper limit. This is equivalent to not having a limit. This is what Linux does today, and is the source of the problems described in this post.</p><p>This occurs for any window scaling factor greater than one. This means everyone.</p>
    <div>
      <h2>A sidebar on terminology</h2>
      <a href="#a-sidebar-on-terminology">
        
      </a>
    </div>
    <p>The window size specified in the TCP header is the receive window size. It is sent from the receiver to the sender. The ACK number plus the window size defines the range of sequence numbers that the sender may send. It is also called the advertised window, or the offered window.</p><p>There are three terms related to TCP window management that are important to understand:</p><ul><li><p>Closing the window. This is when the left edge of the window moves to the right. This occurs every time an ACK of a data packet arrives at the sender.</p></li><li><p>Opening the window. This is when the right edge of the window moves to the right.</p></li><li><p>Shrinking the window. This is when the right edge of the window moves to the left.</p></li></ul><p>Opening and shrinking is not the same thing as the receive window size in the TCP header getting larger or smaller. The right edge is defined as the ACK number plus the receive window size. Shrinking only occurs when that right edge moves to the left (i.e. gets reduced).</p><p><a href="https://www.rfc-editor.org/rfc/rfc7323">RFC 7323</a> describes <a href="https://www.rfc-editor.org/rfc/rfc7323#section-2.4">window retraction</a>. Retracting the window is the same as shrinking the window.</p>
    <div>
      <h2>Discussion Regarding Solutions</h2>
      <a href="#discussion-regarding-solutions">
        
      </a>
    </div>
    <p>There are only three options to consider:</p><ol><li><p>Let the window grow</p></li><li><p>Drop incoming packets</p></li><li><p>Shrink the window</p></li></ol>
    <div>
      <h3>Let the window grow</h3>
      <a href="#let-the-window-grow">
        
      </a>
    </div>
    <p>Letting the window grow is the same as ignoring the memory limits set by autotuning. It results in allocating excessive amounts of memory for no reason. This is really just kicking the can down the road until allocated memory reaches net.ipv4.tcp_rmem max, when we are forced to choose from among one of the other two options.</p>
    <div>
      <h3>Drop incoming packets</h3>
      <a href="#drop-incoming-packets">
        
      </a>
    </div>
    <p>Dropping incoming packets will cause the sender to retransmit the dropped packets, with exponential backoff, until an eventual timeout (depending on the client read rate), which breaks the connection.  ZeroWindows are never sent.  This wastes bandwidth and processing resources by retransmitting packets we know will not be successfully delivered to L7 at the receiver.  This is functionally incorrect for a window full situation.</p>
    <div>
      <h3>Shrink the window</h3>
      <a href="#shrink-the-window">
        
      </a>
    </div>
    <p>Shrinking the window involves moving the right edge of the window to the left when approaching a window full condition.  A ZeroWindow is sent when the window is full.  There is no wasted memory, no wasted bandwidth, and no broken connections.</p><p>The current situation is that we are letting the window grow (option #1), and when net.ipv4.tcp_rmem max is reached, we are dropping packets (option #2).</p><p>We need to stop doing option #1.  We could either drop packets (option #2) when sk_rcvbuf is reached.  This avoids excessive memory usage, but is still functionally incorrect for a window full situation.  Or we could shrink the window (option #3).</p>
    <div>
      <h2>Shrinking the window</h2>
      <a href="#shrinking-the-window">
        
      </a>
    </div>
    <p>It turns out that this issue has already been addressed in the RFC’s.</p><p><a href="https://www.rfc-editor.org/rfc/rfc7323#section-2.4">RFC 7323</a> says:</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/4hjOIKUJlsa2mQ3NTD0oNS/6fe810be0066a57ca07a8fdfbb5cd4dd/download--7--1.png" />
            
            </figure><p>There are two elements here that are important.</p><ul><li><p>“there are instances when a retracted window can be offered”</p></li><li><p>“Implementations MUST ensure that they handle a shrinking window”</p></li></ul><p><a href="https://www.rfc-editor.org/rfc/rfc7323#appendix-F">Appendix F</a> of that RFC describes our situation, adding:</p><ul><li><p>“<i>This is a general problem and can happen any time the sender does a write, which is smaller than the window scale factor.</i>”</p></li></ul>
    <div>
      <h2>Kernel patch</h2>
      <a href="#kernel-patch">
        
      </a>
    </div>
    <p>The Linux kernel patch we wrote to enable TCP window shrinking has been merged upstream and will be in kernel version 6.5 and later.  The commit can be found <a href="https://github.com/torvalds/linux/commit/b650d953cd391595e536153ce30b4aab385643ac">here</a>.</p>
    <div>
      <h2>Rerunning the test above with kernel patch</h2>
      <a href="#rerunning-the-test-above-with-kernel-patch">
        
      </a>
    </div>
    <p>Here is the test we showed above, but this time using the kernel patch:</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/6q08PzhHmDALsyvrqEanvp/a0de8edfc8961bc66ee4f888c2dda58b/download--8--1.png" />
            
            </figure><p>Here is the pattern of packet exchanges that repeat when using the kernel patch:</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/2jENxEgOngxoMsWA2CtkGb/e56d683213ce56c55b3626bedce92a97/download--10--1.png" />
            
            </figure><p>We see that the memory limit is being honored, ZeroWindows are being sent, there are no retransmissions, and no disconnects after 15 minutes. This is the desired result.</p>
    <div>
      <h2>Test results using a TCP window scaling factor of 8</h2>
      <a href="#test-results-using-a-tcp-window-scaling-factor-of-8">
        
      </a>
    </div>
    <p>The window scaling factor of 8 and tcp_adv_win_scale of 1 is commonly seen on the public Internet, so let’s test that.</p><ul><li><p>kernel 6.1.14 vanilla</p></li><li><p>tcp_rmem max = 8 MiB (window scale factor 8, or 256 bytes)</p></li><li><p>tcp_adv_win_scale = 1</p></li></ul>
    <div>
      <h3>Without the kernel patch</h3>
      <a href="#without-the-kernel-patch">
        
      </a>
    </div>
    
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/bymqRbxF3Ty7SokgQACbf/42b90b67aca9e9f539aa634c93b474a3/download--11--1.png" />
            
            </figure><p>At the ~2100 second mark, we see the same problems we saw earlier when using wscale 13.</p>
    <div>
      <h3>With the kernel patch</h3>
      <a href="#with-the-kernel-patch">
        
      </a>
    </div>
    
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/6GEvtqjf7mRG9z4iouZdxU/6a89e8283c392ace7b8b9475a5cba303/download--12--1.png" />
            
            </figure><p>The kernel patch is working as expected.</p>
    <div>
      <h2>Test results using an oscillating reader</h2>
      <a href="#test-results-using-an-oscillating-reader">
        
      </a>
    </div>
    <p>This is a test run where the reader alternates every 240 seconds between reading slow and reading fast.  Slow is 1B every 1 ms and fast is 3300B every 1 ms.</p><ul><li><p>kernel 6.1.14 vanilla</p></li><li><p>net.ipv4.tcp_rmem max = 256 MiB (window scale factor 13, or 8192 bytes)</p></li><li><p>tcp_adv_win_scale = -2</p></li></ul>
    <div>
      <h3>Without the kernel patch</h3>
      <a href="#without-the-kernel-patch">
        
      </a>
    </div>
    
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/wVZgjyGcD2JT5lO8okZWO/7c1b6f087b6686024d2cfd29cdbb138b/download--13-.png" />
            
            </figure>
    <div>
      <h3>With the kernel patch</h3>
      <a href="#with-the-kernel-patch">
        
      </a>
    </div>
    
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/6euKMB55WcEZzPsHR59OGJ/7064210c444249644026b33e4559c1cf/download--14-.png" />
            
            </figure><p>The kernel patch is working as expected.</p><p>NB. We do see the increase of skmem_rb at the 720 second mark, but it only goes to ~20MB and does not grow unbounded. Whether or not 20MB is the most ideal value for this TCP session is an interesting question, but that is a topic for a different blog post.</p>
    <div>
      <h2>Reader never reads</h2>
      <a href="#reader-never-reads">
        
      </a>
    </div>
    <p>Here’s a good one. Say a reader never reads from the socket. How much TCP receive buffer memory would we expect that reader to consume? One might assume the answer is that the reader would read a few packets, store the payload in the receive queue, then pause the flow of packets until the userspace program starts reading.  The actual answer is that the reader will read packets until the receive queue grows to the size of net.ipv4.tcp_rmem max.  This is incorrect behavior, to say the very least.</p><p>For this test, the sender sends 4 bytes every 1 ms.  The reader, literally, never reads from the socket. Not once.</p><ul><li><p>kernel 6.1.14 vanilla</p></li><li><p>net.ipv4.tcp_rmem max = 8 MiB (window scale factor 8, or 256 bytes)</p></li><li><p>net.ipv4.tcp_adv_win_scale = -2</p></li></ul>
    <div>
      <h3>Without the kernel patch:</h3>
      <a href="#without-the-kernel-patch">
        
      </a>
    </div>
    
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/7wmz2uhmftqBWNdyAjJREM/3d780a8cf115aa4ed60dd4f37be68ea4/download--15-.png" />
            
            </figure>
    <div>
      <h3>With the kernel patch:</h3>
      <a href="#with-the-kernel-patch">
        
      </a>
    </div>
    
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/1D4GXbXA6H7kdqXtBM3VPM/9103c7f2821f3fd3b4d4cfed668b7cb2/download--16-.png" />
            
            </figure><p>Using the kernel patch produces the expected behavior.</p>
    <div>
      <h2>Results from the Cloudflare production network</h2>
      <a href="#results-from-the-cloudflare-production-network">
        
      </a>
    </div>
    <p>We deployed this patch to the Cloudflare production network, and can see the effects in aggregate when running at scale.</p>
    <div>
      <h3>Packet Drop Rates</h3>
      <a href="#packet-drop-rates">
        
      </a>
    </div>
    <p>This first graph shows RcvPruned, which shows how many incoming packets per second were dropped due to memory constraints.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/7K8K11hUJCsHQfzVNg0ljy/8abe4f6e7973cc8f86d6ddd97c95f2ed/download--17-.png" />
            
            </figure><p>The patch was enabled on most servers on 05/01 at 22:00, eliminating those drops.</p>
    <div>
      <h3>TCPRcvCollapsed</h3>
      <a href="#tcprcvcollapsed">
        
      </a>
    </div>
    <p>Recall that TCPRcvCollapsed is the number of packets per second that are merged together in the queue in order to reduce memory usage (by eliminating header metadata).  This occurs when memory limits are reached.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/2hvXUoVFy84ss30qG9D9d4/354ba2447d99e4e85366396cfd31b4d0/download--18-.png" />
            
            </figure><p>The patch was enabled on most servers on 05/01 at 22:00. These graphs show the results from one of our data centers. The upper graph shows that the patch has eliminated all collapse processing. The lower graph shows the amount of time spent in collapse processing (each line in the lower graph is a single server). This is important because it can impact Cloudflare’s responsiveness in processing <a href="https://www.cloudflare.com/learning/ddos/glossary/hypertext-transfer-protocol-http/">HTTP requests</a>.  The result of the patch is that all latency due to TCP collapse processing has been eliminated.</p>
    <div>
      <h3>Memory</h3>
      <a href="#memory">
        
      </a>
    </div>
    <p>Because the memory limits set by autotuning are now being enforced, the total amount of memory allocated is reduced.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/5F0FZs5UBS7QLMo6KMAIWE/8d7597782fb2bde9895591379b4ae994/download--19-.png" />
            
            </figure><p>In this graph, the green line shows the total amount of memory allocated for TCP buffers in one of our data centers.  This is with the patch enabled.  The purple line is the same total, but from exactly 7 days prior to the time indicated on the x axis, before the patch was enabled.  Using this approach to visualization, it is clear to see the memory saved with the patch enabled.</p>
    <div>
      <h3>ZeroWindows</h3>
      <a href="#zerowindows">
        
      </a>
    </div>
    <p>TCPWantZeroWindowAdv is the number of times per second that the window calculation based on available buffer memory produced a result that should have resulted in a ZeroWindow being sent to the sender, but was not.  In other words, this is how often the receive buffer was increased beyond the limit set by autotuning.</p><p>After a receiver has sent a Zero Window to the sender, the receiver is not expecting to get any additional data from the sender. Should additional data packets arrive at the receiver during the period when the window size is zero, those packets are dropped and the metric TCPZeroWindowDrop is incremented.  These dropped packets are usually just due to the timing of these events, i.e. the Zero Window packet in one direction and some data packets flowing in the other direction passed by each other on the network.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/6lXFnIJVZKY9W6hW9IWIoA/1f0d0320f00ff92cde5b9b17f131ac4b/download--20-.png" />
            
            </figure><p>The patch was enabled on most servers on 05/01 at 22:00, although it was enabled for a subset of servers on 04/26 and 04/28.</p><p>The upper graph tells us that ZeroWindows are indeed being sent when they need to be based on the available memory at the receiver.  This is what the lack of “Wants” starting on 05/01 is telling us.</p><p>The lower graph reports the packets that are dropped because the session is in a ZeroWindow state. These are ok to drop, because the session is in a ZeroWindow state. These drops do not have a negative impact, for the same reason (it’s in a ZeroWindow state).</p><p>All of these results are as expected.</p><p>Importantly, we have also not found any peer TCP stacks that are non-RFC compliant (i.e. that are not able to accept a shrinking window).</p>
    <div>
      <h2>Summary</h2>
      <a href="#summary">
        
      </a>
    </div>
    <p>In this blog post, we described when and why TCP memory limits are not being honored in the Linux kernel, and introduced a patch that fixes it. All in a day’s work at Cloudflare, where we are helping build a better Internet.</p> ]]></content:encoded>
            <category><![CDATA[TCP]]></category>
            <category><![CDATA[Deep Dive]]></category>
            <guid isPermaLink="false">4Q5kourPRueUXv2i7d1GUO</guid>
            <dc:creator>Mike Freemon</dc:creator>
        </item>
        <item>
            <title><![CDATA[Announcing connect() — a new API for creating TCP sockets from Cloudflare Workers]]></title>
            <link>https://blog.cloudflare.com/workers-tcp-socket-api-connect-databases/</link>
            <pubDate>Tue, 16 May 2023 13:00:13 GMT</pubDate>
            <description><![CDATA[ Today, we are excited to announce a new API in Cloudflare Workers for creating outbound TCP sockets, making it possible to connect directly to databases and any TCP-based service from Workers ]]></description>
            <content:encoded><![CDATA[ 
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/1CjlPkdLJUXlfgIKgq2Jvy/d2e17e3027c02f82e191007561640f79/image2-12.png" />
            
            </figure><p>Today, we are excited to announce a new API in Cloudflare Workers for creating outbound TCP sockets, making it possible to connect directly to any TCP-based service from Workers.</p><p>Standard protocols including <a href="https://www.cloudflare.com/learning/access-management/what-is-ssh/">SSH</a>, MQTT, SMTP, FTP, and IRC are all built on top of TCP. Most importantly, nearly all applications need to connect to databases, and most databases speak TCP. And while <a href="https://developers.cloudflare.com/d1/">Cloudflare D1</a> works seamlessly on Workers, and some <a href="https://developers.cloudflare.com/workers/learning/integrations/databases/">hosted database providers</a> allow connections over HTTP or WebSockets, the vast majority of databases, both relational (SQL) and document-oriented (NoSQL), require clients to connect by opening a direct TCP “socket”, an ongoing two-way connection that is used to send queries and receive data. Now, Workers provides an API for this, the first of many steps to come in allowing you to use any database or infrastructure you choose when building full-stack applications on Workers.</p><p>Database drivers, the client code used to connect to databases and execute queries, are already using this new API. <a href="https://github.com/brianc/node-postgres">pg</a>, the most widely used JavaScript database driver for PostgreSQL, works on Cloudflare Workers today, with more database drivers to come.</p><p>The TCP Socket API is available today to everyone. Get started by reading the <a href="https://developers.cloudflare.com/workers/runtime-apis/tcp-sockets">TCP Socket API docs</a>, or connect directly to any PostgreSQL database from your Worker by following <a href="https://developers.cloudflare.com/workers/databases/connect-to-postgres/">this guide</a>.</p>
    <div>
      <h2>First — what is a TCP Socket?</h2>
      <a href="#first-what-is-a-tcp-socket">
        
      </a>
    </div>
    <p><a href="https://www.cloudflare.com/learning/ddos/glossary/tcp-ip/">TCP (Transmission Control Protocol)</a> is a foundational networking protocol of the Internet. It is the underlying protocol that is used to make HTTP requests (prior to <a href="https://www.cloudflare.com/learning/performance/what-is-http3/">HTTP/3</a>, which uses <a href="https://cloudflare-quic.com/">QUIC</a>), to send email over <a href="https://www.cloudflare.com/learning/email-security/what-is-smtp/">SMTP</a>, to query databases using database–specific protocols like MySQL, and many other application-layer protocols.</p><p>A TCP socket is a programming interface that represents a two-way communication connection between two applications that have both agreed to “speak” over TCP. One application (ex: a Cloudflare Worker) initiates an outbound TCP connection to another (ex: a database server) that is listening for inbound TCP connections. Connections are established by negotiating a three-way handshake, and after the handshake is complete, data can be sent bi-directionally.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/6xxArl43DbexJUoRmw8JrG/0ad545bb25f002a4598d387aca491997/image1-30.png" />
            
            </figure><p>A socket is the programming interface for a single TCP connection — it has both a readable and writable “stream” of data, allowing applications to read and write data on an ongoing basis, as long as the connection remains open.</p>
    <div>
      <h2>connect() — A simpler socket API</h2>
      <a href="#connect-a-simpler-socket-api">
        
      </a>
    </div>
    <p>With Workers, we aim to support standard APIs that are supported across browsers and non-browser environments wherever possible, so that as many NPM packages as possible work on Workers without changes, and package authors don’t have to write runtime-specific code. But for TCP sockets, we faced a challenge — there was no clear shared standard across runtimes. Node.js provides the <a href="https://nodejs.org/api/net.html">net</a> and <a href="https://nodejs.org/api/tls.html">tls</a> APIs, but Deno implements a different API — <a href="https://deno.land/api@v1.33.1?s=Deno.connect">Deno.connect</a>. And web browsers do not provide a raw TCP socket API, though a <a href="https://github.com/WICG/direct-sockets/blob/main/docs/explainer.md">WICG proposal</a> does exist, and it is different from both Node.js and Deno.</p><p>We also considered how a TCP socket API could be designed to maximize performance and ergonomics in a serverless environment. Most networking APIs were designed well before serverless emerged, with the assumption that the developer’s application is also the server, responsible for directly handling configuring TLS options and credentials.</p><p>With this backdrop, we reached out to the community, with a focus on maintainers of database drivers, ORMs and other libraries that create outbound TCP connections. Using this feedback, we’ve tried to incorporate the best elements of existing APIs and proposals, and intend to contribute back to future standards, as part of the <a href="/introducing-the-wintercg/">Web-interoperable Runtimes Community Group (WinterCG)</a>.</p><p>The API we landed on is a simple function, connect(), imported from the new cloudflare:sockets module, that returns an instance of a Socket. Here’s a simple example showing it used to connect to a <a href="https://www.w3.org/People/Bos/PROSA/rep-protocols.html#gopher">Gopher</a> server. Gopher was one of the Internet’s early protocols that relied on TCP/IP, and still works today:</p>
            <pre><code>import { connect } from 'cloudflare:sockets';

export default {
  async fetch(req: Request) {
    const gopherAddr = "gopher.floodgap.com:70";
    const url = new URL(req.url);

    try {
      const socket = connect(gopherAddr);

      const writer = socket.writable.getWriter()
      const encoder = new TextEncoder();
      const encoded = encoder.encode(url.pathname + "\r\n");
      await writer.write(encoded);

      return new Response(socket.readable, { headers: { "Content-Type": "text/plain" } });
    } catch (error) {
      return new Response("Socket connection failed: " + error, { status: 500 });
    }
  }
};</code></pre>
            <p>We think this API design has many benefits that can be realized not just on Cloudflare, but in any serverless environment that adopts this design:</p>
            <pre><code>connect(address: SocketAddress | string, options?: SocketOptions): Socket

declare interface Socket {
  get readable(): ReadableStream;
  get writable(): WritableStream;
  get closed(): Promise&lt;void&gt;;
  close(): Promise&lt;void&gt;;
  startTls(): Socket;
}

declare interface SocketOptions {
  secureTransport?: string;
  allowHalfOpen: boolean;
}

declare interface SocketAddress {
  hostname: string;
  port: number;
}</code></pre>
            
    <div>
      <h3>Opportunistic TLS (StartTLS), without separate APIs</h3>
      <a href="#opportunistic-tls-starttls-without-separate-apis">
        
      </a>
    </div>
    <p>Opportunistic TLS, a pattern of creating an initial insecure connection, and then upgrading it to a secure one that uses TLS, remains common, particularly with database drivers. In Node.js, you must use the <a href="https://nodejs.org/api/net.html#class-netsocket">net</a> API to create the initial connection, and then use the <a href="https://nodejs.org/api/tls.html">tls</a> API to create a new, upgraded connection. In Deno, you pass the original socket to <a href="https://deno.land/api@v1.33.1?s=Deno.startTls">Deno.startTls()</a>, which creates a new, upgraded connection.</p><p>Drawing on a <a href="https://www.w3.org/TR/tcp-udp-sockets/#idl-def-TCPOptions">previous W3C proposal</a> for a TCP Socket API, we’ve simplified this by providing one API, that allows TLS to be enabled, allowed, or used when creating a socket, and exposes a simple method, startTls(), for upgrading a socket to use TLS.</p>
            <pre><code>// Create a new socket without TLS. secureTransport defaults to "off" if not specified.
const socket = connect("address:port", { secureTransport: "off" })

// Create a new socket, then upgrade it to use TLS.
// Once startTls() is called, only the newly created socket can be used.
const socket = connect("address:port", { secureTransport: "starttls" })
const secureSocket = socket.startTls();

// Create a new socket with TLS
const socket = connect("address:port", { secureTransport: "use" })</code></pre>
            
    <div>
      <h3>TLS configuration — a concern of host infrastructure, not application code</h3>
      <a href="#tls-configuration-a-concern-of-host-infrastructure-not-application-code">
        
      </a>
    </div>
    <p>Existing APIs for creating TCP sockets treat TLS as a library that you interact with in your application code. The <a href="https://nodejs.org/api/tls.html#tlscreatesecurecontextoptions">tls.createSecureContext()</a> API from Node.js has a plethora of advanced configuration options that are mostly environment specific. If you use custom certificates when connecting to a particular service, you likely use a different set of credentials and options in production, staging and development. Managing direct file paths to credentials across environments and swapping out .env files in production build steps are common pain points.</p><p>Host infrastructure is best positioned to manage this on your behalf, and similar to Workers support for <a href="/mtls-workers/">making subrequests using mTLS</a>, TLS configuration and credentials for the socket API will be managed via Wrangler, and a connect() function provided via a <a href="https://developers.cloudflare.com/workers/platform/bindings/">capability binding</a>. Currently, custom TLS credentials and configuration are not supported, but are coming soon.</p>
    <div>
      <h3>Start writing data immediately, before the TLS handshake finishes</h3>
      <a href="#start-writing-data-immediately-before-the-tls-handshake-finishes">
        
      </a>
    </div>
    <p>Because the connect() API synchronously returns a new socket, one can start writing to the socket immediately, without waiting for the TCP handshake to first complete. This means that once the handshake completes, data is already available to send immediately, and host platforms can make use of pipelining to optimize performance.</p>
    <div>
      <h2>connect() API + DB drivers = Connect directly to databases</h2>
      <a href="#connect-api-db-drivers-connect-directly-to-databases">
        
      </a>
    </div>
    <p>Many <a href="https://www.cloudflare.com/developer-platform/products/d1/">serverless databases</a> already work on Workers, allowing clients to connect over HTTP or over <a href="/neon-postgres-database-from-workers/">WebSockets</a>. But most databases don’t “speak” HTTP, including databases hosted on most cloud providers.</p><p>Databases each have their own “wire protocol”, and open-source database “drivers” that speak this protocol, sending and receiving data over a TCP socket. Developers rely on these drivers in their own code, as do database ORMs. Our goal is to make sure that you can use the same drivers and ORMs you might use in other runtimes and on other platforms on Workers.</p>
    <div>
      <h2>Try it now — connect to PostgreSQL from Workers</h2>
      <a href="#try-it-now-connect-to-postgresql-from-workers">
        
      </a>
    </div>
    <p>We’ve worked with the maintainers of <a href="https://www.npmjs.com/package/pg">pg</a>, one of the most popular database drivers in the JavaScript ecosystem, used by ORMs including <a href="https://sequelize.org/docs/v6/getting-started/">Sequelize</a> and <a href="https://knexjs.org/">knex.js</a>, to add support for connect().</p><p>You can try this right now. First, create a new Worker and install pg:</p>
            <pre><code>wrangler init
npm install --save pg</code></pre>
            <p>As of this writing, you’ll need to <a href="https://developers.cloudflare.com/workers/wrangler/configuration/#add-polyfills-using-wrangler">enable the node_compat</a> option in wrangler.toml:</p><p><b>wrangler.toml</b></p>
            <pre><code>name = "my-worker"
main = "src/index.ts"
compatibility_date = "2023-05-15"
node_compat = true</code></pre>
            <p>In just 20 lines of TypeScript, you can create a connection to a Postgres database, execute a query, return results in the response, and close the connection:</p><p><b>index.ts</b></p>
            <pre><code>import { Client } from "pg";

export interface Env {
  DB: string;
}

export default {
  async fetch(
    request: Request,
    env: Env,
    ctx: ExecutionContext
  ): Promise&lt;Response&gt; {
    const client = new Client(env.DB);
    await client.connect();
    const result = await client.query({
      text: "SELECT * from customers",
    });
    console.log(JSON.stringify(result.rows));
    const resp = Response.json(result.rows);
    // Close the database connection, but don't block returning the response
    ctx.waitUntil(client.end());
    return resp;
  },
};</code></pre>
            <p>To test this in local development, use the <code>--experimental-local</code> flag (instead of <code>–local</code>), which <a href="/miniflare-and-workerd/">uses the open-source Workers runtime</a>, ensuring that what you see locally mirrors behavior in production:</p>
            <pre><code>wrangler dev --experimental-local</code></pre>
            
    <div>
      <h2>What’s next for connecting to databases from Workers?</h2>
      <a href="#whats-next-for-connecting-to-databases-from-workers">
        
      </a>
    </div>
    <p>This is only the beginning. We’re aiming for the two popular MySQL drivers, <a href="https://github.com/mysqljs/mysql">mysql</a> and <a href="https://github.com/sidorares/node-mysql2">mysql2</a>, to work on Workers soon, with more to follow. If you work on a database driver or ORM, we’d love to help make your library work on Workers.</p><p>If you’ve worked more closely with database scaling and performance, you might have noticed that in the example above, a new connection is created for every request. This is one of the biggest current challenges of connecting to databases from serverless functions, across all platforms. With typical client connection pooling, you maintain a local pool of database connections that remain open. This approach of storing a reference to a connection or connection pool in global scope will not work, and is a poor fit for serverless. Managing individual pools of client connections on a per-isolate basis creates other headaches — when and how should connections be terminated? How can you limit the total number of concurrent connections across many isolates and locations?</p><p>Instead, we’re already working on simpler approaches to connection pooling for the most popular databases. We see a path to a future where you don’t have to think about or manage client connection pooling on your own. We’re also working on a brand new approach to making your database reads lightning fast.</p>
    <div>
      <h2>What’s next for sockets on Workers?</h2>
      <a href="#whats-next-for-sockets-on-workers">
        
      </a>
    </div>
    <p>Supporting outbound TCP connections is only one half of the story — we plan to support inbound TCP and UDP connections, as well as new emerging application protocols based on QUIC, so that you can build applications beyond HTTP with <a href="/introducing-socket-workers/">Socket Workers</a>.</p><p>Earlier today we also announced <a href="/announcing-workers-smart-placement">Smart Placement</a>, which improves performance by placing any Worker that makes multiple HTTP requests to an origin run as close as possible to reduce round-trip time. We’re working on making this work with Workers that open TCP connections, so that if your Worker connects to a database in Virginia and makes many queries over a TCP connection, each query is lightning fast and comes from the nearest location on <a href="https://www.cloudflare.com/network/">Cloudflare’s global network</a>.</p><p>We also plan to support custom certificates and other TLS configuration options in the coming months — tell us what is a must-have in order to connect to the services you need to connect to from Workers.</p>
    <div>
      <h2>Get started, and share your feedback</h2>
      <a href="#get-started-and-share-your-feedback">
        
      </a>
    </div>
    <p>The TCP Socket API is available today to everyone. Get started by reading the <a href="https://developers.cloudflare.com/workers/runtime-apis/tcp-sockets">TCP Socket API docs</a>, or connect directly to any PostgreSQL database from your Worker by following <a href="https://developers.cloudflare.com/workers/databases/connect-to-postgres/">this guide</a>.</p><p>We want to hear your feedback, what you’d like to see next, and more about what you’re building. Join the <a href="https://discord.cloudflare.com/">Cloudflare Developers Discord</a>.</p>
    <div>
      <h3>Watch on Cloudflare TV</h3>
      <a href="#watch-on-cloudflare-tv">
        
      </a>
    </div>
    <div></div><p></p> ]]></content:encoded>
            <category><![CDATA[Developer Week]]></category>
            <category><![CDATA[Cloudflare Workers]]></category>
            <category><![CDATA[TCP]]></category>
            <category><![CDATA[Database]]></category>
            <category><![CDATA[Developers]]></category>
            <category><![CDATA[Developer Platform]]></category>
            <guid isPermaLink="false">14RexUSLCzOVnpWl5DkGIq</guid>
            <dc:creator>Brendan Irvine-Broque</dc:creator>
            <dc:creator>Matt Silverlock</dc:creator>
        </item>
        <item>
            <title><![CDATA[When the window is not fully open, your TCP stack is doing more than you think]]></title>
            <link>https://blog.cloudflare.com/when-the-window-is-not-fully-open-your-tcp-stack-is-doing-more-than-you-think/</link>
            <pubDate>Tue, 26 Jul 2022 13:00:00 GMT</pubDate>
            <description><![CDATA[ In this blog post I'll share my journey deep into the Linux networking stack, trying to understand the memory and window management of the receiving side of a TCP connection ]]></description>
            <content:encoded><![CDATA[ <p>Over the years I've been lurking around the Linux kernel and have investigated the TCP code many times. But when recently we were working on <a href="/optimizing-tcp-for-high-throughput-and-low-latency/">Optimizing TCP for high WAN throughput while preserving low latency</a>, I realized I have gaps in my knowledge about how Linux manages TCP receive buffers and windows. As I dug deeper I found the subject complex and certainly non-obvious.</p><p>In this blog post I'll share my journey deep into the Linux networking stack, trying to understand the memory and window management of the receiving side of a TCP connection. Specifically, looking for answers to seemingly trivial questions:</p><ul><li><p>How much data can be stored in the TCP receive buffer? (it's not what you think)</p></li><li><p>How fast can it be filled? (it's not what you think either!)</p></li></ul><p>Our exploration focuses on the receiving side of the TCP connection. We'll try to understand how to tune it for the best speed, without wasting precious memory.</p>
    <div>
      <h3>A case of a rapid upload</h3>
      <a href="#a-case-of-a-rapid-upload">
        
      </a>
    </div>
    <p>To best illustrate the receive side buffer management we need pretty charts! But to grasp all the numbers, we need a bit of theory.</p><p>We'll draw charts from a receive side of a TCP flow, running a pretty straightforward scenario:</p><ul><li><p>The client opens a TCP connection.</p></li><li><p>The client does <code>send()</code>, and pushes as much data as possible.</p></li><li><p>The server doesn't <code>recv()</code> any data. We expect all the data to stay and wait in the receive queue.</p></li><li><p>We fix the SO_RCVBUF for better illustration.</p></li></ul><p>Simplified pseudocode might look like (<a href="https://github.com/cloudflare/cloudflare-blog/blob/master/2022-07-rmem-a/window.py">full code if you dare</a>):</p>
            <pre><code>sd = socket.socket(AF_INET, SOCK_STREAM, 0)
sd.bind(('127.0.0.3', 1234))
sd.listen(32)

cd = socket.socket(AF_INET, SOCK_STREAM, 0)
cd.setsockopt(SOL_SOCKET, SO_RCVBUF, 32*1024)
cd.connect(('127.0.0.3', 1234))

ssd, _ = sd.accept()

while true:
    cd.send(b'a'*128*1024)</code></pre>
            <p>We're interested in basic questions:</p><ul><li><p>How much data can fit in the server’s receive buffer? It turns out it's not exactly the same as the default read buffer size on Linux; we'll get there.</p></li><li><p>Assuming infinite bandwidth, what is the minimal time  - measured in <a href="https://www.cloudflare.com/learning/cdn/glossary/round-trip-time-rtt/">RTT</a> - for the client to fill the receive buffer?</p></li></ul>
    <div>
      <h3>A bit of theory</h3>
      <a href="#a-bit-of-theory">
        
      </a>
    </div>
    <p>Let's start by establishing some common nomenclature. I'll follow the wording used by the <a href="https://man7.org/linux/man-pages/man8/ss.8.html"><code>ss</code> Linux tool from the <code>iproute2</code> package</a>.</p><p>First, there is the buffer budget limit. <a href="https://man7.org/linux/man-pages/man8/ss.8.html"><code>ss</code> manpage</a> calls it <b>skmem_rb</b>, in the kernel it's named <b>sk_rcvbuf</b>. This value is most often controlled by the Linux autotune mechanism using the <code>net.ipv4.tcp_rmem</code> setting:</p>
            <pre><code>$ sysctl net.ipv4.tcp_rmem
net.ipv4.tcp_rmem = 4096 131072 6291456</code></pre>
            <p>Alternatively it can be manually set with <code>setsockopt(SO_RCVBUF)</code> on a socket. Note that the kernel doubles the value given to this setsockopt. For example SO_RCVBUF=16384 will result in skmem_rb=32768. The max value allowed to this setsockopt is limited to meager 208KiB by default:</p>
            <pre><code>$ sysctl net.core.rmem_max net.core.wmem_max
net.core.rmem_max = 212992
net.core.wmem_max = 212992</code></pre>
            <p><a href="/optimizing-tcp-for-high-throughput-and-low-latency/">The aforementioned blog post</a> discusses why manual buffer size management is problematic - relying on autotuning is generally preferable.</p><p>Here’s a diagram showing how <b>skmem_rb</b> budget is being divided:</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/3EEuOnbl8CKYCv4oWj5Ejw/4a0bf778f484bbddebfac4099d8e21f4/image2-17.png" />
            
            </figure><p>In any given moment, we can think of the budget as being divided into four parts:</p><ul><li><p><b>Recv-q</b>: part of the buffer budget occupied by actual application bytes awaiting <code>read()</code>.</p></li><li><p>Another part of is consumed by metadata handling - the cost of <b>struct sk_buff</b> and such.</p></li><li><p>Those two parts together are reported by <code>ss</code> as <b>skmem_r</b> - kernel name is <b>sk_rmem_alloc</b>.</p></li><li><p>What remains is "free", that is: it's not actively used yet.</p></li><li><p>However, a portion of this "free" region is an advertised window - it may become occupied with application data soon.</p></li><li><p>The remainder will be used for future metadata handling, or might be divided into the advertised window further in the future.</p></li></ul><p>The upper limit for the window is configured by <code>tcp_adv_win_scale</code> setting. By default, the window is set to at most 50% of the "free" space. The value can be clamped further by the TCP_WINDOW_CLAMP option or an internal <code>rcv_ssthresh</code> variable.</p>
    <div>
      <h3>How much data can a server receive?</h3>
      <a href="#how-much-data-can-a-server-receive">
        
      </a>
    </div>
    <p>Our first question was "How much data can a server receive?". A naive reader might think it's simple: if the server has a receive buffer set to say 64KiB, then the client will surely be able to deliver 64KiB of data!</p><p>But this is totally not how it works. To illustrate this, allow me to temporarily set sysctl <code>tcp_adv_win_scale=0</code>. This is not a default and, as we'll learn, it's the wrong thing to do. With this setting the server will indeed set 100% of the receive buffer as an advertised window.</p><p>Here's our setup:</p><ul><li><p>The client tries to send as fast as possible.</p></li><li><p>Since we are interested in the receiving side, we can cheat a bit and speed up the sender arbitrarily. The client has transmission congestion control disabled: we set initcwnd=10000 as the route option.</p></li><li><p>The server has a fixed <b>skmem_rb</b> set at 64KiB.</p></li><li><p>The server has <code><b>tcp_adv_win_scale=0</b></code>.</p></li></ul>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/44j6HUJ496dIXVMkltUe4O/1765b3f25ef767dfcb23d3c079f7e8cb/image6-10.png" />
            
            </figure><p>There are so many things here! Let's try to digest it. First, the X axis is an ingress packet number (we saw about 65). The Y axis shows the buffer sizes as seen on the receive path for every packet.</p><ul><li><p>First, the purple line is a buffer size limit in bytes - <b>skmem_rb</b>. In our experiment we called <code>setsockopt(SO_RCVBUF)=32K</code> and skmem_rb is double that value. Notice, by calling SO_RCVBUF we disabled the Linux autotune mechanism.</p></li><li><p>Green <b>recv-q</b> line is how many application bytes are available in the receive socket. This grows linearly with each received packet.</p></li><li><p>Then there is the blue <b>skmem_r</b>, the used data + metadata cost in the receive socket. It grows just like <b>recv-q</b> but a bit faster, since it accounts for the cost of the metadata kernel needs to deal with.</p></li><li><p>The orange <b>rcv_win</b> is an advertised window. We start with 64KiB (100% of skmem_rb) and go down as the data arrives.</p></li><li><p>Finally, the dotted line shows <b>rcv_ssthresh</b>, which is not important yet, we'll get there.</p></li></ul>
    <div>
      <h3>Running over the budget is bad</h3>
      <a href="#running-over-the-budget-is-bad">
        
      </a>
    </div>
    <p>It's super important to notice that we finished with <b>skmem_r</b> higher than <b>skmem_rb</b>! This is rather unexpected, and undesired. The whole point of the <b>skmem_rb</b> memory budget is, well, not to exceed it. Here's how <code>ss</code> shows it:</p>
            <pre><code>$ ss -m
Netid  State  Recv-Q  Send-Q  Local Address:Port  Peer Address:Port   
tcp    ESTAB  62464   0       127.0.0.3:1234      127.0.0.2:1235
     skmem:(r73984,rb65536,...)</code></pre>
            <p>As you can see, skmem_rb is 65536 and skmem_r is 73984, which is 8448 bytes over! When this happens we have an even bigger issue on our hands. At around the 62nd packet we have an advertised window of 3072 bytes, but while packets are being sent, the receiver is unable to process them! This is easily verifiable by inspecting an nstat TcpExtTCPRcvQDrop counter:</p>
            <pre><code>$ nstat -az TcpExtTCPRcvQDrop
TcpExtTCPRcvQDrop    13    0.0</code></pre>
            <p>In our run 13 packets were dropped. This variable counts a number of packets dropped due to either system-wide or per-socket memory pressure - we know we hit the latter. In our case, soon after the socket memory limit was crossed, new packets were prevented from being enqueued to the socket. This happened even though the TCP advertised window was still open.</p><p>This results in an interesting situation. The receiver's window is open which might indicate it has resources to handle the data. But that's not always the case, like in our example when it runs out of the memory budget.</p><p>The sender will think it hit a network congestion packet loss and will run the usual retry mechanisms including exponential backoff. This behavior can be looked at as desired or undesired, depending on how you look at it. On one hand no data will be lost, the sender can eventually deliver all the bytes reliably. On the other hand the exponential backoff logic might stall the sender for a long time, causing a noticeable delay.</p><p>The root of the problem is straightforward - Linux kernel <b>skmem_rb</b> sets a memory budget for both the <b>data</b> and <b>metadata</b> which reside on the socket. In a pessimistic case each packet might incur a cost of a <b>struct sk_buff</b> + <b>struct skb_shared_info</b>, which on my system is 576 bytes, above the actual payload size, plus memory waste due to network card buffer alignment:</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/7nJyE7p1rtHK9SvSTDnZoj/c02019aeed1e3b17f24b506b4eeaef36/image7-10.png" />
            
            </figure><p>We now understand that Linux can't just advertise 100% of the memory budget as an advertised window. Some budget must be reserved for metadata and such. The upper limit of window size is expressed as a fraction of the "free" socket budget. It is controlled by <code>tcp_adv_win_scale</code>, with the following values:</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/6ZfsgDbgLiLQ0HXUV5mVeK/31e596946e101fef2443896f9db8fcdb/image9-5.png" />
            
            </figure><p>By default, Linux sets the advertised window at most at 50% of the remaining buffer space.</p><p>Even with 50% of space "reserved" for metadata, the kernel is very smart and tries hard to reduce the metadata memory footprint. It has two mechanisms for this:</p><ul><li><p><b>TCP Coalesce</b> - on the happy path, Linux is able to throw away <b>struct sk_buff</b>. It can do so, by just linking the data to the previously enqueued packet. You can think about it as if it was <a href="https://www.spinics.net/lists/netdev/msg755359.html">extending the last packet on the socket</a>.</p></li><li><p><b>TCP Collapse</b> - when the memory budget is hit, Linux runs "collapse" code. Collapse rewrites and defragments the receive buffer from many small skb's into a few very long segments - therefore reducing the metadata cost.</p></li></ul><p>Here's an extension to our previous chart showing these mechanisms in action:</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/2KhHEeAUvJ6rinNLoRBwd1/36b733fddcbb885d8db5b076602ca168/image3-10.png" />
            
            </figure><p><b>TCP Coalesce</b> is a very effective measure and works behind the scenes at all times. In the bottom chart, the packets where the coalesce was engaged are shown with a pink line. You can see - the <b>skmem_r</b> bumps (blue line) are clearly correlated with a <b>lack</b> of coalesce (pink line)! The nstat TcpExtTCPRcvCoalesce counter might be helpful in debugging coalesce issues.</p><p>The <b>TCP Collapse</b> is a bigger gun. <a href="/optimizing-tcp-for-high-throughput-and-low-latency/">Mike wrote about it extensively</a>, and <a href="/the-story-of-one-latency-spike/">I wrote a blog post years ago, when the latency of TCP collapse hit us hard</a>. In the chart above, the collapse is shown as a red circle. We clearly see it being engaged after the socket memory budget is reached - from packet number 63. The nstat TcpExtTCPRcvCollapsed counter is relevant here. This value growing is a bad sign and might indicate bad latency spikes - especially when dealing with larger buffers. Normally collapse is supposed to be run very sporadically. A <a href="https://lore.kernel.org/lkml/20120510173135.615265392@linuxfoundation.org/">prominent kernel developer describes</a> this pessimistic situation:</p><blockquote><p>This also means tcp advertises a too optimistic window for a given allocated rcvspace: When receiving frames, <code>sk_rmem_alloc</code> can hit <code>sk_rcvbuf</code> limit and we call <code>tcp_collapse()</code> too often, especially when application is slow to drain its receive queue [...] This is a major latency source.</p></blockquote><p>If the memory budget remains exhausted after the collapse, Linux will drop ingress packets. In our chart it's marked as a red "X". The nstat TcpExtTCPRcvQDrop counter shows the count of dropped packets.</p>
    <div>
      <h3>rcv_ssthresh predicts the metadata cost</h3>
      <a href="#rcv_ssthresh-predicts-the-metadata-cost">
        
      </a>
    </div>
    <p>Perhaps counter-intuitively, the memory cost of a packet can be much larger than the amount of actual application data contained in it. It depends on number of things:</p><ul><li><p><b>Network card</b>: some network cards always allocate a full page (4096, or even 16KiB) per packet, no matter how small or large the payload.</p></li><li><p><b>Payload size</b>: shorter packets, will have worse metadata to content ratio since <b>struct skb</b> will be comparably larger.</p></li><li><p>Whether XDP is being used.</p></li><li><p>L2 header size: things like ethernet, vlan tags, and tunneling can add up.</p></li><li><p>Cache line size: many kernel structs are cache line aligned. On systems with larger cache lines, they will use more memory (see P4 or S390X architectures).</p></li></ul><p>The first two factors are the most important. Here's a run when the sender was specially configured to make the metadata cost bad and the coalesce ineffective (the <a href="https://github.com/cloudflare/cloudflare-blog/blob/master/2022-07-rmem-a/window.py#L90">details of the setup are messy</a>):</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/6Oo38G0pRDoxfqkcIE7D9Y/c372a9cba2402cee11c14fc815875ea3/image1-10.png" />
            
            </figure><p>You can see the kernel hitting TCP collapse multiple times, which is totally undesired. Each time a collapse kernel is likely to rewrite the full receive buffer. This whole kernel machinery, from reserving some space for metadata with tcp_adv_win_scale, via using coalesce to reduce the memory cost of each packet, up to the rcv_ssthresh limit, exists to avoid this very case of hitting collapse too often.</p><p>The kernel machinery most often works fine, and TCP collapse is rare in practice. However, we noticed that's not the case for certain types of traffic. One example is <a href="https://lore.kernel.org/lkml/CA+wXwBSGsBjovTqvoPQEe012yEF2eYbnC5_0W==EAvWH1zbOAg@mail.gmail.com/">websocket traffic with loads of tiny packets</a> and a slow reader. One <a href="https://elixir.bootlin.com/linux/latest/source/net/ipv4/tcp_input.c#L452">kernel comment talks about</a> such a case:</p>
            <pre><code>* The scheme does not work when sender sends good segments opening
* window and then starts to feed us spaghetti. But it should work
* in common situations. Otherwise, we have to rely on queue collapsing.</code></pre>
            <p>Notice that the <b>rcv_ssthresh</b> line dropped down on the TCP collapse. This variable is an internal limit to the advertised window. By dropping it the kernel effectively says: hold on, I mispredicted the packet cost, next time I'm given an opportunity I'm going to open a smaller window. Kernel will advertise a smaller window and be more careful - all of this dance is done to avoid the collapse.</p>
    <div>
      <h3>Normal run - continuously updated window</h3>
      <a href="#normal-run-continuously-updated-window">
        
      </a>
    </div>
    <p>Finally, here's a chart from a normal run of a connection. Here, we use the default <code>tcp_adv_win_wcale=1 (50%)</code>:</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/1ZlSO1vQxnHim1D8dav4Aa/5ce538b22b546d194df83130d9f39bc9/image5-13.png" />
            
            </figure><p>Early in the connection you can see <b>rcv_win</b> being continuously updated with each received packet. This makes sense: while the <b>rcv_ssthresh</b> and <b>tcp_adv_win_scale</b> restrict the advertised window to never exceed 32KiB, the window is sliding nicely as long as there is enough space. At packet 18 the receiver stops updating the window and waits a bit. At packet 32 the receiver decides there still is some space and updates the window again, and so on. At the end of the flow the socket has 56KiB of data. This 56KiB of data was received over a sliding window reaching at most 32KiB .</p><p>The saw blade pattern of rcv_win is enabled by delayed ACK (aka QUICKACK). You can see the "<b>acked</b>" bytes in red dashed line. Since the ACK's might be delayed, the receiver waits a bit before updating the window. If you want a smooth line, you can use <code>quickack 1</code> per-route parameter, but this is not recommended since it will result in many small ACK packets flying over the wire.</p><p>In normal connection we expect the majority of packets to be coalesced and the collapse/drop code paths never to be hit.</p>
    <div>
      <h3>Large receive windows - rcv_ssthresh</h3>
      <a href="#large-receive-windows-rcv_ssthresh">
        
      </a>
    </div>
    <p>For large bandwidth transfers over big latency links - big BDP case - it's beneficial to have a very wide advertised window. However, Linux takes a while to fully open large receive windows:</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/6UL4j5NH62FE1350yWnTcP/e257d2160e41a3f71aa3b727debc44fc/image8-4.png" />
            
            </figure><p>In this run, the <b>skmem_rb</b> is set to 2MiB. As opposed to previous runs, the buffer budget is large and the receive window doesn't start with 50% of the skmem_rb! Instead it starts from 64KiB and grows linearly. It takes a while for Linux to ramp up the receive window to full size - ~800KiB in this case. The window is clamped by <b>rcv_ssthresh</b>. This variable starts at 64KiB and then grows at a rate of two full-MSS packets per each packet which has a "good" ratio of total size (truesize) to payload size.</p><p><a href="https://lore.kernel.org/lkml/CANn89i+mhqGaM2tuhgEmEPbbNu_59GGMhBMha4jnnzFE=UBNYg@mail.gmail.com/">Eric Dumazet writes</a> about this behavior:</p><blockquote><p>Stack is conservative about RWIN increase, it wants to receive packets to have an idea of the skb-&gt;len/skb-&gt;truesize ratio to convert a memory budget to  RWIN.Some drivers have to allocate 16K buffers (or even 32K buffers) just to hold one segment (of less than 1500 bytes of payload), while others are able to pack memory more efficiently.</p></blockquote><p>This behavior of slow window opening is fixed, and not configurable in vanilla kernel. <a href="https://lore.kernel.org/netdev/20220721151041.1215017-1-marek@cloudflare.com/#r">We prepared a kernel patch that allows to start up with higher rcv_ssthresh</a> based on per-route option <code>initrwnd</code>:</p>
            <pre><code>$ ip route change local 127.0.0.0/8 dev lo initrwnd 1000</code></pre>
            <p>With the patch and the route change deployed, this is how the buffers look:</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/4YE7Oolhn4ZQ9ihNi11HEL/af8c492bc03243e12b54541e954a3061/image4-12.png" />
            
            </figure><p>The advertised window is limited to 64KiB during the TCP handshake, but with our kernel patch enabled it's quickly bumped up to 1MiB in the first ACK packet afterwards. In both runs it took ~1800 packets to fill the receive buffer, however it took different time. In the first run the sender could push only 64KiB onto the wire in the second RTT. In the second run it could immediately push full 1MiB of data.</p><p>This trick of aggressive window opening is not really necessary for most users. It's only helpful when:</p><ul><li><p>You have high-bandwidth TCP transfers over big-latency links.</p></li><li><p>The metadata + buffer alignment cost of your NIC is sensible and predictable.</p></li><li><p>Immediately after the flow starts your application is ready to send a lot of data.</p></li><li><p>The sender has configured large <code>initcwnd</code>.</p></li></ul>
    <div>
      <h3>You care about shaving off every possible RTT.</h3>
      <a href="#you-care-about-shaving-off-every-possible-rtt">
        
      </a>
    </div>
    <p>On our systems we do have such flows, but arguably it might not be a common scenario. In the real world most of your TCP connections go to the nearest CDN point of presence, which is very close.</p>
    <div>
      <h3>Getting it all together</h3>
      <a href="#getting-it-all-together">
        
      </a>
    </div>
    <p>In this blog post, we discussed a seemingly simple case of a TCP sender filling up the receive socket. We tried to address two questions: with our isolated setup, how much data can be sent, and how quickly?</p><p>With the default settings of net.ipv4.tcp_rmem, Linux initially sets a memory budget of 128KiB for the receive data and metadata. On my system, given full-sized packets, it's able to eventually accept around 113KiB of application data.</p><p>Then, we showed that the receive window is not fully opened immediately. Linux keeps the receive window small, as it tries to predict the metadata cost and avoid overshooting the memory budget, therefore hitting TCP collapse. By default, with the net.ipv4.tcp_adv_win_scale=1, the upper limit for the advertised window is 50% of "free" memory. rcv_ssthresh starts up with 64KiB and grows linearly up to that limit.</p><p>On my system it took five window updates - six RTTs in total - to fill the 128KiB receive buffer. In the first batch the sender sent ~64KiB of data (remember we hacked the <code>initcwnd</code> limit), and then the sender topped it up with smaller and smaller batches until the receive window fully closed.</p><p>I hope this blog post is helpful and explains well the relationship between the buffer size and advertised window on Linux. Also, it describes the often misunderstood rcv_ssthresh which limits the advertised window in order to manage the memory budget and predict the unpredictable cost of metadata.</p><p>In case you wonder, similar mechanisms are in play in QUIC. The QUIC/H3 libraries though are still pretty young and don't have so many complex and mysterious toggles.... yet.</p><p>As always, <a href="https://github.com/cloudflare/cloudflare-blog/tree/master/2022-07-rmem-a">the code and instructions on how to reproduce the charts are available at our GitHub</a>.</p> ]]></content:encoded>
            <category><![CDATA[Linux]]></category>
            <category><![CDATA[TCP]]></category>
            <guid isPermaLink="false">ROvfvY7ClXiGsjf1moUld</guid>
            <dc:creator>Marek Majkowski</dc:creator>
        </item>
        <item>
            <title><![CDATA[A July 4 technical reading list]]></title>
            <link>https://blog.cloudflare.com/july-4-2022-reading-list/</link>
            <pubDate>Mon, 04 Jul 2022 12:55:08 GMT</pubDate>
            <description><![CDATA[ Here’s a short list of recent technical blog posts to give you something to read today ]]></description>
            <content:encoded><![CDATA[ 
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/2S9gHqjdCaiGCiCTBkGt0P/3a2a26f413cb9a908a9112a858495a7e/image1-61.png" />
            
            </figure><p>Here’s a short list of recent technical blog posts to give you something to read today.</p>
    <div>
      <h3>Internet Explorer, we hardly knew ye</h3>
      <a href="#internet-explorer-we-hardly-knew-ye">
        
      </a>
    </div>
    <p>Microsoft has announced the end-of-life for the venerable Internet Explorer browser. Here <a href="/internet-explorer-retired/">we take a look</a> at the demise of IE and the rise of the Edge browser. And we investigate how many bots on the Internet continue to impersonate Internet Explorer versions that have long since been replaced.</p>
    <div>
      <h3>Live-patching security vulnerabilities inside the Linux kernel with eBPF Linux Security Module</h3>
      <a href="#live-patching-security-vulnerabilities-inside-the-linux-kernel-with-ebpf-linux-security-module">
        
      </a>
    </div>
    <p>Looking for something with a lot of technical detail? Look no further than <a href="/live-patch-security-vulnerabilities-with-ebpf-lsm/">this blog about live-patching</a> the Linux kernel using eBPF. Code, Makefiles and more within!</p>
    <div>
      <h3>Hertzbleed explained</h3>
      <a href="#hertzbleed-explained">
        
      </a>
    </div>
    <p>Feeling mathematical? Or just need a dose of CPU-level antics? Look no further than this <a href="/hertzbleed-explained/">deep explainer</a> about how CPU frequency scaling leads to a nasty side channel affecting cryptographic algorithms.</p>
    <div>
      <h3>Early Hints update: How Cloudflare, Google, and Shopify are working together to build a faster Internet for everyone</h3>
      <a href="#early-hints-update-how-cloudflare-google-and-shopify-are-working-together-to-build-a-faster-internet-for-everyone">
        
      </a>
    </div>
    <p>The HTTP standard for Early Hints shows a lot of promise. How much? In this blog post, we <a href="/early-hints-performance/">dig into data</a> about Early Hints in the real world and show how much faster the web is with it.</p>
    <div>
      <h3>Private Access Tokens: eliminating CAPTCHAs on iPhones and Macs with open standards</h3>
      <a href="#private-access-tokens-eliminating-captchas-on-iphones-and-macs-with-open-standards">
        
      </a>
    </div>
    <p>Dislike CAPTCHAs? Yes, us too. As part of our program to eliminate captures there’s a new standard: Private Access Tokens. This blog shows <a href="/eliminating-captchas-on-iphones-and-macs-using-new-standard/">how they work</a> and how they can be used to prove you’re human without saying who you are.</p>
    <div>
      <h3>Optimizing TCP for high WAN throughput while preserving low latency</h3>
      <a href="#optimizing-tcp-for-high-wan-throughput-while-preserving-low-latency">
        
      </a>
    </div>
    <p>Network nerd? Yeah, me too. Here’s a very <a href="/optimizing-tcp-for-high-throughput-and-low-latency/">in depth look</a> at how we tune TCP parameters for low latency and high throughput.</p><p>...<i>We protect </i><a href="https://www.cloudflare.com/network-services/"><i>entire corporate networks</i></a><i>, help customers build </i><a href="https://workers.cloudflare.com/"><i>Internet-scale applications efficiently</i></a><i>, accelerate any </i><a href="https://www.cloudflare.com/performance/accelerate-internet-applications/"><i>website or Internet application</i></a><i>, ward off </i><a href="https://www.cloudflare.com/ddos/"><i>DDoS attacks</i></a><i>, keep </i><a href="https://www.cloudflare.com/application-security/"><i>hackers at bay</i></a><i>, and can help you on </i><a href="https://www.cloudflare.com/products/zero-trust/"><i>your journey to Zero Trust</i></a><i>.</i></p><p><i>Visit </i><a href="https://1.1.1.1/"><i>1.1.1.1</i></a><i> from any device to get started with our free app that makes your Internet faster and safer.To learn more about our mission to help build a better Internet, start </i><a href="https://www.cloudflare.com/learning/what-is-cloudflare/"><i>here</i></a><i>. If you’re looking for a new career direction, check out </i><a href="http://cloudflare.com/careers"><i>our open positions</i></a><i>.</i></p> ]]></content:encoded>
            <category><![CDATA[Reading List]]></category>
            <category><![CDATA[Radar]]></category>
            <category><![CDATA[Linux]]></category>
            <category><![CDATA[TCP]]></category>
            <category><![CDATA[Hertzbleed]]></category>
            <category><![CDATA[eBPF]]></category>
            <guid isPermaLink="false">4ffQabh80U3V99Grzwc88g</guid>
            <dc:creator>John Graham-Cumming</dc:creator>
        </item>
        <item>
            <title><![CDATA[Optimizing TCP for high WAN throughput while preserving low latency]]></title>
            <link>https://blog.cloudflare.com/optimizing-tcp-for-high-throughput-and-low-latency/</link>
            <pubDate>Fri, 01 Jul 2022 13:00:01 GMT</pubDate>
            <description><![CDATA[ In this post, we describe how we modified the Linux kernel to optimize for both low latency and high throughput concurrently ]]></description>
            <content:encoded><![CDATA[ <p></p><p>Here at Cloudflare we're constantly working on improving our service. Our engineers are looking at hundreds of parameters of our traffic, making sure that we get better all the time.</p><p>One of the core numbers we keep a close eye on is HTTP request latency, which is important for many of our products. We regard latency spikes as bugs to be fixed. One example is the 2017 story of <a href="/the-sad-state-of-linux-socket-balancing/">"Why does one NGINX worker take all the load?"</a>, where we optimized our TCP Accept queues to improve overall latency of TCP sockets waiting for accept().</p><p>Performance tuning is a holistic endeavor, and we monitor and continuously improve a range of other performance metrics as well, including throughput. Sometimes, tradeoffs have to be made. Such a case occurred in 2015, when a latency spike was discovered in our processing of HTTP requests. The solution at the time was to set tcp_rmem to 4 MiB, which minimizes the amount of time the kernel spends on TCP collapse processing. It was this collapse processing that was causing the latency spikes. Later in this post we discuss TCP collapse processing in more detail.</p><p>The tradeoff is that using a low value for tcp_rmem limits TCP throughput over high latency links. The following graph shows the maximum throughput as a function of network latency for a window size of 2 MiB. Note that the 2 MiB corresponds to a tcp_rmem value of 4 MiB due to the tcp_adv_win_scale setting in effect at the time.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/1mRCX3pD7U0Yzn1QeHDDhL/cef49d60e7e32c23d364cbbc65dd50e7/image10-5.png" />
            
            </figure><p>For the Cloudflare products then in existence, this was not a major problem, as connections terminate and content is served from nearby servers due to our BGP anycast routing.</p><p>Since then, we have added new products, such as Magic WAN, WARP, Spectrum, Gateway, and others. These represent new types of use cases and traffic flows.</p><p>For example, imagine you're a typical Magic WAN customer. You have connected all of your worldwide offices together using the Cloudflare global network. While Time to First Byte still matters, Magic WAN office-to-office traffic also needs good throughput. For example, a lot of traffic over these corporate connections will be file sharing using protocols such as SMB. These are <a href="https://en.wikipedia.org/wiki/Elephant_flow">elephant flows</a> over <a href="https://datatracker.ietf.org/doc/html/rfc1072">long fat networks</a>. Throughput is the metric every eyeball watches as they are downloading files.</p><p>We need to continue to provide world-class low latency while simultaneously providing high throughput over high-latency connections.</p><p>Before we begin, let’s introduce the players in our game.</p><p><b>TCP receive window</b> is the maximum number of unacknowledged user payload bytes the sender should transmit (bytes-in-flight) at any point in time. The size of the receive window can and does go up and down during the course of a TCP session. It is a mechanism whereby the receiver can tell the sender to stop sending if the sent packets cannot be successfully received because the receive buffers are full. It is this receive window that often limits throughput over high-latency networks.</p><p><b>net.ipv4.tcp_adv_win_scale</b> is a (non-intuitive) number used to account for the overhead needed by Linux to process packets. The receive window is specified in terms of user payload bytes. Linux needs additional memory beyond that to track other data associated with packets it is processing.</p><p>The value of the receive window changes during the lifetime of a TCP session, depending on a number of factors. The maximum value that the receive window can be is limited by the amount of free memory available in the receive buffer, according to this table:</p><table><tr><td><p>tcp_adv_win_scale</p></td><td><p>TCP window size</p></td></tr><tr><td><p>4</p></td><td><p>15/16 * available memory in receive buffer</p></td></tr><tr><td><p>3</p></td><td><p>⅞ * available memory in receive buffer</p></td></tr><tr><td><p>2</p></td><td><p>¾ * available memory in receive buffer</p></td></tr><tr><td><p>1</p></td><td><p>½ * available memory in receive buffer</p></td></tr><tr><td><p>0</p></td><td><p>available memory in receive buffer</p></td></tr><tr><td><p>-1</p></td><td><p>½ * available memory in receive buffer</p></td></tr><tr><td><p>-2</p></td><td><p>¼ * available memory in receive buffer</p></td></tr><tr><td><p>-3</p></td><td><p>⅛ * available memory in receive buffer</p></td></tr></table><p>We can intuitively (and correctly) understand that the amount of available memory in the receive buffer is the difference between the used memory and the maximum limit. But what is the maximum size a receive buffer can be? The answer is sk_rcvbuf.</p><p><b>sk_rcvbuf</b> is a per-socket field that specifies the maximum amount of memory that a receive buffer can allocate. This can be set programmatically with the socket option SO_RCVBUF. This can sometimes be useful to do, for localhost TCP sessions, for example, but in general the use of SO_RCVBUF is not recommended.</p><p>So how is sk_rcvbuf set? The most appropriate value for that depends on the latency of the TCP session and other factors. This makes it difficult for L7 applications to know how to set these values correctly, as they will be different for every TCP session. The solution to this problem is Linux autotuning.</p>
    <div>
      <h2>Linux autotuning</h2>
      <a href="#linux-autotuning">
        
      </a>
    </div>
    <p>Linux autotuning is logic in the Linux kernel that adjusts the buffer size limits and the receive window based on actual packet processing. It takes into consideration a number of things including TCP session <a href="https://www.cloudflare.com/learning/cdn/glossary/round-trip-time-rtt/">RTT</a>, L7 read rates, and the amount of available host memory.</p><p>Autotuning can sometimes seem mysterious, but it is actually fairly straightforward.</p><p>The central idea is that Linux can track the rate at which the local application is reading data off of the receive queue. It also knows the session RTT. Because Linux knows these things, it can automatically increase the buffers and receive window until it reaches the point at which the application layer or network bottleneck links are the constraint on throughput (and not host buffer settings). At the same time, autotuning prevents slow local readers from having excessively large receive queues. The way autotuning does that is by limiting the receive window and its corresponding receive buffer to an appropriate size for each socket.</p><p>The values set by autotuning can be seen via the Linux “<code>ss</code>” command from the <code>iproute</code> package (e.g. “<code>ss -tmi</code>”).  The relevant output fields from that command are:</p><p><b>Recv-Q</b> is the number of user payload bytes not yet read by the local application.</p><p><b>rcv_ssthresh</b> is the window clamp, a.k.a. the maximum receive window size. This value is not known to the sender. The sender receives only the current window size, via the TCP header field. A closely-related field in the kernel, tp-&gt;window_clamp, is the maximum window size allowable based on the amount of available memory. rcv_ssthresh is the receiver-side slow-start threshold value.</p><p><b>skmem_r</b> is the actual amount of memory that is allocated, which includes not only user payload (Recv-Q) but also additional memory needed by Linux to process the packet (packet metadata). This is known within the kernel as sk_rmem_alloc.</p><p>Note that there are other buffers associated with a socket, so skmem_r does not represent the total memory that a socket might have allocated. Those other buffers are not involved in the issues presented in this post.</p><p><b>skmem_rb</b> is the maximum amount of memory that could be allocated by the socket for the receive buffer. This is higher than rcv_ssthresh to account for memory needed for packet processing that is not packet data. Autotuning can increase this value (up to tcp_rmem max) based on how fast the L7 application is able to read data from the socket and the RTT of the session. This is known within the kernel as sk_rcvbuf.</p><p><b>rcv_space</b> is the high water mark of the rate of the local application reading from the receive buffer during any RTT. This is used internally within the kernel to adjust sk_rcvbuf.</p><p>Earlier we mentioned a setting called tcp_rmem. <b>net.ipv4.tcp_rmem</b> consists of three values, but in this document we are always referring to the third value (except where noted). It is a global setting that specifies the maximum amount of memory that any TCP receive buffer can allocate, i.e. the maximum permissible value that autotuning can use for sk_rcvbuf. This is essentially just a failsafe for autotuning, and under normal circumstances should play only a minor role in TCP memory management.</p><p>It’s worth mentioning that receive buffer memory is not preallocated. Memory is allocated based on actual packets arriving and sitting in the receive queue. It’s also important to realize that filling up a receive queue is not one of the criteria that autotuning uses to increase sk_rcvbuf. Indeed, preventing this type of excessive buffering (<a href="https://en.wikipedia.org/wiki/Bufferbloat">bufferbloat</a>) is one of the benefits of autotuning.</p>
    <div>
      <h2>What’s the problem?</h2>
      <a href="#whats-the-problem">
        
      </a>
    </div>
    <p>The problem is that we must have a large TCP receive window for high <a href="https://en.wikipedia.org/wiki/Bandwidth-delay_product">BDP</a> sessions. This is directly at odds with the latency spike problem mentioned above.</p><p>Something has to give. The laws of physics (speed of light in glass, etc.) dictate that we must use large window sizes. There is no way to get around that. So we are forced to solve the latency spikes differently.</p>
    <div>
      <h2>A brief recap of the latency spike problem</h2>
      <a href="#a-brief-recap-of-the-latency-spike-problem">
        
      </a>
    </div>
    <p>Sometimes a TCP session will fill up its receive buffers. When that happens, the Linux kernel will attempt to reduce the amount of memory the receive queue is using by performing what amounts to a “defragmentation” of memory. This is called collapsing the queue. Collapsing the queue takes time, which is what drives up HTTP request latency.</p><p>We do not want to spend time collapsing TCP queues.</p><p>Why do receive queues fill up to the point where they hit the maximum memory limit? The usual situation is when the local application starts out reading data from the receive queue at one rate (triggering autotuning to raise the max receive window), followed by the local application slowing down its reading from the receive queue. This is valid behavior, and we need to handle it correctly.</p>
    <div>
      <h2>Selecting sysctl values</h2>
      <a href="#selecting-sysctl-values">
        
      </a>
    </div>
    <p>Before exploring solutions, let’s first decide what we need as the maximum TCP window size.</p><p>As we have seen above in the discussion about BDP, the window size is determined based upon the RTT and desired throughput of the connection.</p><p>Because Linux autotuning will adjust correctly for sessions with lower RTTs and bottleneck links with lower throughput, all we need to be concerned about are the maximums.</p><p>For latency, we have chosen 300 ms as the maximum expected latency, as that is the measured latency between our Zurich and Sydney facilities. It seems reasonable enough as a worst-case latency under normal circumstances.</p><p>For throughput, although we have very fast and modern hardware on the Cloudflare global network, we don’t expect a single TCP session to saturate the hardware. We have arbitrarily chosen 3500 mbps as the highest supported throughput for our highest latency TCP sessions.</p><p>The calculation for those numbers results in a BDP of 131MB, which we round to the more aesthetic value of 128 MiB.</p><p>Recall that allocation of TCP memory includes metadata overhead in addition to packet data. The ratio of actual amount of memory allocated to user payload size varies, depending on NIC driver settings, packet size, and other factors. For full-sized packets on some of our hardware, we have measured average allocations up to 3 times the packet data size. In order to reduce the frequency of TCP collapse on our servers, we set tcp_adv_win_scale to -2. From the table above, we know that the max window size will be ¼ of the max buffer space.</p><p>We end up with the following sysctl values:</p>
            <pre><code>net.ipv4.tcp_rmem = 8192 262144 536870912
net.ipv4.tcp_wmem = 4096 16384 536870912
net.ipv4.tcp_adv_win_scale = -2</code></pre>
            <p>A tcp_rmem of 512MiB and tcp_adv_win_scale of -2 results in a maximum window size that autotuning can set of 128 MiB, our desired value.</p>
    <div>
      <h2>Disabling TCP collapse</h2>
      <a href="#disabling-tcp-collapse">
        
      </a>
    </div>
    <p>Patient: Doctor, it hurts when we collapse the TCP receive queue.</p><p>Doctor: Then don’t do that!</p><p>Generally speaking, when a packet arrives at a buffer when the buffer is full, the packet gets dropped. In the case of these receive buffers, Linux tries to “save the packet” when the buffer is full by collapsing the receive queue. Frequently this is successful, but it is not guaranteed to be, and it takes time.</p><p>There are no problems created by immediately just dropping the packet instead of trying to save it. The receive queue is full anyway, so the local receiver application still has data to read. The sender’s congestion control will notice the drop and/or ZeroWindow and will respond appropriately. Everything will continue working as designed.</p><p>At present, there is no setting provided by Linux to disable the TCP collapse. We developed an in-house patch to the kernel to disable the TCP collapse logic.</p>
    <div>
      <h2>Kernel patch – Attempt #1</h2>
      <a href="#kernel-patch-attempt-1">
        
      </a>
    </div>
    <p>The kernel patch for our first attempt was straightforward. At the top of tcp_try_rmem_schedule(), if the memory allocation fails, we simply return (after pred_flag = 0 and tcp_sack_reset()), thus completely skipping the tcp_collapse and related logic.</p><p>It didn’t work.</p><p>Although we eliminated the latency spikes while using large buffer limits, we did not observe the throughput we expected.</p><p>One of the realizations we made as we investigated the situation was that standard network benchmarking tools such as iperf3 and similar do not expose the problem we are trying to solve. iperf3 does not fill the receive queue. Linux autotuning does not open the TCP window large enough. Autotuning is working perfectly for our well-behaved benchmarking program.</p><p>We need application-layer software that is slightly less well-behaved, one that exercises the autotuning logic under test. So we wrote one.</p>
    <div>
      <h2>A new benchmarking tool</h2>
      <a href="#a-new-benchmarking-tool">
        
      </a>
    </div>
    <p>Anomalies were seen during our “Attempt #1” that negatively impacted throughput. The anomalies were seen only under certain specific conditions, and we realized we needed a better benchmarking tool to detect and measure the performance impact of those anomalies.</p><p>This tool has turned into an invaluable resource during the development of this patch and raised confidence in our solution.</p><p>It consists of two Python programs. The reader opens a TCP session to the daemon, at which point the daemon starts sending user payload as fast as it can, and never stops sending.</p><p>The reader, on the other hand, starts and stops reading in a way to open up the TCP receive window wide open and then repeatedly causes the buffers to fill up completely. More specifically, the reader implemented this logic:</p><ol><li><p>reads as fast as it can, for five seconds</p><ul><li><p>this is called fast mode</p></li><li><p>opens up the window</p></li></ul></li><li><p>calculates 5% of the high watermark of the bytes reader during any previous one second</p></li><li><p>for each second of the next 15 seconds:</p><ul><li><p>this is called slow mode</p></li><li><p>reads that 5% number of bytes, then stops reading</p></li><li><p>sleeps for the remainder of that particular second</p></li><li><p>most of the second consists of no reading at all</p></li></ul></li><li><p>steps 1-3 are repeated in a loop three times, so the entire run is 60 seconds</p></li></ol><p>This has the effect of highlighting any issues in the handling of packets when the buffers repeatedly hit the limit.</p>
    <div>
      <h2>Revisiting default Linux behavior</h2>
      <a href="#revisiting-default-linux-behavior">
        
      </a>
    </div>
    <p>Taking a step back, let’s look at the default Linux behavior. The following is kernel v5.15.16.</p><table><tr><td><p>NIC speed (mbps)</p></td><td><p>RTT (ms)</p></td><td><p>tcp_rmem (MiB)</p></td><td><p>tcp_adv_win_scale</p></td><td><p>tcp_disable_collapse</p></td><td><p>TCP window (MiB)</p></td><td><p>buffer metadata to user payload ratio</p></td><td><p>Prune Called</p></td><td><p>RcvCollapsed</p></td><td><p>RcvQDrop</p></td><td><p>OFODrop</p></td><td><p>Test Result</p></td></tr><tr><td><p>1000</p></td><td><p>300</p></td><td><p>512</p></td><td><p>-2</p></td><td><p>0</p></td><td><p>128</p></td><td><p>4</p></td><td><p>0</p></td><td><p>0</p></td><td><p>0</p></td><td><p>0</p></td><td><p>GOOD</p></td></tr><tr><td><p>1000</p></td><td><p>300</p></td><td><p>256</p></td><td><p>1</p></td><td><p>0</p></td><td><p>128</p></td><td><p>2</p></td><td><p>0</p></td><td><p>0</p></td><td><p>0</p></td><td><p>0</p></td><td><p>GOOD</p></td></tr><tr><td><p>1000</p></td><td><p>300</p></td><td><p>170</p></td><td><p>2</p></td><td><p>0</p></td><td><p>128</p></td><td><p>1.33</p></td><td><p>24</p></td><td><p>490K</p></td><td><p>0</p></td><td><p>0</p></td><td><p>GOOD</p></td></tr><tr><td><p>1000</p></td><td><p>300</p></td><td><p>146</p></td><td><p>3</p></td><td><p>0</p></td><td><p>128</p></td><td><p>1.14</p></td><td><p>57</p></td><td><p>616K</p></td><td><p>0</p></td><td><p>0</p></td><td><p>GOOD</p></td></tr><tr><td><p>1000</p></td><td><p>300</p></td><td><p>137</p></td><td><p>4</p></td><td><p>0</p></td><td><p>128</p></td><td><p>1.07</p></td><td><p>74</p></td><td><p>803K</p></td><td><p>0</p></td><td><p>0</p></td><td><p>GOOD</p></td></tr></table><p>The Linux kernel is effective at freeing up space in order to make room for incoming packets when the receive buffer memory limit is hit. As documented previously, the cost for saving these packets (i.e. not dropping them) is latency.</p><p>However, the latency spikes, in <i>milliseconds</i>, for tcp_try_rmem_schedule(), are:</p><p>tcp_rmem 170 MiB, tcp_adv_win_scale +2 (170p2):</p>
            <pre><code>@ms:
[0]       27093 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
[1]           0 |
[2, 4)        0 |
[4, 8)        0 |
[8, 16)       0 |
[16, 32)      0 |
[32, 64)     16 |</code></pre>
            <p>tcp_rmem 146 MiB, tcp_adv_win_scale +3 (146p3):</p>
            <pre><code>@ms:
(..., 16)  25984 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
[16, 20)       0 |
[20, 24)       0 |
[24, 28)       0 |
[28, 32)       0 |
[32, 36)       0 |
[36, 40)       0 |
[40, 44)       1 |
[44, 48)       6 |
[48, 52)       6 |
[52, 56)       3 |</code></pre>
            <p>tcp_rmem 137 MiB, tcp_adv_win_scale +4 (137p4):</p>
            <pre><code>@ms:
(..., 16)  37222 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
[16, 20)       0 |
[20, 24)       0 |
[24, 28)       0 |
[28, 32)       0 |
[32, 36)       0 |
[36, 40)       1 |
[40, 44)       8 |
[44, 48)       2 |</code></pre>
            <p>These are the latency spikes we cannot have on the Cloudflare global network.</p>
    <div>
      <h2>Kernel patch – Attempt #2</h2>
      <a href="#kernel-patch-attempt-2">
        
      </a>
    </div>
    <p>So the “something” that was not working in Attempt #1 was that the receive queue memory limit was hit early on as the flow was just ramping up (when the values for sk_rmem_alloc and sk_rcvbuf were small, ~800KB). This occurred at about the two second mark for 137p4 test (about 2.25 seconds for 170p2).</p><p>In hindsight, we should have noticed that tcp_prune_queue() actually raises sk_rcvbuf when it can. So we modified the patch in response to that, added a guard to allow the collapse to execute when sk_rmem_alloc is less than the threshold value.</p><p><code>net.ipv4.tcp_collapse_max_bytes = 6291456</code></p><p>The next section discusses how we arrived at this value for tcp_collapse_max_bytes.</p><p>The patch is available <a href="https://github.com/cloudflare/linux/blob/master/patches/0014-add-a-sysctl-to-enable-disable-tcp_collapse-logic.patch">here</a>.</p><p>The results with the new patch are as follows:</p><p>oscil – 300ms tests</p><table><tr><td><p>Test</p></td><td><p>RTT (ms)</p></td><td><p>tcp_rmem (MiB)</p></td><td><p>tcp_adv_win_scale</p></td><td><p>tcp_disable_collapse (MiB)</p></td><td><p>NIC speed (mbps)</p></td><td><p>TCP window (MiB)</p></td><td><p>real buffer metadata to user payload ratio</p></td><td><p>RcvCollapsed</p></td><td><p>RcvQDrop</p></td><td><p>OFODrop</p></td><td><p>max latency (us)</p><p>
</p></td><td><p>Test Result</p></td></tr><tr><td><p>oscil reader</p></td><td><p>300</p></td><td><p>512</p></td><td><p>-2</p></td><td><p>6</p></td><td><p>1000</p></td><td><p>128</p></td><td><p>4</p></td><td><p>0</p></td><td><p>0</p></td><td><p>0</p></td><td><p>12</p></td><td><p>1-941</p><p>941</p><p>941</p></td></tr><tr><td><p>oscil reader</p></td><td><p>300</p></td><td><p>256</p></td><td><p>1</p></td><td><p>6</p></td><td><p>1000</p></td><td><p>128</p></td><td><p>2</p></td><td><p>0</p></td><td><p>0</p></td><td><p>0</p></td><td><p>11</p></td><td><p>1-941</p><p>941</p><p>941</p></td></tr><tr><td><p>oscil reader</p></td><td><p>300</p></td><td><p>170</p></td><td><p>2</p></td><td><p>6</p></td><td><p>1000</p></td><td><p>128</p></td><td><p>1.33</p></td><td><p>0</p></td><td><p>9</p></td><td><p>86</p></td><td><p>11</p></td><td><p>1-941</p><p>36-605</p><p>1-298</p></td></tr><tr><td><p>oscil reader</p></td><td><p>300</p></td><td><p>146</p></td><td><p>3</p></td><td><p>6</p></td><td><p>1000</p></td><td><p>128</p></td><td><p>1.14</p></td><td><p>0</p></td><td><p>7</p></td><td><p>1550</p></td><td><p>16</p></td><td><p>1-940</p><p>2-82</p><p>292-395</p></td></tr><tr><td><p>oscil reader</p></td><td><p>300</p></td><td><p>137</p></td><td><p>4</p></td><td><p>6</p></td><td><p>1000</p></td><td><p>128</p></td><td><p>1.07</p></td><td><p>0</p></td><td><p>10</p></td><td><p>3020</p></td><td><p>9</p></td><td><p>1-940</p><p>2-13</p><p>13-33</p></td></tr></table><p>oscil – 20ms tests</p><table><tr><td><p>Test</p></td><td><p>RTT (ms)</p></td><td><p>tcp_rmem (MiB)</p></td><td><p>tcp_adv_win_scale</p></td><td><p>tcp_disable_collapse (MiB)</p></td><td><p>NIC speed (mbps)</p></td><td><p>TCP window (MiB)</p></td><td><p>real buffer metadata to user payload ratio</p></td><td><p>RcvCollapsed</p></td><td><p>RcvQDrop</p></td><td><p>OFODrop</p></td><td><p>max latency (us)</p><p>
</p></td><td><p>Test Result</p></td></tr><tr><td><p>oscil reader</p></td><td><p>20</p></td><td><p>512</p></td><td><p>-2</p></td><td><p>6</p></td><td><p>1000</p></td><td><p>128</p></td><td><p>4</p></td><td><p>0</p></td><td><p>0</p></td><td><p>0</p></td><td><p>13</p></td><td><p>795-941</p><p>941</p><p>941</p></td></tr><tr><td><p>oscil reader</p></td><td><p>20</p></td><td><p>256</p></td><td><p>1</p></td><td><p>6</p></td><td><p>1000</p></td><td><p>128</p></td><td><p>2</p></td><td><p>0</p></td><td><p>0</p></td><td><p>0</p></td><td><p>13</p></td><td><p>795-941</p><p>941</p><p>941</p></td></tr><tr><td><p>oscil reader</p></td><td><p>20</p></td><td><p>170</p></td><td><p>2</p></td><td><p>6</p></td><td><p>1000</p></td><td><p>128</p></td><td><p>1.33</p></td><td><p>0</p></td><td><p>0</p></td><td><p>0</p></td><td><p>8</p></td><td><p>795-941</p><p>941</p><p>941</p></td></tr><tr><td><p>oscil reader</p></td><td><p>20</p></td><td><p>146</p></td><td><p>3</p></td><td><p>6</p></td><td><p>1000</p></td><td><p>128</p></td><td><p>1.14</p></td><td><p>0</p></td><td><p>0</p></td><td><p>0</p></td><td><p>7</p></td><td><p>795-941</p><p>941</p><p>941</p></td></tr><tr><td><p>oscil reader</p></td><td><p>20</p></td><td><p>137</p></td><td><p>4</p></td><td><p>6</p></td><td><p>1000</p></td><td><p>128</p></td><td><p>1.07</p></td><td><p>0</p></td><td><p>4</p></td><td><p>196</p></td><td><p>12</p></td><td><p>795-941</p><p>13-941</p><p>941</p></td></tr></table><p>oscil – 0ms tests</p><table><tr><td><p>Test</p></td><td><p>RTT (ms)</p></td><td><p>tcp_rmem (MiB)</p></td><td><p>tcp_adv_win_scale</p></td><td><p>tcp_disable_collapse (MiB)</p></td><td><p>NIC speed (mbps)</p></td><td><p>TCP window (MiB)</p></td><td><p>real buffer metadata to user payload ratio</p></td><td><p>RcvCollapsed</p></td><td><p>RcvQDrop</p></td><td><p>OFODrop</p></td><td><p>max latency (us)</p><p>
</p></td><td><p>Test Result</p></td></tr><tr><td><p>oscil reader</p></td><td><p>0.3</p></td><td><p>512</p></td><td><p>-2</p></td><td><p>6</p></td><td><p>1000</p></td><td><p>128</p></td><td><p>4</p></td><td><p>0</p></td><td><p>0</p></td><td><p>0</p></td><td><p>9</p></td><td><p>941</p><p>941</p><p>941</p></td></tr><tr><td><p>oscil reader</p></td><td><p>0.3</p></td><td><p>256</p></td><td><p>1</p></td><td><p>6</p></td><td><p>1000</p></td><td><p>128</p></td><td><p>2</p></td><td><p>0</p></td><td><p>0</p></td><td><p>0</p></td><td><p>22</p></td><td><p>941</p><p>941</p><p>941</p></td></tr><tr><td><p>oscil reader</p></td><td><p>0.3</p></td><td><p>170</p></td><td><p>2</p></td><td><p>6</p></td><td><p>1000</p></td><td><p>128</p></td><td><p>1.33</p></td><td><p>0</p></td><td><p>0</p></td><td><p>0</p></td><td><p>8</p></td><td><p>941</p><p>941</p><p>941</p></td></tr><tr><td><p>oscil reader</p></td><td><p>0.3</p></td><td><p>146</p></td><td><p>3</p></td><td><p>6</p></td><td><p>1000</p></td><td><p>128</p></td><td><p>1.14</p></td><td><p>0</p></td><td><p>0</p></td><td><p>0</p></td><td><p>10</p></td><td><p>941</p><p>941</p><p>941</p></td></tr><tr><td><p>oscil reader</p></td><td><p>0.3</p></td><td><p>137</p></td><td><p>4</p></td><td><p>6</p></td><td><p>1000</p></td><td><p>128</p></td><td><p>1.07</p></td><td><p>0</p></td><td><p>0</p></td><td><p>0</p></td><td><p>10</p></td><td><p>941</p><p>941</p><p>941</p></td></tr></table><p>iperf3 – 300 ms tests</p><table><tr><td><p>Test</p></td><td><p>RTT (ms)</p></td><td><p>tcp_rmem (MiB)</p></td><td><p>tcp_adv_win_scale</p></td><td><p>tcp_disable_collapse (MiB)</p></td><td><p>NIC speed (mbps)</p></td><td><p>TCP window (MiB)</p></td><td><p>real buffer metadata to user payload ratio</p></td><td><p>RcvCollapsed</p></td><td><p>RcvQDrop</p></td><td><p>OFODrop</p></td><td><p>max latency (us)</p><p>
</p></td><td><p>Test Result</p></td></tr><tr><td><p>iperf3</p></td><td><p>300</p></td><td><p>512</p></td><td><p>-2</p></td><td><p>6</p></td><td><p>1000</p></td><td><p>128</p></td><td><p>4</p></td><td><p>0</p></td><td><p>0</p></td><td><p>0</p></td><td><p>7</p></td><td><p>941</p></td></tr><tr><td><p>iperf3</p></td><td><p>300</p></td><td><p>256</p></td><td><p>1</p></td><td><p>6</p></td><td><p>1000</p></td><td><p>128</p></td><td><p>2</p></td><td><p>0</p></td><td><p>0</p></td><td><p>0</p></td><td><p>6</p></td><td><p>941</p></td></tr><tr><td><p>iperf3</p></td><td><p>300</p></td><td><p>170</p></td><td><p>2</p></td><td><p>6</p></td><td><p>1000</p></td><td><p>128</p></td><td><p>1.33</p></td><td><p>0</p></td><td><p>0</p></td><td><p>0</p></td><td><p>9</p></td><td><p>941</p></td></tr><tr><td><p>iperf3</p></td><td><p>300</p></td><td><p>146</p></td><td><p>3</p></td><td><p>6</p></td><td><p>1000</p></td><td><p>128</p></td><td><p>1.14</p></td><td><p>0</p></td><td><p>0</p></td><td><p>0</p></td><td><p>11</p></td><td><p>941</p></td></tr><tr><td><p>iperf3</p></td><td><p>300</p></td><td><p>137</p></td><td><p>4</p></td><td><p>6</p></td><td><p>1000</p></td><td><p>128</p></td><td><p>1.07</p></td><td><p>0</p></td><td><p>0</p></td><td><p>0</p></td><td><p>7</p></td><td><p>941</p></td></tr></table><p>iperf3 – 20 ms tests</p><table><tr><td><p>Test</p></td><td><p>RTT (ms)</p></td><td><p>tcp_rmem (MiB)</p></td><td><p>tcp_adv_win_scale</p></td><td><p>tcp_disable_collapse (MiB)</p></td><td><p>NIC speed (mbps)</p></td><td><p>TCP window (MiB)</p></td><td><p>real buffer metadata to user payload ratio</p></td><td><p>RcvCollapsed</p></td><td><p>RcvQDrop</p></td><td><p>OFODrop</p></td><td><p>max latency (us)</p><p>
</p></td><td><p>Test Result</p></td></tr><tr><td><p>iperf3</p></td><td><p>20</p></td><td><p>512</p></td><td><p>-2</p></td><td><p>6</p></td><td><p>1000</p></td><td><p>128</p></td><td><p>4</p></td><td><p>0</p></td><td><p>0</p></td><td><p>0</p></td><td><p>7</p></td><td><p>941</p></td></tr><tr><td><p>iperf3</p></td><td><p>20</p></td><td><p>256</p></td><td><p>1</p></td><td><p>6</p></td><td><p>1000</p></td><td><p>128</p></td><td><p>2</p></td><td><p>0</p></td><td><p>0</p></td><td><p>0</p></td><td><p>15</p></td><td><p>941</p></td></tr><tr><td><p>iperf3</p></td><td><p>20</p></td><td><p>170</p></td><td><p>2</p></td><td><p>6</p></td><td><p>1000</p></td><td><p>128</p></td><td><p>1.33</p></td><td><p>0</p></td><td><p>0</p></td><td><p>0</p></td><td><p>7</p></td><td><p>941</p></td></tr><tr><td><p>iperf3</p></td><td><p>20</p></td><td><p>146</p></td><td><p>3</p></td><td><p>6</p></td><td><p>1000</p></td><td><p>128</p></td><td><p>1.14</p></td><td><p>0</p></td><td><p>0</p></td><td><p>0</p></td><td><p>7</p></td><td><p>941</p></td></tr><tr><td><p>iperf3</p></td><td><p>20</p></td><td><p>137</p></td><td><p>4</p></td><td><p>6</p></td><td><p>1000</p></td><td><p>128</p></td><td><p>1.07</p></td><td><p>0</p></td><td><p>0</p></td><td><p>0</p></td><td><p>6</p></td><td><p>941</p></td></tr></table><p>iperf3 – 0ms tests</p><table><tr><td><p>Test</p></td><td><p>RTT (ms)</p></td><td><p>tcp_rmem (MiB)</p></td><td><p>tcp_adv_win_scale</p></td><td><p>tcp_disable_collapse (MiB)</p></td><td><p>NIC speed (mbps)</p></td><td><p>TCP window (MiB)</p></td><td><p>real buffer metadata to user payload ratio</p></td><td><p>RcvCollapsed</p></td><td><p>RcvQDrop</p></td><td><p>OFODrop</p></td><td><p>max latency (us)</p><p>
</p></td><td><p>Test Result</p></td></tr><tr><td><p>iperf3</p></td><td><p>0.3</p></td><td><p>512</p></td><td><p>-2</p></td><td><p>6</p></td><td><p>1000</p></td><td><p>128</p></td><td><p>4</p></td><td><p>0</p></td><td><p>0</p></td><td><p>0</p></td><td><p>6</p></td><td><p>941</p></td></tr><tr><td><p>iperf3</p></td><td><p>0.3</p></td><td><p>256</p></td><td><p>1</p></td><td><p>6</p></td><td><p>1000</p></td><td><p>128</p></td><td><p>2</p></td><td><p>0</p></td><td><p>0</p></td><td><p>0</p></td><td><p>14</p></td><td><p>941</p></td></tr><tr><td><p>iperf3</p></td><td><p>0.3</p></td><td><p>170</p></td><td><p>2</p></td><td><p>6</p></td><td><p>1000</p></td><td><p>128</p></td><td><p>1.33</p></td><td><p>0</p></td><td><p>0</p></td><td><p>0</p></td><td><p>6</p></td><td><p>941</p></td></tr><tr><td><p>iperf3</p></td><td><p>0.3</p></td><td><p>146</p></td><td><p>3</p></td><td><p>6</p></td><td><p>1000</p></td><td><p>128</p></td><td><p>1.14</p></td><td><p>0</p></td><td><p>0</p></td><td><p>0</p></td><td><p>7</p></td><td><p>941</p></td></tr><tr><td><p>iperf3</p></td><td><p>0.3</p></td><td><p>137</p></td><td><p>4</p></td><td><p>6</p></td><td><p>1000</p></td><td><p>128</p></td><td><p>1.07</p></td><td><p>0</p></td><td><p>0</p></td><td><p>0</p></td><td><p>6</p></td><td><p>941</p></td></tr></table><p>All tests are successful.</p>
    <div>
      <h2>Setting tcp_collapse_max_bytes</h2>
      <a href="#setting-tcp_collapse_max_bytes">
        
      </a>
    </div>
    <p>In order to determine this setting, we need to understand what the biggest queue we <i>can</i> collapse without incurring unacceptable latency.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/6PfP9U02g39dedYXSqz0H2/8b61121e3ffe7f102fea0d682d902c56/image8-12.png" />
            
            </figure>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/14e9NHQT0aciu7bJvpLyIM/5df55a86e2a24c7c7501c2c80a38e847/image7-13.png" />
            
            </figure><p>Using 6 MiB should result in a maximum latency of no more than 2 ms.</p>
    <div>
      <h2>Cloudflare production network results</h2>
      <a href="#cloudflare-production-network-results">
        
      </a>
    </div>
    
    <div>
      <h3>Current production settings (“Old”)</h3>
      <a href="#current-production-settings-old">
        
      </a>
    </div>
    
            <pre><code>net.ipv4.tcp_rmem = 8192 2097152 16777216
net.ipv4.tcp_wmem = 4096 16384 33554432
net.ipv4.tcp_adv_win_scale = -2
net.ipv4.tcp_collapse_max_bytes = 0
net.ipv4.tcp_notsent_lowat = 4294967295</code></pre>
            <p>tcp_collapse_max_bytes of 0 means that the custom feature is disabled and that the vanilla kernel logic is used for TCP collapse processing.</p>
    <div>
      <h3>New settings under test (“New”)</h3>
      <a href="#new-settings-under-test-new">
        
      </a>
    </div>
    
            <pre><code>net.ipv4.tcp_rmem = 8192 262144 536870912
net.ipv4.tcp_wmem = 4096 16384 536870912
net.ipv4.tcp_adv_win_scale = -2
net.ipv4.tcp_collapse_max_bytes = 6291456
net.ipv4.tcp_notsent_lowat = 131072</code></pre>
            <p>The tcp_notsent_lowat setting is discussed in the last section of this post.</p><p>The middle value of tcp_rmem was changed as a result of separate work that found that Linux autotuning was setting receive buffers too high for localhost sessions. This updated setting reduces TCP memory usage for those sessions, but does not change anything about the type of TCP sessions that is the focus of this post.</p><p>For the following benchmarks, we used non-Cloudflare host machines in Iowa, US, and Melbourne, Australia performing data transfers to the Cloudflare data center in Marseille, France. In Marseille, we have some hosts configured with the existing production settings, and others with the system settings described in this post. Software used is perf3 version 3.9, kernel 5.15.32.</p>
    <div>
      <h3>Throughput results</h3>
      <a href="#throughput-results">
        
      </a>
    </div>
    
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/3GkgLi4X0vuXMl7rOEWvZK/e64146a2a197174ab5e563b34b8ba9e8/image3-36.png" />
            
            </figure><table><tr><td><p>
</p></td><td><p>RTT</p><p>(ms)</p></td><td><p>Throughput with Current Settings</p><p>(mbps)</p></td><td><p>Throughput with</p><p>New Settings</p><p>(mbps)</p></td><td><p>Increase</p><p>Factor</p></td></tr><tr><td><p>Iowa to</p><p>Marseille</p></td><td><p>121 </p></td><td><p>276</p></td><td><p>6600</p></td><td><p>24x</p></td></tr><tr><td><p>Melbourne to Marseille</p></td><td><p>282</p></td><td><p>120</p></td><td><p>3800</p></td><td><p>32x</p></td></tr></table><p><b>Iowa-Marseille throughput</b></p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/2WvQmKxKD8buJU17GU15Kw/798330bea25ad3dedfe5d1beb8578363/image6-16.png" />
            
            </figure><p><b>Iowa-Marseille receive window and bytes-in-flight</b></p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/2V2iFmp0s1h8vH4SyrzFQS/5b1118af5ff1ee2116aacfe7c9d60927/image2-51.png" />
            
            </figure><p><b>Melbourne-Marseille throughput</b></p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/PnqeHPMJbmCuyGvt9UjtA/037328ab56686611fce568409e0c8336/image9-10.png" />
            
            </figure><p><b>Melbourne-Marseille receive window and bytes-in-flight</b></p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/4H5cZs3oyG7KiTKL9m4b7R/ff9563e9d1a69da0adb653587dd1f336/image5-21.png" />
            
            </figure><p>Even with the new settings in place, the Melbourne to Marseille performance is limited by the receive window on the Cloudflare host. This means that further adjustments to these settings yield even higher throughput.</p>
    <div>
      <h3>Latency results</h3>
      <a href="#latency-results">
        
      </a>
    </div>
    <p>The Y-axis on these charts are the 99th percentile time for TCP collapse in seconds.</p><p><b>Cloudflare hosts in Marseille running the current production settings</b></p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/1jxkFikBYH12eNxfHcHwXR/c8c3e183a28b84a4f93bc725309d5eff/image11-4.png" />
            
            </figure><p><b>Cloudflare hosts in Marseille running the new settings</b></p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/6dqk8mtvEpghINGK0bfKUz/cec115db36d4a927ca4764bfe022cad8/image1-59.png" />
            
            </figure><p>The takeaway in looking at these graphs is that maximum TCP collapse time for the new settings is no worse than with the current production settings. This is the desired result.</p>
    <div>
      <h3>Send Buffers</h3>
      <a href="#send-buffers">
        
      </a>
    </div>
    <p>What we have shown so far is that the receiver side seems to be working well, but what about the sender side?</p><p>As part of this work, we are setting tcp_wmem max to 512 MiB. For oscillating reader flows, this can cause the send buffer to become quite large. This represents bufferbloat and wasted kernel memory, both things that nobody likes or wants.</p><p>Fortunately, there is already a solution: <b>tcp_notsent_lowat</b>. This setting limits the size of unsent bytes in the write queue. More details can be found at <a href="https://lwn.net/Articles/560082/">https://lwn.net/Articles/560082</a>.</p><p>The results are significant:</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/3yXWQ4mWrhKWV8Tzv0bUCb/75339710d08b943a8cb884a68377d4ec/image4-29.png" />
            
            </figure><p>The RTT for these tests was 466ms. Throughput is not negatively affected. Throughput is at full wire speed in all cases (1 Gbps). Memory usage is as reported by /proc/net/sockstat, TCP mem.</p><p>Our web servers already set tcp_notsent_lowat to 131072 for its sockets. All other senders are using 4 GiB, the default value. We are changing the sysctl so that 131072 is in effect for all senders running on the server.</p>
    <div>
      <h2>Conclusion</h2>
      <a href="#conclusion">
        
      </a>
    </div>
    <p>The goal of this work is to open the throughput floodgates for high BDP connections while simultaneously ensuring very low HTTP request latency.</p><p>We have accomplished that goal.</p><p>...<i>We protect </i><a href="https://www.cloudflare.com/network-services/"><i>entire corporate networks</i></a><i>, help customers build </i><a href="https://workers.cloudflare.com/"><i>Internet-scale applications efficiently</i></a><i>, accelerate any </i><a href="https://www.cloudflare.com/performance/accelerate-internet-applications/"><i>website or Internet application</i></a><i>, ward off </i><a href="https://www.cloudflare.com/ddos/"><i>DDoS attacks</i></a><i>, keep </i><a href="https://www.cloudflare.com/application-security/"><i>hackers at bay</i></a><i>, and can help you on </i><a href="https://www.cloudflare.com/products/zero-trust/"><i>your journey to Zero Trust</i></a><i>.</i></p><p><i>Visit </i><a href="https://1.1.1.1/"><i>1.1.1.1</i></a><i> from any device to get started with our free app that makes your Internet faster and safer.To learn more about our mission to help build a better Internet, start </i><a href="https://www.cloudflare.com/learning/what-is-cloudflare/"><i>here</i></a><i>. If you’re looking for a new career direction, check out </i><a href="http://cloudflare.com/careers"><i>our open positions</i></a><i>.</i></p> ]]></content:encoded>
            <category><![CDATA[Deep Dive]]></category>
            <category><![CDATA[TCP]]></category>
            <category><![CDATA[Latency]]></category>
            <category><![CDATA[Optimization]]></category>
            <guid isPermaLink="false">BcOgofewzZGwenQrFsVMq</guid>
            <dc:creator>Mike Freemon</dc:creator>
        </item>
        <item>
            <title><![CDATA[A Primer on Proxies]]></title>
            <link>https://blog.cloudflare.com/a-primer-on-proxies/</link>
            <pubDate>Sat, 19 Mar 2022 17:01:15 GMT</pubDate>
            <description><![CDATA[ A technical dive into traditional TCP proxying over HTTP ]]></description>
            <content:encoded><![CDATA[ 
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/4icLjzJ8inC97t9zh3LiWw/e0e75625752de444e0fd2a32f627112e/image2-73.png" />
            
            </figure><p>Traffic proxying, the act of encapsulating one flow of data inside another, is a valuable privacy tool for establishing boundaries on the Internet. Encapsulation has an overhead, Cloudflare and our Internet peers strive to avoid turning it into a performance cost. MASQUE is the latest collaboration effort to design efficient proxy protocols based on IETF standards. We're already running these at scale in production; see our recent blog post about Cloudflare's role in <a href="/icloud-private-relay/">iCloud Private Relay</a> for an example.</p><p>In this blog post series, we’ll dive into proxy protocols.</p><p>To begin, let’s start with a simple question: what is proxying? In this case, we are focused on <b>forward</b> proxying — a client establishes an end-to-end tunnel to a target server via a proxy server. This contrasts with the Cloudflare CDN, which operates as a <a href="https://www.cloudflare.com/learning/cdn/glossary/reverse-proxy/"><b>reverse</b></a> <a href="https://www.cloudflare.com/learning/cdn/glossary/reverse-proxy/">proxy</a> that terminates client connections and then takes responsibility for actions such as caching, security including <a href="https://www.cloudflare.com/learning/ddos/glossary/web-application-firewall-waf/">WAF</a>, load balancing, etc. With forward proxying, the details about the tunnel, such as how it is established and used, whether it provides confidentiality via authenticated encryption, and so on, vary by proxy protocol. Before going into specifics, let’s start with one of the most common tunnels used on the Internet: TCP.</p>
    <div>
      <h3>Transport basics: TCP provides a reliable byte stream</h3>
      <a href="#transport-basics-tcp-provides-a-reliable-byte-stream">
        
      </a>
    </div>
    <p>The TCP transport protocol is a rich topic. For the purposes of this post, we will focus on one aspect: TCP provides a readable and writable, reliable, and ordered byte stream. Some protocols like HTTP and TLS require reliable transport underneath them and TCP's single byte stream is an ideal fit. The application layer reads or writes to this byte stream, but the details about how TCP sends this data "on the wire" are typically abstracted away.</p><p>Large application objects are written into a stream, then they are split into many small packets, and they are sent in order to the network. At the receiver, packets are read from the network and combined back into an identical stream. Networks are not perfect and packets can be lost or reordered. TCP is clever at dealing with this and not worrying the application with details. It just works. A way to visualize this is to imagine a magic paper shredder that can both shred documents and convert shredded papers back to whole documents. Then imagine you and your friend bought a pair of these and decided that it would be fun to send each other shreds.</p><p>The one problem with TCP is that when a lost packet is detected at a receiver, the sender needs to retransmit it. This takes time to happen and can mean that the byte stream reconstruction gets delayed. This is known as TCP head-of-line blocking. Applications regularly use TCP via a socket API that abstracts away protocol details; they often can't tell if there are delays because the other end is slow at sending or if the network is slowing things down via packet loss.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/xnUbBQnb4droA0xmJMJ69/c07b46a941cdc3a7cbb75dc696050f38/image1-84.png" />
            
            </figure>
    <div>
      <h3>Proxy Protocols</h3>
      <a href="#proxy-protocols">
        
      </a>
    </div>
    <p>Proxying TCP is immensely useful for many applications, including, though certainly not limited to HTTPS, <a href="https://www.cloudflare.com/learning/access-management/what-is-ssh/">SSH</a>, and RDP. In fact, <a href="/oblivious-dns/">Oblivious DoH</a>, which is a proxy protocol for DNS messages, could very well be implemented using a TCP proxy, though there are reasons <a href="https://datatracker.ietf.org/doc/html/draft-pauly-dprive-oblivious-doh-11#appendix-A">why this may not be desirable</a>. Today, there are a number of different options for proxying TCP end-to-end, including:</p><ul><li><p>SOCKS, which runs in cleartext and requires an expensive connection establishment step.</p></li><li><p>Transparent TCP proxies, commonly referred to as performance enhancing proxies (PEPs), which must be on path and offer no additional transport security, and, definitionally, are limited to TCP protocols.</p></li><li><p>Layer 4 proxies such as Cloudflare <a href="https://developers.cloudflare.com/spectrum/">Spectrum</a>, which might rely on side carriage metadata via something like the <a href="https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt">PROXY protocol</a>.</p></li><li><p>HTTP CONNECT, which transforms HTTPS connections into opaque byte streams.</p></li></ul><p>While SOCKS and PEPs are viable options for some use cases, when choosing which proxy protocol to build future systems upon, it made most sense to choose a reusable and general-purpose protocol that provides well-defined and standard abstractions. As such, the IETF chose to focus on using HTTP as a substrate via the CONNECT method.</p><p>The concept of using HTTP as a substrate for proxying is not new. Indeed, HTTP/1.1 and HTTP/2 have supported proxying TCP-based protocols for a long time. In the following sections of this post, we’ll explain in detail how CONNECT works across different versions of HTTP, including HTTP/1.1, HTTP/2, and the <a href="https://www.cloudflare.com/learning/performance/what-is-http3/">recently standardized HTTP/3</a>.</p>
    <div>
      <h3>HTTP/1.1 and CONNECT</h3>
      <a href="#http-1-1-and-connect">
        
      </a>
    </div>
    <p>In HTTP/1.1, the <a href="https://www.rfc-editor.org/rfc/rfc7231#section-4.3.6">CONNECT method</a> can be used to establish an end-to-end TCP tunnel to a target server via a proxy server. This is commonly applied to use cases where there is a benefit of protecting the traffic between the client and the proxy, or where the proxy can provide access control at network boundaries. For example, a Web browser can be configured to issue all of its HTTP requests via an HTTP proxy.</p><p>A client sends a CONNECT request to the proxy server, which requests that it opens a TCP connection to the target server and desired port. It looks something like this:</p>
            <pre><code>CONNECT target.example.com:80 HTTP/1.1
Host: target.example.com</code></pre>
            <p>If the proxy succeeds in opening a TCP connection to the target, it responds with a 2xx range status code. If there is some kind of problem, an error status in the 5xx range can be returned. Once a tunnel is established there are two independent TCP connections; one on either side of the proxy. If a flow needs to stop, you can simply terminate them.</p><p>HTTP CONNECT proxies forward data between the client and the target server. The TCP packets themselves are not tunneled, only the data on the logical byte stream. Although the proxy is supposed to forward data and not process it, if the data is plaintext there would be nothing to stop it. In practice, CONNECT is often used to create an end-to-end TLS connection where only the client and target server have access to the protected content; the proxy sees only TLS records and can't read their content because it doesn't have access to the keys.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/1ParPDOxCyJFT2m3UYsLtR/d76fbce62a99c53fa68bc86773c231bd/image8-1.png" />
            
            </figure><p>Finally, it's worth noting that after a successful CONNECT request, the HTTP connection (and the TCP connection underpinning it) has been converted into a tunnel. There is no more possibility of issuing other HTTP messages, to the proxy itself, on the connection.</p>
    <div>
      <h3>HTTP/2 and CONNECT</h3>
      <a href="#http-2-and-connect">
        
      </a>
    </div>
    <p><a href="https://www.rfc-editor.org/rfc/rfc7540.html">HTTP/2</a> adds logical streams above the TCP layer in order to support concurrent requests and responses on a single connection. Streams are also reliable and ordered byte streams, operating on top of TCP. Returning to our magic shredder analogy: imagine you wanted to send a book. Shredding each page one after another and rebuilding the book one page at a time is slow, but handling multiple pages at the same time might be faster. HTTP/2 streams allow us to do that. But, as we all know, trying to put too much into a shredder can sometimes cause it to jam.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/37F1LF53K2Plk0vYbcdmgg/d7d57e19b13ebce0aabb910568f4d9f6/image3-33.png" />
            
            </figure><p>In HTTP/2, each request and response is sent on a different stream. To support this, HTTP/2 defines frames that contain the stream identifier that they are associated with. Requests and responses are composed of HEADERS and DATA frames which contain HTTP header fields and HTTP content, respectively. Frames can be large. When they are sent on the wire they might span multiple TLS records or TCP segments. Side note: the HTTP WG has been working on a new revision of the document that defines HTTP semantics that are common to all HTTP versions. The terms message, header fields, and content all come from <a href="https://www.ietf.org/archive/id/draft-ietf-httpbis-semantics-19.html#name-message-abstraction">this description</a>.</p><p>HTTP/2 concurrency allows applications to read and write multiple objects at different rates, which can improve HTTP application performance, such as web browsing. HTTP/1.1 traditionally dealt with this concurrency by opening multiple TCP connections in parallel and striping requests across these connections. In contrast, HTTP/2 multiplexes frames belonging to different streams onto the single byte stream provided by one TCP connection. Reusing a single connection has benefits, but it still leaves HTTP/2 at risk of TCP head-of-line blocking. For more details, refer to <a href="https://calendar.perfplanet.com/2020/head-of-line-blocking-in-quic-and-http-3-the-details/">Perf Planet blog</a>.</p><p><a href="https://datatracker.ietf.org/doc/html/rfc7540#section-8.3">HTTP/2 also supports the CONNECT method</a>. In contrast to HTTP/1.1, CONNECT requests do not take over an entire HTTP/2 connection. Instead, they convert a single stream into an end-to-end tunnel. It looks something like this:</p>
            <pre><code>:method = CONNECT
:authority = target.example.com:443</code></pre>
            <p>If the proxy succeeds in opening a TCP connection, it responds with a 2xx (Successful) status code. After this, the client sends DATA frames to the proxy, and the content of these frames are put into TCP packets sent to the target. In the return direction, the proxy reads from the TCP byte stream and populates DATA frames. If a tunnel needs to stop, you can simply terminate the stream; there is no need to terminate the HTTP/2 connection.</p><p>By using HTTP/2, a client can create multiple CONNECT tunnels in a single connection. This can help reduce resource usage (saving the global count of TCP connections) and allows related tunnels to be logically grouped together, ensuring that they "share fate" when either client or proxy need to gracefully close. On the proxy-to-server side there are still multiple independent TCP connections.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/2zNstK2cYrqJKofOIiOeII/2c4bda54b181ce6c171b264589511421/image7.png" />
            
            </figure><p>One challenge of multiplexing tunnels on concurrent streams is how to effectively prioritize them. We've talked in the past about <a href="/better-http-2-prioritization-for-a-faster-web/">prioritization for web pages</a>, but the story is a bit different for CONNECT. We've been thinking about this and captured <a href="https://httpwg.org/http-extensions/draft-ietf-httpbis-priority.html#name-scheduling-and-the-connect-">considerations</a> in the new <a href="/adopting-a-new-approach-to-http-prioritization/">Extensible Priorities</a> draft.</p>
    <div>
      <h3>QUIC, HTTP/3 and CONNECT</h3>
      <a href="#quic-http-3-and-connect">
        
      </a>
    </div>
    <p>QUIC is a new secure and multiplexed transport protocol from the IETF. QUIC version 1 was published as <a href="https://www.rfc-editor.org/rfc/rfc9000.html">RFC 9000</a> in May 2021 and, <a href="/quic-version-1-is-live-on-cloudflare/">the next day</a>, we enabled it for all Cloudflare customers.</p><p>QUIC is composed of several foundational features. You can think of these like individual puzzle pieces that interlink to form a transport service. This service needs one more piece, an application mapping, to bring it all together.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/7gGK0LOkLSU64zmxkd9gVd/d02d3a3f1f7e13d7c2819342f61f6a4a/image4-15.png" />
            
            </figure><p>Similar to HTTP/2, QUIC version 1 provides reliable and ordered streams. But QUIC streams live at the transport layer and they are the only type of QUIC primitive that can carry application data. QUIC has no opinion on how streams get used. Applications that wish to use QUIC must define that themselves.</p><p>QUIC streams can be long (up to 2^62 - 1 bytes). Stream data is sent on the wire in the form of <a href="https://www.rfc-editor.org/rfc/rfc9000.html#name-stream-frames">STREAM frames</a>. All QUIC frames must fit completely inside a QUIC packet. QUIC packets must fit entirely in a UDP datagram; fragmentation is prohibited. These requirements mean that a long stream is serialized to a series of QUIC packets sized roughly to the path <a href="https://en.wikipedia.org/wiki/Maximum_transmission_unit">MTU</a> (Maximum Transmission Unit). STREAM frames provide reliability via QUIC loss detection and recovery. Frames are acknowledged by the receiver and if the sender detects a loss (via missing acknowledgments), QUIC will retransmit the lost data. In contrast, TCP retransmits packets. This difference is an important feature of QUIC, letting implementations decide how to repacketize and reschedule lost data.</p><p>When multiplexing streams, different packets can contain <a href="https://www.rfc-editor.org/rfc/rfc9000.html#name-stream-frames">STREAM frames</a> belonging to different stream identifiers. This creates independence between streams and helps avoid the head-of-line blocking caused by packet loss that we see in TCP. If a UDP packet containing data for one stream is lost, other streams can continue to make progress without being blocked by retransmission of the lost stream.</p><p>To use our magic shredder analogy one more time: we're sending a book again, but this time we parallelise our task by using independent shredders. We need to logically associate them together so that the receiver knows the pages and shreds are all for the same book, but otherwise they can progress with less chance of jamming.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/2KPS4I6E6zvfPnrY4zNlxB/912d09cf787fa46d6dc77140919ba607/image6-5.png" />
            
            </figure><p><a href="https://datatracker.ietf.org/doc/draft-ietf-quic-http/">HTTP/3</a> is an example of an application mapping that describes how streams are used to exchange: HTTP settings, <a href="https://datatracker.ietf.org/doc/html/draft-ietf-quic-qpack-21">QPACK</a> state, and request and response messages. HTTP/3 still defines its own frames like HEADERS and DATA, but it is overall simpler than HTTP/2 because QUIC deals with the hard stuff. Since HTTP/3 just sees a logical byte stream, its frames can be arbitrarily sized. The QUIC layer handles segmenting HTTP/3 frames over STREAM frames for sending in packets. HTTP/3 <a href="https://datatracker.ietf.org/doc/html/draft-ietf-quic-http-34#section-4.2">also supports the CONNECT method</a>. It functions identically to CONNECT in HTTP/2, each request stream converting to an end-to-end tunnel.</p>
    <div>
      <h3>HTTP packetization comparison</h3>
      <a href="#http-packetization-comparison">
        
      </a>
    </div>
    <p>We've talked about HTTP/1.1, HTTP/2 and HTTP/3. The diagram below is a convenient way to summarize how HTTP requests and responses get serialized for transmission over a secure transport. The main difference is that with TLS, protected records are split across several TCP segments. While with QUIC there is no record layer, each packet has its own protection.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/2Ixri8ytJ113sldUVNplLm/47ffbc388a4a45ae481a78e39b2986a0/image5-18.png" />
            
            </figure>
    <div>
      <h3>Limitations and looking ahead</h3>
      <a href="#limitations-and-looking-ahead">
        
      </a>
    </div>
    <p>HTTP CONNECT is a simple and elegant protocol that has a tremendous number of application use cases, especially for privacy-enhancing technology. In particular, applications can use it to proxy <a href="https://www.cloudflare.com/learning/dns/dns-over-tls/">DNS-over-HTTPS</a> similar to what’s been done for Oblivious DoH, or more generic HTTPS traffic (based on HTTP/1.1 or HTTP/2), and many more.</p><p>However, what about non-TCP traffic? Recall that HTTP/3 is an application mapping for QUIC, and therefore runs over UDP as well. What if we wanted to proxy QUIC? What if we wanted to proxy entire IP datagrams, similar to VPN technologies like IPsec or WireGuard? This is where <a href="/unlocking-quic-proxying-potential/">MASQUE</a> comes in. In the next post, we’ll discuss how the <a href="https://datatracker.ietf.org/wg/masque/about/">MASQUE Working Group</a> is standardizing technologies to enable proxying for datagram-based protocols like UDP and IP.</p> ]]></content:encoded>
            <category><![CDATA[Deep Dive]]></category>
            <category><![CDATA[TCP]]></category>
            <category><![CDATA[Proxying]]></category>
            <category><![CDATA[Research]]></category>
            <category><![CDATA[iCloud Private Relay]]></category>
            <guid isPermaLink="false">2YU980GMLipuAmzDuDgrTc</guid>
            <dc:creator>Lucas Pardue</dc:creator>
            <dc:creator>Christopher Wood</dc:creator>
        </item>
        <item>
            <title><![CDATA[Zero Trust client sessions]]></title>
            <link>https://blog.cloudflare.com/zero-trust-client-sessions/</link>
            <pubDate>Fri, 18 Mar 2022 13:00:48 GMT</pubDate>
            <description><![CDATA[ Starting today, you can build Zero Trust rules that require periodic authentication to control network access ]]></description>
            <content:encoded><![CDATA[ <p></p><p>Starting today, you can build <a href="https://www.cloudflare.com/learning/security/glossary/what-is-zero-trust/">Zero Trust</a> rules that require periodic authentication to control network access. We’ve made this feature available for years for web-based applications, but we’re excited to bring this level of granular enforcement to TCP connections and UDP flows.</p><p>We’re excited to announce that Zero Trust client-based sessions are now generally available. During CIO Week in 2021, we announced the beta program for this feature. We incorporated feedback from early users into the generally available version. In this post, I will revisit why Zero Trust client-based sessions are important, how the feature works and what we learned during the beta.</p>
    <div>
      <h3>Securing traffic with Sessions</h3>
      <a href="#securing-traffic-with-sessions">
        
      </a>
    </div>
    <p>We built Zero Trust client-based sessions to enhance the security of Cloudflare’s Zero Trust Network Access (ZTNA). The Zero Trust client is software that runs on a user machine and forwards all traffic from the machine to Cloudflare before it is sent over the Internet. This includes traffic bound for internal IPs and hostnames that typically house sensitive business applications. These sensitive applications were traditionally accessed using a VPN. Unlike VPNs, Cloudflare’s ZTNA allows administrators to set granular policies about who can access a specific resource. The only piece missing was that once a user enrolled their machine with the Zero Trust client, they had a forever persistent session. This makes lost/stolen laptops, shared workstations and personal devices more of a risk than they should be. We built Zero Trust client-based sessions to solve this.</p><p>Zero Trust client-based sessions require a user to reauthenticate with their identity provider before accessing specific resources. The authentication pop-up is triggered only when a user attempts to access a protected resource. This prevents unnecessary pop-ups to users where a session may never be necessary. Administrators can specify how often they would like their users to reauthenticate, depending on the resource. This is possible because the user’s last successful authentication is saved and evaluated against any ZTNA policy with a session configured.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/7wEzf3RR9myNQR8FxML1iK/1b32ade28d71a11a537bf70bd0481ff3/image1-76.png" />
            
            </figure>
    <div>
      <h3>What we learned during the beta period</h3>
      <a href="#what-we-learned-during-the-beta-period">
        
      </a>
    </div>
    <p>During the beta period of Zero Trust client-based sessions, we worked closely with our customers and Cloudflare’s own security team to identify areas for immediate improvement. We identified two major areas of improvements before releasing to General Availability: pop-ups, which can be intrusive, and browser-based authentication, which is not always possible. We identified new strategies for properly serving an authentication pop up to a user without being overly intrusive. In the future, users will have control over when they receive notifications to authenticate. The other area for improvement was that on certain machines and operating systems, browser-based authentication is not always possible. We are planning to add an option to authenticate directly from the Zero Trust client itself.</p>
    <div>
      <h3>What’s next</h3>
      <a href="#whats-next">
        
      </a>
    </div>
    <p>This is only the beginning for Zero Trust client-based authentication. In the future, we plan to add options for step-up multifactor authentication and automated enrollment options via certificates and Service Tokens. Getting started is easy! Follow <a href="https://developers.cloudflare.com/cloudflare-one/policies/filtering/enforce-sessions/">this guide</a> for setting up Zero Trust client-based sessions in your Cloudflare Zero Trust dashboard.</p> ]]></content:encoded>
            <category><![CDATA[Security Week]]></category>
            <category><![CDATA[Zero Trust]]></category>
            <category><![CDATA[Cloudflare Zero Trust]]></category>
            <category><![CDATA[TCP]]></category>
            <category><![CDATA[Security]]></category>
            <guid isPermaLink="false">42l3UWEBITlWpYkQsLLjAw</guid>
            <dc:creator>Kenny Johnson</dc:creator>
        </item>
        <item>
            <title><![CDATA[How to stop running out of ephemeral ports and start to love long-lived connections]]></title>
            <link>https://blog.cloudflare.com/how-to-stop-running-out-of-ephemeral-ports-and-start-to-love-long-lived-connections/</link>
            <pubDate>Wed, 02 Feb 2022 09:53:28 GMT</pubDate>
            <description><![CDATA[ Often programmers have assumptions that turn out, to their surprise, to be invalid. From my experience this happens a lot. Every API, technology or system can be abused beyond its limits and break in a miserable way ]]></description>
            <content:encoded><![CDATA[ <p>Often programmers have assumptions that turn out, to their surprise, to be invalid. From my experience this happens a lot. Every API, technology or system can be abused beyond its limits and break in a miserable way.</p><p>It's particularly interesting when basic things used everywhere fail. Recently we've reached such a breaking point in a ubiquitous part of Linux networking: establishing a network connection using the <code>connect()</code> system call.</p><p>Since we are not doing anything special, just establishing TCP and UDP connections, how could anything go wrong? Here's one example: we noticed alerts from a misbehaving server, logged in to check it out and saw:</p>
            <pre><code>marek@:~# ssh 127.0.0.1
ssh: connect to host 127.0.0.1 port 22: Cannot assign requested address</code></pre>
            <p>You can imagine the face of my colleague who saw that. SSH to localhost refuses to work, while she was already using SSH to connect to that server! On another occasion:</p>
            <pre><code>marek@:~# dig cloudflare.com @1.1.1.1
dig: isc_socket_bind: address in use</code></pre>
            <p>This time a basic DNS query failed with a weird networking error. Failing DNS is a bad sign!</p><p>In both cases the problem was Linux running out of ephemeral ports. When this happens it's unable to establish any outgoing connections. This is a pretty serious failure. It's usually transient and if you don't know what to look for it might be hard to debug.</p><p>The root cause lies deeper though. We can often ignore limits on the number of outgoing connections. But we encountered cases where we hit limits on the number of concurrent outgoing connections during normal operation.</p><p>In this blog post I'll explain why we had these issues, how we worked around them, and present an userspace code implementing an improved variant of <code>connect()</code> syscall.</p>
    <div>
      <h3>Outgoing connections on Linux part 1 - TCP</h3>
      <a href="#outgoing-connections-on-linux-part-1-tcp">
        
      </a>
    </div>
    <p>Let's start with a bit of historical background.</p>
    <div>
      <h3>Long-lived connections</h3>
      <a href="#long-lived-connections">
        
      </a>
    </div>
    <p>Back in 2014 Cloudflare announced support for WebSockets. We wrote two articles about it:</p><ul><li><p><a href="/cloudflare-now-supports-websockets/">Cloudflare Now Supports WebSockets</a></p></li><li><p><a href="https://idea.popcount.org/2014-04-03-bind-before-connect/">Bind before connect</a></p></li></ul><p>If you skim these blogs, you'll notice we were totally fine with the WebSocket protocol, framing and operation. What worried us was our capacity to handle large numbers of concurrent outgoing connections towards the origin servers. Since WebSockets are long-lived, allowing them through our servers might greatly increase the concurrent connection count. And this did turn out to be a problem. It was possible to hit a ceiling for a total number of outgoing connections imposed by the Linux networking stack.</p><p>In a pessimistic case, each Linux connection consumes a local port (ephemeral port), and therefore the total connection count is limited by the size of the ephemeral port range.</p>
    <div>
      <h3>Basics - how port allocation works</h3>
      <a href="#basics-how-port-allocation-works">
        
      </a>
    </div>
    <p>When establishing an outbound connection a typical user needs the destination address and port. For example, DNS might resolve <code>cloudflare.com</code> to the '104.1.1.229' IPv4 address. A simple Python program can establish a connection to it with the following code:</p>
            <pre><code>cd = socket.socket(AF_INET, SOCK_STREAM)
cd.connect(('104.1.1.229', 80))</code></pre>
            <p>The operating system’s job is to figure out how to reach that destination, selecting an appropriate source address and source port to form the full 4-tuple for the connection:</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/1zDTSzTRPl4JRrdWfjbzkP/63e0de7a453377f267b41ee0fa394a33/image4-1.png" />
            
            </figure><p>The operating system chooses the source IP based on the routing configuration. On Linux we can see which source IP will be chosen with <code>ip route get</code>:</p>
            <pre><code>$ ip route get 104.1.1.229
104.1.1.229 via 192.168.1.1 dev eth0 src 192.168.1.8 uid 1000
	cache</code></pre>
            <p>The <code>src</code> parameter in the result shows the discovered source IP address that should be used when going towards that specific target.</p><p>The source port, on the other hand, is chosen from the local port range configured for outgoing connections, also known as the ephemeral port range. On Linux this is controlled by the following sysctls:</p>
            <pre><code>$ sysctl net.ipv4.ip_local_port_range net.ipv4.ip_local_reserved_ports
net.ipv4.ip_local_port_range = 32768    60999
net.ipv4.ip_local_reserved_ports =</code></pre>
            <p>The <code>ip_local_port_range</code> sets the low and high (inclusive) port range to be used for outgoing connections. The <code>ip_local_reserved_ports</code> is used to skip specific ports if the operator needs to reserve them for services.</p>
    <div>
      <h3>Vanilla TCP is a happy case</h3>
      <a href="#vanilla-tcp-is-a-happy-case">
        
      </a>
    </div>
    <p>The default ephemeral port range contains more than 28,000 ports (60999+1-32768=28232). Does that mean we can have at most 28,000 outgoing connections? That’s the core question of this blog post!</p><p>In TCP the connection is identified by a full 4-tuple, for example:</p>
<table>
<thead>
  <tr>
    <td><span>full 4-tuple</span></td>
    <td><span>192.168.1.8</span></td>
    <td><span>32768</span></td>
    <td><span>104.1.1.229</span></td>
    <td><span>80</span></td>
  </tr>
</thead>
</table><p>In principle, it is possible to reuse the source IP and port, and share them against another destination. For example, there could be two simultaneous outgoing connections with these 4-tuples:</p>
<table>
<thead>
  <tr>
    <th><span>full 4-tuple #A</span></th>
    <th><span>192.168.1.8</span></th>
    <th><span>32768</span></th>
    <th><span>104.1.1.229</span></th>
    <th><span>80</span></th>
  </tr>
</thead>
<tbody>
  <tr>
    <td><span>full 4-tuple #B</span></td>
    <td><span>192.168.1.8</span></td>
    <td><span>32768</span></td>
    <td><span>151.101.1.57</span></td>
    <td><span>80</span></td>
  </tr>
</tbody>
</table><p>This "source two-tuple" sharing can happen in practice when establishing connections using the vanilla TCP code:</p>
            <pre><code>sd = socket.socket(SOCK_STREAM)
sd.connect( (remote_ip, remote_port) )</code></pre>
            <p>But slightly different code can prevent this sharing, as we’ll discuss.</p><p>In the rest of this blog post, we’ll summarise the behaviour of code fragments that make outgoing connections showing:</p><ul><li><p>The technique’s description</p></li><li><p>The typical `errno` value in the case of port exhaustion</p></li><li><p>And whether the kernel is able to reuse the {source IP, source port}-tuple against another destination</p></li></ul><p>The last column is the most important since it shows if there is a low limit of total concurrent connections. As we're going to see later, the limit is present more often than we'd expect.</p>
<table>
<thead>
  <tr>
    <th><span>technique description</span></th>
    <th><span>errno on port exhaustion</span></th>
    <th><span>possible src 2-tuple reuse</span></th>
  </tr>
</thead>
<tbody>
  <tr>
    <td><span>connect(dst_IP, dst_port)</span></td>
    <td><span>EADDRNOTAVAIL</span></td>
    <td><span>yes (good!)</span></td>
  </tr>
</tbody>
</table><p>In the case of generic TCP, things work as intended. Towards a single destination it's possible to have as many connections as an ephemeral range allows. When the range is exhausted (against a single destination), we'll see EADDRNOTAVAIL error. The system also is able to correctly reuse local two-tuple {source IP, source port} for ESTABLISHED sockets against other destinations. This is expected and desired.</p>
    <div>
      <h3>Manually selecting source IP address</h3>
      <a href="#manually-selecting-source-ip-address">
        
      </a>
    </div>
    <p>Let's go back to the Cloudflare server setup. Cloudflare operates many services, to name just two: CDN (caching HTTP reverse proxy) and <a href="/1111-warp-better-vpn">WARP</a>.</p><p>For Cloudflare, it’s important that we don’t mix traffic types among our outgoing IPs. Origin servers on the Internet might want to differentiate traffic based on our product. The simplest example is <a href="https://www.cloudflare.com/learning/cdn/what-is-a-cdn/">CDN</a>: it's appropriate for an origin server to firewall off non-CDN inbound connections. Allowing Cloudflare cache pulls is totally fine, but allowing WARP connections which contain untrusted user traffic might lead to problems.</p><p>To achieve such outgoing IP separation, each of our applications must be explicit about which source IPs to use. They can’t leave it up to the operating system; the automatically-chosen source could be wrong. While it's technically possible to configure routing policy rules in Linux to express such requirements, we decided not to do that and keep Linux routing configuration as simple as possible.</p><p>Instead, before calling <code>connect()</code>, our applications select the source IP with the <code>bind()</code> syscall. A trick we call "bind-before-connect":</p>
            <pre><code>sd = socket.socket(SOCK_STREAM)
sd.bind( (src_IP, 0) )
sd.connect( (dst_IP, dst_port) )</code></pre>
            
<table>
<thead>
  <tr>
    <th><span>technique description</span></th>
    <th><span>errno on port exhaustion</span></th>
    <th><span>possible src 2-tuple reuse</span></th>
  </tr>
</thead>
<tbody>
  <tr>
    <td><span>bind(src_IP, 0)</span><br /><span>connect(dst_IP, dst_port)</span></td>
    <td><span>EADDRINUSE</span></td>
    <td><span>no </span><span>(bad!)</span></td>
  </tr>
</tbody>
</table><p>This code looks rather innocent, but it hides a considerable drawback. When calling <code>bind()</code>, the kernel attempts to find an unused local two-tuple. Due to BSD API shortcomings, the operating system can't know what we plan to do with the socket. It's totally possible we want to <code>listen()</code> on it, in which case sharing the source IP/port with a connected socket will be a disaster! That's why the source two-tuple selected when calling <code>bind()</code> must be unique.</p><p>Due to this API limitation, in this technique the source two-tuple can't be reused. Each connection effectively "locks" a source port, so the number of connections is constrained by the size of the ephemeral port range. Notice: one source port is used up for each connection, no matter how many destinations we have. This is bad, and is exactly the problem we were dealing with back in 2014 in the WebSockets articles mentioned above.</p><p>Fortunately, it's fixable.</p>
    <div>
      <h3>IP_BIND_ADDRESS_NO_PORT</h3>
      <a href="#ip_bind_address_no_port">
        
      </a>
    </div>
    <p>Back in 2014 we fixed the problem by setting the SO_REUSEADDR socket option and manually retrying <code>bind()</code>+ <code>connect()</code> a couple of times on error. This worked ok, but later in 2015 <a href="https://kernelnewbies.org/Linux_4.2#Networking">Linux introduced a proper fix: the IP_BIND_ADDRESS_NO_PORT socket option</a>. This option tells the kernel to delay reserving the source port:</p>
            <pre><code>sd = socket.socket(SOCK_STREAM)
sd.setsockopt(IPPROTO_IP, IP_BIND_ADDRESS_NO_PORT, 1)
sd.bind( (src_IP, 0) )
sd.connect( (dst_IP, dst_port) )</code></pre>
            
<table>
<thead>
  <tr>
    <th><span>technique description</span></th>
    <th><span>errno on port exhaustion</span></th>
    <th><span>possible src 2-tuple reuse</span></th>
  </tr>
</thead>
<tbody>
  <tr>
    <td><span>IP_BIND_ADDRESS_NO_PORT<br />bind(src_IP, 0)</span><br /><span>connect(dst_IP, dst_port)</span></td>
    <td><span>EADDRNOTAVAIL</span></td>
    <td><span>yes (good!)</span></td>
  </tr>
</tbody>
</table><p>This gets us back to the desired behavior. On modern Linux, when doing bind-before-connect for TCP, you should set IP_BIND_ADDRESS_NO_PORT.</p>
    <div>
      <h3>Explicitly selecting a source port</h3>
      <a href="#explicitly-selecting-a-source-port">
        
      </a>
    </div>
    <p>Sometimes an application needs to select a specific source port. For example: the operator wants to control full 4-tuple in order to debug ECMP routing issues.</p><p>Recently a colleague wanted to run a cURL command for debugging, and he needed the source port to be fixed. cURL provides the <code>--local-port</code> option to do this¹ :</p>
            <pre><code>$ curl --local-port 9999 -4svo /dev/null https://cloudflare.com/cdn-cgi/trace
*   Trying 104.1.1.229:443...</code></pre>
            <p>In other situations source port numbers should be controlled, as they can be used as an input to a routing mechanism.</p><p>But setting the source port manually is not easy. We're back to square one in our hackery since IP_BIND_ADDRESS_NO_PORT is not an appropriate tool when calling <code>bind()</code> with a specific source port value. To get the scheme working again and be able to share source 2-tuple, we need to turn to SO_REUSEADDR:</p>
            <pre><code>sd = socket.socket(SOCK_STREAM)
sd.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sd.bind( (src_IP, src_port) )
sd.connect( (dst_IP, dst_port) )</code></pre>
            <p>Our summary table:</p>
<table>
<thead>
  <tr>
    <th><span>technique description</span></th>
    <th><span>errno on port exhaustion</span></th>
    <th><span>possible src 2-tuple reuse</span></th>
  </tr>
</thead>
<tbody>
  <tr>
    <td><span>SO_REUSEADDR<br />bind(src_IP, src_port)</span><br /><span>connect(dst_IP, dst_port)</span></td>
    <td><span>EADDRNOTAVAIL</span></td>
    <td><span>yes (good!)</span></td>
  </tr>
</tbody>
</table><p>Here, the user takes responsibility for handling conflicts, when an ESTABLISHED socket sharing the 4-tuple already exists. In such a case <code>connect</code> will fail with EADDRNOTAVAIL and the application should retry with another acceptable source port number.</p>
    <div>
      <h3>Userspace connectx implementation</h3>
      <a href="#userspace-connectx-implementation">
        
      </a>
    </div>
    <p>With these tricks, <a href="https://github.com/cloudflare/cloudflare-blog/blob/master/2022-02-connectx/connectx.py#L93-L110">we can implement a common function and call it <code>connectx</code></a>. It will do what <code>bind()</code>+<code>connect()</code> should, but won't have the unfortunate ephemeral port range limitation. In other words, created sockets are able to share local two-tuples as long as they are going to distinct destinations:</p>
            <pre><code>def connectx((source_IP, source_port), (destination_IP, destination_port)):</code></pre>
            <p>We have three use cases this API should support:</p>
<table>
<thead>
  <tr>
    <th><span>user specified</span></th>
    <th><span>technique</span></th>
  </tr>
</thead>
<tbody>
  <tr>
    <td><span>{_, _, dst_IP, dst_port}</span></td>
    <td><span>vanilla connect()</span></td>
  </tr>
  <tr>
    <td><span>{src_IP, _, dst_IP, dst_port}</span></td>
    <td><span>IP_BIND_ADDRESS_NO_PORT</span></td>
  </tr>
  <tr>
    <td><span>{src_IP, src_port, dst_IP, dst_port}</span></td>
    <td><span>SO_REUSEADDR</span></td>
  </tr>
</tbody>
</table><p>The name we chose isn't an accident. MacOS (specifically the underlying Darwin OS) has exactly that function implemented <a href="https://www.manpagez.com/man/2/connectx">as a <code>connectx()</code> system call</a> (<a href="https://github.com/apple/darwin-xnu/blob/a1babec6b135d1f35b2590a1990af3c5c5393479/bsd/netinet/tcp_usrreq.c#L517">implementation</a>):</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/2ixMd6STGDjs1IO4DhAaFQ/3cbca6a9ec28010fb15f4004e2450587/image2.png" />
            
            </figure><p>It's more powerful than our <code>connectx</code> code, since it supports TCP Fast Open.</p><p>Should we, Linux users, be envious? For TCP, it's possible to get the right kernel behaviour with the appropriate setsockopt/bind/connect dance, so a kernel syscall is not quite needed.</p><p>But for UDP things turn out to be much more complicated and a dedicated syscall might be a good idea.</p>
    <div>
      <h3>Outgoing connections on Linux - part 2 - UDP</h3>
      <a href="#outgoing-connections-on-linux-part-2-udp">
        
      </a>
    </div>
    <p>In the previous section we listed three use cases for outgoing connections that should be supported by the operating system:</p><ul><li><p>Vanilla egress: operating system chooses the outgoing IP and port</p></li><li><p>Source IP selection: user selects outgoing IP but the OS chooses port</p></li><li><p>Full 4-tuple: user selects full 4-tuple for the connection</p></li></ul><p>We demonstrated how to implement all three cases on Linux for TCP, without hitting connection count limits due to source port exhaustion.</p><p>It's time to extend our implementation to UDP. This is going to be harder.</p><p>For UDP, Linux maintains one hash table that is keyed on local IP and port, which can hold duplicate entries. Multiple UDP connected sockets can not only share a 2-tuple but also a 4-tuple! It's totally possible to have two distinct, connected sockets having exactly the same 4-tuple. This feature was created for multicast sockets. The implementation was then carried over to unicast connections, but it is confusing. With conflicting sockets on unicast addresses, only one of them will receive any traffic. A newer connected socket will "overshadow" the older one. It's surprisingly hard to detect such a situation. To get UDP <code>connectx()</code> right, we will need to work around this "overshadowing" problem.</p>
    <div>
      <h3>Vanilla UDP is limited</h3>
      <a href="#vanilla-udp-is-limited">
        
      </a>
    </div>
    <p>It might come as a surprise to many, but by default, the total count for outbound UDP connections is limited by the ephemeral port range size. Usually, with Linux you can't have more than ~28,000 connected UDP sockets, even if they point to multiple destinations.</p><p>Ok, let's start with the simplest and most common way of establishing outgoing UDP connections:</p>
            <pre><code>sd = socket.socket(SOCK_DGRAM)
sd.connect( (dst_IP, dst_port) )</code></pre>
            
<table>
<thead>
  <tr>
    <th><span>technique description</span></th>
    <th><span>errno on port exhaustion</span></th>
    <th><span>possible src 2-tuple reuse</span></th>
    <th><span>risk of overshadowing</span></th>
  </tr>
</thead>
<tbody>
  <tr>
    <td><span>connect(dst_IP, dst_port)</span></td>
    <td><span>EAGAIN</span></td>
    <td><span>no </span><span>(bad!)</span></td>
    <td><span>no</span></td>
  </tr>
</tbody>
</table><p>The simplest case is not a happy one. The total number of concurrent outgoing UDP connections on Linux is limited by the ephemeral port range size. On our multi-tenant servers, with potentially long-lived gaming and H3/QUIC flows containing WebSockets, this is too limiting.</p><p>On TCP we were able to slap on a <code>setsockopt</code> and move on. No such easy workaround is available for UDP.</p><p>For UDP, without REUSEADDR, Linux avoids sharing local 2-tuples among UDP sockets. During <code>connect()</code> it tries to find a 2-tuple that is not used yet. As a side note: there is no fundamental reason that it looks for a unique 2-tuple as opposed to a unique 4-tuple during 'connect()'. This suboptimal behavior might be fixable.</p>
    <div>
      <h3>SO_REUSEADDR is hard</h3>
      <a href="#so_reuseaddr-is-hard">
        
      </a>
    </div>
    <p>To allow local two-tuple reuse we need the SO_REUSEADDR socket option. Sadly, this would also allow established sockets to share a 4-tuple, with the newer socket overshadowing the older one.</p>
            <pre><code>sd = socket.socket(SOCK_DGRAM)
sd.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sd.connect( (dst_IP, dst_port) )</code></pre>
            
<table>
<thead>
  <tr>
    <th><span>technique description</span></th>
    <th><span>errno on port exhaustion</span></th>
    <th><span>possible src 2-tuple reuse</span></th>
    <th><span>risk of overshadowing</span></th>
  </tr>
</thead>
<tbody>
  <tr>
    <td><span>SO_REUSEADDR</span><br /><span>connect(dst_IP, dst_port)</span></td>
    <td><span>EAGAIN</span></td>
    <td><span>yes</span></td>
    <td><span>yes </span><span>(bad!)</span></td>
  </tr>
</tbody>
</table><p>In other words, we can't just set SO_REUSEADDR and move on, since we might hit a local 2-tuple that is already used in a connection against the same destination. We might already have an identical 4-tuple connected socket underneath. Most importantly, during such a conflict we won't be notified by any error. This is unacceptably bad.</p>
    <div>
      <h3>Detecting socket conflicts with eBPF</h3>
      <a href="#detecting-socket-conflicts-with-ebpf">
        
      </a>
    </div>
    <p>We thought a good solution might be to write an eBPF program to detect such conflicts. The idea was to put a code on the <code>connect()</code> syscall. Linux cgroups allow the BPF_CGROUP_INET4_CONNECT hook. The eBPF is called every time a process under a given cgroup runs the <code>connect()</code> syscall. This is pretty cool, and we thought it would allow us to verify if there is a 4-tuple conflict before moving the socket from UNCONNECTED to CONNECTED states.</p><p><a href="https://github.com/cloudflare/cloudflare-blog/tree/master/2022-02-connectx/ebpf_connect4">Here is how to load and attach our eBPF</a></p>
            <pre><code>bpftool prog load ebpf.o /sys/fs/bpf/prog_connect4  type cgroup/connect4
bpftool cgroup attach /sys/fs/cgroup/unified/user.slice connect4 pinned /sys/fs/bpf/prog_connect4</code></pre>
            <p>With such a code, we'll greatly reduce the probability of overshadowing:</p>
<table>
<thead>
  <tr>
    <th><span>technique description</span></th>
    <th><span>errno on port exhaustion</span></th>
    <th><span>possible src 2-tuple reuse</span></th>
    <th><span>risk of overshadowing</span></th>
  </tr>
</thead>
<tbody>
  <tr>
    <td><span>INET4_CONNECT hook</span><br /><span>SO_REUSEADDR</span><br /><span>connect(dst_IP, dst_port)</span></td>
    <td><span>manual port discovery, EPERM on conflict</span></td>
    <td><span>yes</span></td>
    <td><span>yes, but small</span></td>
  </tr>
</tbody>
</table><p>However, this solution is limited. First, it doesn't work for sockets with an automatically assigned source IP or source port, it only works when a user manually creates a 4-tuple connection from userspace. Then there is a second issue: a typical race condition. We don't grab any lock, so it's technically possible a conflicting socket will be created on another CPU in the time between our eBPF conflict check and the finish of the real <code>connect()</code> syscall machinery. In short, this lockless eBPF approach is better than nothing, but fundamentally racy.</p>
    <div>
      <h3>Socket traversal - SOCK_DIAG ss way</h3>
      <a href="#socket-traversal-sock_diag-ss-way">
        
      </a>
    </div>
    <p>There is another way to verify if a conflicting socket already exists: we can check for connected sockets in userspace. It's possible to do it without any privileges quite effectively with the SOCK_DIAG_BY_FAMILY feature of <code>netlink</code> interface. This is the same technique the <code>ss</code> tool uses to print out sockets available on the system.</p><p>The netlink code is not even all that complicated. <a href="https://github.com/cloudflare/cloudflare-blog/blob/master/2022-02-connectx/connectx.py#L23">Take a look at the code</a>. Inside the kernel, it goes <a href="https://elixir.bootlin.com/linux/latest/source/net/ipv4/udp_diag.c#L28">quickly into a fast <code>__udp_lookup()</code> routine</a>. This is great - we can avoid iterating over all sockets on the system.</p><p>With that function handy, we can draft our UDP code:</p>
            <pre><code>sd = socket.socket(SOCK_DGRAM)
sd.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
cookie = sd.getsockopt(socket.SOL_SOCKET, SO_COOKIE, 8)
sd.bind( src_addr )
c, _ = _netlink_udp_lookup(family, src_addr, dst_addr)
if c != cookie:
    raise OSError(...)
sd.connect( dst_addr )</code></pre>
            <p>This code has the same race condition issue as the connect inet eBPF hook before. But it's a good starting point. We need some locking to avoid the race condition. Perhaps it's possible to do it in the userspace.</p>
    <div>
      <h3>SO_REUSEADDR as a lock</h3>
      <a href="#so_reuseaddr-as-a-lock">
        
      </a>
    </div>
    <p>Here comes a breakthrough: we can use SO_REUSEADDR as a locking mechanism. Consider this:</p>
            <pre><code>sd = socket.socket(SOCK_DGRAM)
cookie = sd.getsockopt(socket.SOL_SOCKET, SO_COOKIE, 8)
sd.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sd.bind( src_addr )
sd.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 0)
c, _ = _netlink_udp_lookup(family, src_addr, dst_addr)
if c != cookie:
    raise OSError()
sd.connect( dst_addr )
sd.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)</code></pre>
            <p>The idea here is:</p><ul><li><p>We need REUSEADDR around bind, otherwise it wouldn't be possible to reuse a local port. It's technically possible to clear REUSEADDR after bind. Doing this technically makes the kernel socket state inconsistent, but it doesn't hurt anything in practice.</p></li><li><p>By clearing REUSEADDR, we're locking new sockets from using that source port. At this stage we can check if we have ownership of the 4-tuple we want. Even if multiple sockets enter this critical section, only one, the newest, can win this verification. This is a cooperative algorithm, so we assume all tenants try to behave.</p></li><li><p>At this point, if the verification succeeds, we can perform <code>connect()</code> and have a guarantee that the 4-tuple won't be reused by another socket at any point in the process.</p></li></ul><p>This is rather convoluted and hacky, but it satisfies our requirements:</p>
<table>
<thead>
  <tr>
    <th><span>technique description</span></th>
    <th><span>errno on port exhaustion</span></th>
    <th><span>possible src 2-tuple reuse</span></th>
    <th><span>risk of overshadowing</span></th>
  </tr>
</thead>
<tbody>
  <tr>
    <td><span>REUSEADDR as a lock</span></td>
    <td><span>EAGAIN</span></td>
    <td><span>yes</span></td>
    <td><span>no</span></td>
  </tr>
</tbody>
</table><p>Sadly, this schema only works when we know the full 4-tuple, so we can't rely on kernel automatic source IP or port assignments.</p>
    <div>
      <h3>Faking source IP and port discovery</h3>
      <a href="#faking-source-ip-and-port-discovery">
        
      </a>
    </div>
    <p>In the case when the user calls 'connect' and specifies only target 2-tuple - destination IP and port, the kernel needs to fill in the missing bits - the source IP and source port. Unfortunately the described algorithm expects the full 4-tuple to be known in advance.</p><p>One solution is to implement source IP and port discovery in userspace. This turns out to be not that hard. For example, <a href="https://github.com/cloudflare/cloudflare-blog/blob/master/2022-02-connectx/connectx.py#L204">here's a snippet of our code</a>:</p>
            <pre><code>def _get_udp_port(family, src_addr, dst_addr):
    if ephemeral_lo == None:
        _read_ephemeral()
    lo, hi = ephemeral_lo, ephemeral_hi
    start = random.randint(lo, hi)
    ...</code></pre>
            
    <div>
      <h3>Putting it all together</h3>
      <a href="#putting-it-all-together">
        
      </a>
    </div>
    <p>Combining the manual source IP, port discovery and the REUSEADDR locking dance, we get a decent userspace implementation of <code>connectx()</code> for UDP.</p><p>We have covered all three use cases this API should support:</p>
<table>
<thead>
  <tr>
    <th><span>user specified</span></th>
    <th><span>comments</span></th>
  </tr>
</thead>
<tbody>
  <tr>
    <td><span>{_, _, dst_IP, dst_port}</span></td>
    <td><span>manual source IP and source port discovery</span></td>
  </tr>
  <tr>
    <td><span>{src_IP, _, dst_IP, dst_port}</span></td>
    <td><span>manual source port discovery</span></td>
  </tr>
  <tr>
    <td><span>{src_IP, src_port, dst_IP, dst_port}</span></td>
    <td><span>just our "REUSEADDR as lock" technique</span></td>
  </tr>
</tbody>
</table><p><a href="https://github.com/cloudflare/cloudflare-blog/blob/master/2022-02-connectx/connectx.py#L116-L166">Take a look at the full code</a>.</p>
    <div>
      <h3>Summary</h3>
      <a href="#summary">
        
      </a>
    </div>
    <p>This post described a problem we hit in production: running out of ephemeral ports. This was partially caused by our servers running numerous concurrent connections, but also because we used the Linux sockets API in a way that prevented source port reuse. It meant that we were limited to ~28,000 concurrent connections per protocol, which is not enough for us.</p><p>We explained how to allow source port reuse and prevent having this ephemeral-port-range limit imposed. We showed an userspace <code>connectx()</code> function, which is a better way of creating outgoing TCP and UDP connections on Linux.</p><p>Our UDP code is more complex, based on little known low-level features, assumes cooperation between tenants and undocumented behaviour of the Linux operating system. Using REUSEADDR as a locking mechanism is rather unheard of.</p><p>The <code>connectx()</code> functionality is valuable, and should be added to Linux one way or another. It's not trivial to get all its use cases right. Hopefully, this blog post shows how to achieve this in the best way given the operating system API constraints.</p><p>___</p><p>¹ On a side note, on the second cURL run it fails due to TIME-WAIT sockets: "bind failed with errno 98: Address already in use".</p><p>One option is to wait for the TIME_WAIT socket to die, or work around this with the <a href="https://github.com/cloudflare/cloudflare-blog/blob/master/2022-02-connectx/killtw.py">time-wait sockets kill script</a>. Killing time-wait sockets is generally a bad idea, violating protocol, unneeded and sometimes doesn't work. But hey, in some extreme cases it's good to know what's possible. Just saying.</p> ]]></content:encoded>
            <category><![CDATA[Deep Dive]]></category>
            <category><![CDATA[UDP]]></category>
            <category><![CDATA[TCP]]></category>
            <category><![CDATA[Linux]]></category>
            <guid isPermaLink="false">319tj39kXPyzuiPbC755uC</guid>
            <dc:creator>Marek Majkowski</dc:creator>
        </item>
        <item>
            <title><![CDATA[Announcing Argo for Spectrum]]></title>
            <link>https://blog.cloudflare.com/argo-spectrum/</link>
            <pubDate>Tue, 23 Nov 2021 13:58:39 GMT</pubDate>
            <description><![CDATA[ Announcing general availability of Argo for Spectrum, a way to turbo-charge any TCP based application. ]]></description>
            <content:encoded><![CDATA[ <p></p><p>Today we're excited to announce the general availability of Argo for Spectrum, a way to turbo-charge any TCP based application. With Argo for Spectrum, you can reduce latency, packet loss and improve connectivity for any TCP application, including common protocols like Minecraft, Remote Desktop Protocol and SFTP.</p>
    <div>
      <h3>The Internet — more than just a browser</h3>
      <a href="#the-internet-more-than-just-a-browser">
        
      </a>
    </div>
    <p>When people think of the Internet, many of us think about using a browser to view websites. Of course, it’s so much more! We often use other ways to connect to each other and to the resources we need for work. For example, you may interact with servers for work using SSH File Transfer Protocol (SFTP), git or Remote Desktop software. At home, you might play a video game on the Internet with friends.</p><p>To help people that protect these services against DDoS attacks, Spectrum launched in 2018 and extends Cloudflare’s <a href="https://www.cloudflare.com/ddos/">DDoS protection</a> to any TCP or UDP based protocol. Customers use it for a wide variety of use cases, including to protect video streaming (RTMP), gaming and internal IT systems. Spectrum also supports common VoIP protocols such as SIP and RTP, which have recently seen an <a href="/attacks-on-voip-providers/">increase in DDoS ransomware attacks</a>. A lot of these applications are also highly sensitive to performance issues. No one likes waiting for a file to upload or dealing with a lagging video game.</p><p>Latency and throughput are the two metrics people generally discuss when talking about network performance. Latency refers to the amount of time a piece of data (a packet) takes to traverse between two systems. Throughput refers to the amount of bits you can actually send per second. This blog will discuss how these two interplay and how we improve them with Argo for Spectrum.</p>
    <div>
      <h3>Argo to the rescue</h3>
      <a href="#argo-to-the-rescue">
        
      </a>
    </div>
    <p>There are a number of factors that cause poor performance between two points on the Internet, including network congestion, the distance between the two points, and packet loss. This is a problem many of our customers have, even on web applications. To help, we launched <a href="/argo/">Argo Smart Routing</a> in 2017, a way to reduce latency (or <i>time to first byte</i>, to be precise) for any HTTP request that goes to an origin.</p><p>That’s great for folks who run websites, but what if you’re working on an application that doesn’t speak HTTP? Up until now people had limited options for improving performance for these applications. That changes today with the general availability of Argo for Spectrum. Argo for Spectrum offers the same benefits as Argo Smart Routing for any TCP-based protocol.</p><p>Argo for Spectrum takes the same smarts from our network traffic and applies it to Spectrum. At time of writing, Cloudflare sits in front of approximately 20% of the Alexa top 10 million websites. That means that we see, in near real-time, which networks are congested, which are slow and which are dropping packets. We use that data and take action by provisioning faster routes, which sends packets through the Internet faster than normal routing. Argo for Spectrum works the exact same way, using the same intelligence and routing plane but extending it to any TCP based application.</p>
    <div>
      <h3>Performance</h3>
      <a href="#performance">
        
      </a>
    </div>
    <p>But what does this mean for real application performance? To find out, we ran a set of benchmarks on Catchpoint. Catchpoint is a service that allows you to set up <a href="https://www.cloudflare.com/application-services/solutions/app-performance-monitoring/">performance monitoring</a> from all over the world. Tests are repeated at intervals and aggregate results are reported. We wanted to use a third party such as Catchpoint to get objective results (as opposed to running themselves).</p><p>For our test case, we used a file server in the Netherlands as our origin. We provisioned various tests on Catchpoint to measure file transfer performance from various places in the world: Rabat, Tokyo, Los Angeles and Lima.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/1Dmiv8f30ef7K9FQ6O1Nyi/131f81007fa1c71ecebb4237f1ad759e/image2-28.png" />
            
            </figure><p>Throughput of a 10MB file. Higher is better.</p><p>Depending on location, transfers saw increases of up to 108% (for locations such as Tokyo) and <b>85% on average</b>. Why is it <b>so</b> much faster? The answer is <a href="https://en.wikipedia.org/wiki/Bandwidth-delay_product"><i>bandwidth delay product</i></a>. In layman's terms, bandwidth delay product means that the higher the latency, the lower the throughput. This is because with transmission protocols such as TCP, we need to wait for the other party to acknowledge that they received data before we can send more.</p><p>As an analogy, let’s assume we’re operating a water cleaning facility. We send unprocessed water through a pipe to a cleaning facility, but we’re not sure how much capacity the facility has! To test, we send an amount of water through the pipe. Once the water has arrived, the facility will call us up and say, “we can easily handle this amount of water at a time, please send more.” If the pipe is short, the feedback loop is quick: the water will arrive, and we’ll immediately be able to send more without having to wait. If we have a very, very long pipe, we have to stop sending water for a while before we get confirmation that the water has arrived and there’s enough capacity.</p><p>The same happens with TCP: we send an amount of data to the wire and wait to get confirmation that it arrived. If the <i>latency</i> is high it reduces the throughput because we’re constantly waiting for confirmation. If latency is low we can throttle throughput at a high rate. With Spectrum and Argo, we help in two ways: the first is that Spectrum terminates the TCP connection close to the user, meaning that latency for that link is low. The second is that Argo reduces the latency between our edge and the origin. In concert, they create a set of low-latency connections, resulting in a low overall bandwidth delay product between users in origin. The result is a much higher throughput than you would otherwise get.</p><p>Argo for Spectrum supports any TCP based protocol. This includes commonly used protocols like SFTP, git (over SSH), RDP and SMTP, but also media streaming and gaming protocols such as RTMP and Minecraft. Setting up Argo for Spectrum is easy. When creating a Spectrum application, just hit the “Argo Smart Routing” toggle. Any traffic will automatically be smart routed.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/3m5hR3BEdy6PqTp7jo7XyT/1ce3ff692d52b0fa677e27c79311dcf1/image3-35.png" />
            
            </figure><p>Argo for Spectrum covers much more than just these applications: we support any TCP-based protocol. If you're interested, reach out to your account team today to see what we can do for you.</p> ]]></content:encoded>
            <category><![CDATA[Argo Smart Routing]]></category>
            <category><![CDATA[Spectrum]]></category>
            <category><![CDATA[TCP]]></category>
            <category><![CDATA[Performance]]></category>
            <guid isPermaLink="false">7YylXseoJGsIrnn3GLNzq</guid>
            <dc:creator>Achiel van der Mandele</dc:creator>
        </item>
        <item>
            <title><![CDATA[flowtrackd: DDoS Protection with Unidirectional TCP Flow Tracking]]></title>
            <link>https://blog.cloudflare.com/announcing-flowtrackd/</link>
            <pubDate>Tue, 14 Jul 2020 11:00:00 GMT</pubDate>
            <description><![CDATA[ flowtrackd is a software-defined DDoS protection system that significantly improves our ability to automatically detect and mitigate even the most complex TCP-based DDoS attacks. If you are a Magic Transit customer, this feature will be enabled by default at no additional cost on July 29, 2020. ]]></description>
            <content:encoded><![CDATA[ <p></p><p><a href="https://www.cloudflare.com/magic-transit/">Magic Transit</a> is Cloudflare’s L3 DDoS Scrubbing service for protecting network infrastructure. As part of our ongoing investment in Magic Transit and our <a href="https://www.cloudflare.com/ddos/">DDoS protection</a> capabilities, we’re excited to talk about a new piece of software helping to protect Magic Transit customers: <b><i>flowtrackd</i></b><i>.</i> flowtrackd is a software-defined DDoS protection system that significantly improves our ability to automatically detect and mitigate even the most complex TCP-based DDoS attacks. If you are a Magic Transit customer, this feature will be enabled by default at no additional cost on July 30, 2020.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/3cB4AX4qGrbT7hbc2AH2fP/f4927a9879bade2ea0e21824dc724d89/Social---Case-Study-Quote-template-2.png" />
            
            </figure>
    <div>
      <h3>TCP-Based DDoS Attacks</h3>
      <a href="#tcp-based-ddos-attacks">
        
      </a>
    </div>
    <p>In the first quarter of 2020, one out of every two L3/4 <a href="https://www.cloudflare.com/learning/ddos/what-is-a-ddos-attack/">DDoS attacks</a> Cloudflare mitigated was an <a href="https://www.cloudflare.com/learning/ddos/what-is-an-ack-flood/">ACK Flood</a>, and over 66% of all L3/4 attacks were <a href="https://www.cloudflare.com/learning/ddos/glossary/tcp-ip/">TCP</a> based. Most types of DDoS attacks can be mitigated by finding unique characteristics that are present in all attack packets and using that to distinguish ‘good’ packets from the ‘bad’ ones. This is called "stateless" mitigation, because any packet that has these unique characteristics can simply be dropped without remembering any information (or "state") about the other packets that came before it. However, when attack packets have no unique characteristics, then "stateful" mitigation is required, because whether a certain packet is good or bad depends on the other packets that have come before it.</p><p>The most sophisticated types of TCP flood require stateful mitigation, where every TCP connection must be tracked in order to know whether any particular TCP packet is part of an active connection. That kind of mitigation is called "flow tracking", and it is typically implemented in Linux by the <a href="https://en.wikipedia.org/wiki/Iptables">iptables</a> conntrack module. However, DDoS protection with conntrack is not as simple as flipping the iptable switch, especially at the scale and complexity that Cloudflare operates in. If you're interested to learn more, in <a href="/conntrack-tales-one-thousand-and-one-flows/">this</a> blog we talk about the technical challenges of implementing iptables conntrack.</p><p>Complex TCP DDoS attacks pose a threat as they can be harder to detect and mitigate. They therefore have the potential to cause service degradation, outages and increased false positives with inaccurate mitigation rules. So how does Cloudflare block patternless DDoS attacks without affecting legitimate traffic?</p>
    <div>
      <h3>Bidirectional TCP Flow Tracking</h3>
      <a href="#bidirectional-tcp-flow-tracking">
        
      </a>
    </div>
    <p>Using Cloudflare's traditional products, HTTP applications can be protected by the <a href="https://www.cloudflare.com/waf/">WAF</a> service, and TCP/UDP applications can be protected by <a href="https://www.cloudflare.com/products/cloudflare-spectrum/">Spectrum</a>. These services are "<a href="https://www.cloudflare.com/learning/cdn/glossary/reverse-proxy/">reverse proxies</a>", meaning that traffic passes through Cloudflare in both directions. In this bidirectional topology, we see the entire TCP flow (i.e., segments sent by both the client and the server) and can therefore track the state of the underlying TCP connection. This way, we know if a TCP packet belongs to an existing flow or if it is an “out of state” TCP packet. Out of state TCP packets look just like regular TCP packets, but they don’t belong to any real connection between a client and a server. These packets are most likely part of an attack and are therefore dropped.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/190bHsEl8drp7r686sUlCi/04efd3f411afac52d87b9072c7f43f73/image2-4.png" />
            
            </figure><p><b>Reverse Proxy: What Cloudflare Sees</b></p><p>While not trivial, <a href="/conntrack-tales-one-thousand-and-one-flows/">tracking TCP</a> flows can be done when we serve as a proxy between the client and server, allowing us to absorb and mitigate out of state TCP floods. However it becomes <i>much</i> more challenging when we only see half of the connection: the ingress flow. This visibility into ingress but not egress flows is the default deployment method for Cloudflare’s Magic Transit service, so we had our work cut out for us in identifying out of state packets.</p>
    <div>
      <h3>The Challenge With Unidirectional TCP Flows</h3>
      <a href="#the-challenge-with-unidirectional-tcp-flows">
        
      </a>
    </div>
    <p>With Magic Transit, Cloudflare receives inbound internet traffic on behalf of the customer, scrubs DDoS attacks, and routes the clean traffic to the customer’s data center over a tunnel. The data center then responds directly to the eyeball client using a technique known as Direct Server Return (DSR).</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/2sYZnV8m6FNZoMw0TQBxxZ/18566279daa78e35189982a018809526/image6.png" />
            
            </figure><p>Magic Transit: Asymmetric L3 Routing</p><p>Using DSR, when a TCP handshake is initiated by an eyeball client, it sends a SYN packet that gets routed via Cloudflare to the origin data center. The origin then responds with a SYN-ACK directly to the client, bypassing Cloudflare. Finally, the client responds with an ACK that once again routes to the origin via Cloudflare and the connection is then considered established.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/4dPx4Zrjj0SAppPOL9Nx7v/ee17d72aceb74493eafd9197c4918dd9/image3-6.png" />
            
            </figure><p><b>L3 Routing: What Cloudflare Sees</b></p><p>In a unidirectional flow we don’t see the SYN+ACK sent from the origin to the eyeball client, and therefore can't utilize our existing flow tracking capabilities to identify out of state packets.</p>
    <div>
      <h3>Unidirectional TCP Flow Tracking</h3>
      <a href="#unidirectional-tcp-flow-tracking">
        
      </a>
    </div>
    <p>To overcome the challenges of unidirectional flows, we recently completed the development and rollout of a new system, codenamed flowtrackd (“flow tracking daemon”). flowtrackd is a state machine that hooks into the network interface. Using only the ingress traffic that routes through Cloudflare, flowtrackd determines whether to forward or drop each received TCP packet based on the state of its related connection.</p><p>The state machine that determines the state of the flows was developed in-house and complements Gatebot and dosd. Together Gatebot, dosd, and flowtrackd provide a comprehensive multi layer DDoS protection.</p>
    <div>
      <h3>Releasing flowtrackd to the Wild</h3>
      <a href="#releasing-flowtrackd-to-the-wild">
        
      </a>
    </div>
    <p>And it works! Less than a day after releasing flowtrackd to an early access customer, flowtrackd automatically detected and mitigated an ACK flood that peaked at 6 million packets per second. No downtime, service disruption, or false positives were reported.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/3Ns2NsGj3FiUSTaIB55aSv/4e6ad11d086cd6d605e96897dcaecbe8/image4-2.png" />
            
            </figure><p>flowtrackd Mitigates 6M pps Flood</p>
    <div>
      <h3>Cloudflare’s DDoS Protection - Delivered From Every Data Center</h3>
      <a href="#cloudflares-ddos-protection-delivered-from-every-data-center">
        
      </a>
    </div>
    <p>As opposed to legacy scrubbing center providers with limited network infrastructures, Cloudflare provides DDoS Protection from every one of our data centers in over 200 locations around the world. We write our own software-defined DDoS protection systems. Notice I say system<b>s</b>, because as opposed to vendors that use a dedicated third party appliance, we’re able to write and spin up whatever software we need, deploy it in the optimal location in our tech stack and are therefore not dependent on other vendors or be limited to the capabilities of one appliance.</p><p>flowtrackd joins the Cloudflare DDoS protection family which includes our veteran <a href="/meet-gatebot-a-bot-that-allows-us-to-sleep/">Gatebot</a> and the younger and energetic <a href="/rolling-with-the-punches-shifting-attack-tactics-dropping-packets-faster-cheaper-at-the-edge/">dosd</a>. flowtrackd will be available from every one of our data centers, with a total mitigation capacity of over 37 Tbps, protecting our Magic Transit customers against the most complex TCP DDoS attacks.</p><p><b>New to Magic Transit?</b> Replace your legacy provider with Magic Transit and pay nothing until your current contract expires. Offer expires September 1, 2020. Click <a href="https://www.cloudflare.com/lp/better/">here</a> for details.</p> ]]></content:encoded>
            <category><![CDATA[DDoS]]></category>
            <category><![CDATA[Security]]></category>
            <category><![CDATA[TCP]]></category>
            <category><![CDATA[Magic Transit]]></category>
            <category><![CDATA[Gatebot]]></category>
            <guid isPermaLink="false">36KNxycC03DZF9kgnwpwVb</guid>
            <dc:creator>Omer Yoachimik</dc:creator>
        </item>
        <item>
            <title><![CDATA[Conntrack tales - one thousand and one flows]]></title>
            <link>https://blog.cloudflare.com/conntrack-tales-one-thousand-and-one-flows/</link>
            <pubDate>Mon, 06 Apr 2020 11:00:00 GMT</pubDate>
            <description><![CDATA[ We were wondering - can we just enable Linux "conntrack"? How does it actually work? I volunteered to help the team understand the dark corners of the Linux's "conntrack" stateful firewall subsystem. ]]></description>
            <content:encoded><![CDATA[ <p>At Cloudflare we develop new products at a great pace. Their needs often challenge the architectural assumptions we made in the past. For example, years ago we decided to avoid using Linux's "conntrack" - stateful firewall facility. This brought great benefits - it simplified our iptables firewall setup, sped up the system a bit and made the inbound packet path easier to understand.</p><p>But eventually our needs changed. One of our new products had a reasonable need for it. But we weren't confident - can we just enable conntrack and move on? How does it actually work? I volunteered to help the team understand the dark corners of the "conntrack" subsystem.</p>
    <div>
      <h2>What is conntrack?</h2>
      <a href="#what-is-conntrack">
        
      </a>
    </div>
    <p>"Conntrack" is a part of Linux network stack, specifically part of the firewall subsystem. To put that into perspective: early firewalls were entirely stateless. They could express only basic logic, like: allow SYN packets to port 80 and 443, and block everything else.</p><p>The stateless design gave some basic <a href="https://www.cloudflare.com/learning/network-layer/network-security/">network security</a>, but was quickly deemed insufficient. You see, there are certain things that can't be expressed in a stateless way. The canonical example is assessment of ACK packets - it's impossible to say if an ACK packet is legitimate or part of a port scanning attempt, without tracking the connection state.</p><p>To fill such gaps all the operating systems implemented connection tracking inside their firewalls. This tracking is usually implemented as a big table, with at least 6 columns: protocol (usually TCP or UDP), source IP, source port, destination IP, destination port and connection state. On Linux this subsystem is called "conntrack" and is often enabled by default. Here's how the table looks on my laptop inspected with "conntrack -L" command:</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/57PxFTDUBFZqODrE5OrOul/a9d34a54bf247021ba000b15119461bc/image5-2.png" />
            
            </figure><p>The obvious question is how large this state tracking table can be. This setting is under "/proc/sys/net/nf_conntrack_max":</p>
            <pre><code>$ cat /proc/sys/net/nf_conntrack_max
262144</code></pre>
            <p>This is a global setting, but the limit is per container. On my system each container, or "network namespace", can have up to 256K conntrack entries.</p><p>What exactly happens when the number of concurrent connections exceeds the conntrack limit?</p>
    <div>
      <h2>Testing conntrack is hard</h2>
      <a href="#testing-conntrack-is-hard">
        
      </a>
    </div>
    <p>In past testing conntrack was hard - it required complex hardware or vm setup. Fortunately, these days we can use modern "user namespace" facilities which do permission magic, allowing an unprivileged user to feel like root. Using the tool "unshare" it's possible to create an isolated environment where we can precisely control the packets going through and experiment with iptables and conntrack without threatening the health of our host system. With appropriate parameters it's possible to create and manage a networking namespace, including access to namespaced iptables and conntrack, from an unprivileged user.</p><p>This script is the heart of our test:</p>
            <pre><code># Enable tun interface
ip tuntap add name tun0 mode tun
ip link set tun0 up
ip addr add 192.0.2.1 peer 192.0.2.2 dev tun0
ip route add 0.0.0.0/0 via 192.0.2.2 dev tun0

# Refer to conntrack at least once to ensure it's enabled
iptables -t raw -A PREROUTING -j CT
# Create a counter in mangle table
iptables -t mangle -A PREROUTING
# Make sure reverse traffic doesn't affect conntrack state
iptables -t raw -A OUTPUT -p tcp --sport 80 -j DROP

tcpdump -ni any -B 16384 -ttt &amp;
...
./venv/bin/python3 send_syn.py

conntrack -L
# Show iptables counters
iptables -nvx -t raw -L PREROUTING
iptables -nvx -t mangle -L PREROUTING</code></pre>
            <p>This bash script is shortened for readability. See the <a href="https://github.com/cloudflare/cloudflare-blog/blob/master/2020-04-conntrack-syn/test-1.bash">full version here</a>. The accompanying "send_syn.py" is just sending 10 SYN packets over "tun0" interface. <a href="https://github.com/cloudflare/cloudflare-blog/blob/master/2020-04-conntrack-syn/send_syn.py">Here is the source</a> but allow me to paste it here - showing off "scapy" is always fun:</p>
            <pre><code>tun = TunTapInterface("tun0", mode_tun=True)
tun.open()

for i in range(10000,10000+10):
    ip=IP(src="198.18.0.2", dst="192.0.2.1")
    tcp=TCP(sport=i, dport=80, flags="S")
    send(ip/tcp, verbose=False, inter=0.01, socket=tun)</code></pre>
            <p>The bash script above contains a couple of gems. Let's walk through them.</p><p>First, please note that we can't just inject packets into the loopback interface using <a href="http://man7.org/linux/man-pages/man7/raw.7.html">SOCK_RAW sockets</a>. The Linux networking stack is a complex beast. The semantics of sending packets over a SOCK_RAW are different then delivering a packet over a real interface. We'll discuss this later, but for now, to avoid triggering unexpected behaviour, we will deliver packets over a tun/tap device which better emulates a real interface.</p><p>Then we need to make sure the conntrack is active in the network namespace we wish to use for testing. Traditionally, just loading the kernel module would have done that, but in the brave new world of containers and network namespaces, a method had to be found to allow conntrack to be active in some and inactive in other containers. Hence this is tied to usage - rules referencing conntrack must exist in the namespace's iptables for conntrack to be active inside the container.</p><p>As a side note, <a href="https://lwn.net/Articles/740455/">containers triggering host to load kernel modules</a> is an <a href="https://github.com/weaveworks/go-odp/blob/6b0aa22550d9325eb8f43418185859e13dc0de1d/odp/dpif.go#L67-L90">interesting subject</a>.</p><p>After the "-t raw -A PREROUTING" rule, which we added "-t mangle -A PREROUTING" rule, but notice - it doesn't have any action! This syntax is allowed by iptables and it is pretty useful to get iptables to report rule counters. We'll need these counters soon. A careful reader might suggest looking at "policy" counters in iptables to achieve our goal. Sadly, "policy" counters (increased for each packet entering a chain), work only if there is at least one rule inside it.</p><p>The rest of the steps are self-explanatory. We set up "tcpdump" in the background, send 10 SYN packets to 127.0.0.1:80 using the "scapy" Python library. Then we print the conntrack table and iptables counters.</p><p>Let's run this script in action. Remember to run it under networking namespace as fake root with "unshare -Ur -n":</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/4xUiZXoGQrhYU3Lugqbx97/9db644c8c56f944445fc7ebb559d6d95/image6.png" />
            
            </figure><p>This is all nice. First we see a "tcpdump" listing showing 10 SYN packets. Then we see the conntrack table state, showing 10 created flows. Finally, we see iptables counters in two rules we created, each showing 10 packets processed.</p>
    <div>
      <h2>Can conntrack table fill up?</h2>
      <a href="#can-conntrack-table-fill-up">
        
      </a>
    </div>
    <p>Given that the conntrack table is size constrained, what exactly happens when it fills up? Let's check it out. First, we need to drop the conntrack size. As mentioned it's controlled by a global toggle - it's necessary to tune it on the host side. Let's reduce the table size to 7 entries, and repeat our test:</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/2iH78254UDI6cAFqqiAWcy/92aafe709b997fcc234279d2966077c1/image4-3.png" />
            
            </figure><p>This is getting interesting. We still see the 10 inbound SYN packets. We still see that the "-t raw PREROUTING" table received 10 packets, but this is where similarities end. The "-t mangle PREROUTING" table saw only 7 packets. Where did the three missing SYN packets go?</p><p>It turns out they went where all the dead packets go. They were hard dropped. Conntrack on overfill does exactly that. It even complains in the "dmesg":</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/3Gtrs2Na2a4NKQbNllKw8w/ddf3755fff4e9b89dc455260b8a14107/image1-1.png" />
            
            </figure><p>This is confirmed by our iptables counters. Let's review the <a href="https://upload.wikimedia.org/wikipedia/commons/3/37/Netfilter-packet-flow.svg">famous iptables</a> diagram:</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/6wBlH7JteM8K0KVBp6P6CG/c1d17141b74544ca9809714354bf05ca/image7.png" />
            
            </figure><p><a href="https://commons.wikimedia.org/wiki/File:Netfilter-packet-flow.svg">image</a> by <a href="https://commons.wikimedia.org/wiki/User_talk:Jengelh">Jan Engelhardt</a> CC BY-SA 3.0</p><p>As we can see, the "-t raw PREROUTING" happens before conntrack, while "-t mangle PREROUTING" is just after it. This is why we see 10 and 7 packets reported by our iptables counters.</p><p>Let me emphasize the gravity of our discovery. We showed three completely valid SYN packets being implicitly dropped by "conntrack". There is no explicit "-j DROP" iptables rule. There is no configuration to be toggled. Just the fact of using "conntrack" means that, when it's full, packets creating new flows will be dropped. No questions asked.</p><p>This is the dark side of using conntrack. If you use it, you absolutely must make sure it doesn't get filled.</p><p>We could end our investigation here, but there are a couple of interesting caveats.</p>
    <div>
      <h2>Strict vs loose</h2>
      <a href="#strict-vs-loose">
        
      </a>
    </div>
    <p>Conntrack supports a "strict" and "loose" mode, as configured by "nf_conntrack_tcp_loose" toggle.</p>
            <pre><code>$ cat /proc/sys/net/netfilter/nf_conntrack_tcp_loose
1</code></pre>
            <p>By default, it's set to "loose" which means that stray ACK packets for unseen TCP flows will create new flow entries in the table. We can generalize: "conntrack" will implicitly drop all the packets that create new flow, whether that's SYN or just stray ACK.</p><p>What happens when we clear the "nf_conntrack_tcp_loose=0" setting? This is a subject for another blog post, but suffice to say - it's a mess. First, this setting is not settable in the network namespace scope - although it should be. To test it you need to be in the root network namespace. Then, due to twisted logic the ACK will be dropped on a full conntrack table, even though in this case it doesn't create a flow. If the table is not full, the ACK packet will pass through it, having "-ctstate INVALID" from "mangle" table forward.</p>
    <div>
      <h2>When doesn't a conntrack entry get created?</h2>
      <a href="#when-doesnt-a-conntrack-entry-get-created">
        
      </a>
    </div>
    <p>There are important situations when conntrack entry is not created. For example, we could replace these line in our script:</p>
            <pre><code># Make sure reverse traffic doesn't affect conntrack state
iptables -t raw -A OUTPUT -p tcp --sport 80 -j DROP</code></pre>
            <p>With those:</p>
            <pre><code># Make sure inbound SYN packets don't go to networking stack
iptables -A INPUT -j DROP</code></pre>
            <p>Naively we could think dropping SYN packets past the conntrack layer would not interfere with the created flows. This is not correct. In spite of these SYN packets having been seen by conntrack, no flow state is created for them. Packets hitting "-j DROP" will not create new conntrack flows. Pretty magical, isn't it?</p>
    <div>
      <h2>Full Conntrack causes with EPERM</h2>
      <a href="#full-conntrack-causes-with-eperm">
        
      </a>
    </div>
    <p>Recently we hit a case when a "sendto()" syscall on UDP socket from one of our applications was erroring with EPERM. This is pretty weird, and not documented in the man page. My colleague had no doubts:</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/4qoszkkHAOaErPNjv6cwPZ/c22d83083c706014dff1c47dd3beb517/image9-1.png" />
            
            </figure><p>I'll save you the gruesome details, but indeed, the full conntrack table will do that to your new UDP flows - you will get EPERM. Beware. Funnily enough, it's possible to get EPERM if an outbound packet is dropped on OUTPUT firewall in other ways. For example:</p>
            <pre><code>marek:~$ sudo iptables -I OUTPUT -p udp --dport 53 --dst 192.0.2.8 -j DROP
marek:~$ strace -e trace=write nc -vu 192.0.2.8 53
write(3, "X", 1)                        = -1 EPERM (Operation not permitted)
+++ exited with 1 +++</code></pre>
            <p>If you ever receive EPERM from "sendto()", you might want to treat it as a transient error, if you suspect a filled conntrack problem, or permanent error if you blame iptables configuration.</p><p>This is also why we can't send our SYN packets directly using SOCK_RAW sockets in our test. Let's see what happens on conntrack overfill with standard "hping3" tool:</p>
            <pre><code>$ hping3 -S -i u10000 -c 10 --spoof 192.18.0.2 192.0.2.1 -p 80 -I lo
HPING 192.0.2.1 (lo 192.0.2.1): S set, 40 headers + 0 data bytes
[send_ip] sendto: Operation not permitted</code></pre>
            <p>"send()" even on a SOCK_RAW socket fails with EPERM when conntrack table is full.</p>
    <div>
      <h2>Full conntrack can happen on a SYN flood</h2>
      <a href="#full-conntrack-can-happen-on-a-syn-flood">
        
      </a>
    </div>
    <p>There is one more caveat. During a SYN flood, the conntrack entries will totally be created for the spoofed flows. Take a look at second test case we prepared, this time correctly listening on port 80, and sending SYN+ACK:</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/2vNW5JQuRFZyumzKOrIQVW/ea983dcbf2e8a876a8f3264b3104d4d4/image8.png" />
            
            </figure><p>We can see 7 SYN+ACK's flying out of the port 80 listening socket. The final three SYN's go nowhere as they are dropped by conntrack.</p><p>This has important implications. If you use conntrack on publicly accessible ports, during SYN flood <a href="/syn-packet-handling-in-the-wild/">mitigation technologies like SYN Cookies</a> won't help. You are still at risk of running out of conntrack space and therefore affecting legitimate connections.</p><p>For this reason, as a general rule consider avoiding conntrack on inbound connections (-j NOTRACK). Alternatively having some reasonable rate limits on iptables layer, doing "-j DROP". This will work well and won't create new flows, as we discussed above. The best method though, would be to trigger SYN Cookies from a layer before conntrack, like XDP. But this is a subject for another time.</p>
    <div>
      <h2>Summary</h2>
      <a href="#summary">
        
      </a>
    </div>
    <p>Over the years Linux conntrack has gone through many changes and has improved a lot. While performance used to be a major concern, these days it's considered to be very fast. Dark corners remain. Correctly applying conntrack is tricky.</p><p>In this blog post we showed how it's possible to test parts of conntrack with "unshare" and a series of scripts. We showed the behaviour when the conntrack table gets filled - packets might implicitly be dropped. Finally, we mentioned the curious case of SYN floods where incorrectly applied conntrack may cause harm.</p><p>Stay tuned for more horror stories as we dig deeper and deeper into the Linux networking stack guts.</p> ]]></content:encoded>
            <category><![CDATA[Linux]]></category>
            <category><![CDATA[Network]]></category>
            <category><![CDATA[TCP]]></category>
            <guid isPermaLink="false">13hrLdB4ySqi2j6KpGDkBy</guid>
            <dc:creator>Marek Majkowski</dc:creator>
        </item>
        <item>
            <title><![CDATA[A cost-effective and extensible testbed for transport protocol development]]></title>
            <link>https://blog.cloudflare.com/a-cost-effective-and-extensible-testbed-for-transport-protocol-development/</link>
            <pubDate>Tue, 14 Jan 2020 16:07:15 GMT</pubDate>
            <description><![CDATA[ At Cloudflare, we develop protocols at multiple layers of the network stack. In the past, we focused on HTTP/1.1, HTTP/2, and TLS 1.3. Now, we are working on QUIC and HTTP/3, which are still in IETF draft, but gaining a lot of interest. ]]></description>
            <content:encoded><![CDATA[ <p><i>This was originally published on </i><a href="https://calendar.perfplanet.com/2019/how-to-develop-a-practical-transport-protocol/"><i>Perf Planet's 2019 Web Performance Calendar</i></a><i>.</i></p><p>At Cloudflare, we develop protocols at multiple layers of the network stack. In the past, we focused on HTTP/1.1, HTTP/2, and TLS 1.3. Now, we are working on <a href="/http3-the-past-present-and-future/">QUIC and HTTP/3</a>, which are still in IETF draft, but gaining a lot of interest.</p><p>QUIC is a secure and multiplexed transport protocol that aims to perform better than TCP under some network conditions. It is specified in a family of documents: a transport layer which specifies packet format and basic state machine, recovery and congestion control, security based on TLS 1.3, and an HTTP application layer mapping, which is now called <a href="https://www.cloudflare.com/learning/performance/what-is-http3/">HTTP/3</a>.</p><p>Let’s focus on the transport and recovery layer first. This layer provides a basis for what is sent on the wire (the packet binary format) and how we send it reliably. It includes how to open the connection, how to handshake a new secure session with the help of TLS, how to send data reliably and how to react when there is packet loss or reordering of packets. Also it includes flow control and congestion control to interact well with other transport protocols in the same network. With confidence in the basic transport and recovery layer,  we can take a look at higher application layers such as HTTP/3.</p><p>To develop such a transport protocol, we need multiple stages of the development environment. Since this is a network protocol, it’s best to test in an actual physical network to see how works on the wire. We may start the development using localhost, but after some time we may want to send and receive packets with other hosts. We can build a lab with a couple of virtual machines, using Virtualbox, VMWare or even with Docker. We also have a local testing environment with a Linux VM. But sometimes these have a limited network (localhost only) or are noisy due to other processes in the same host or virtual machines.</p><p>Next step is to have a test lab, typically an isolated network focused on protocol analysis only consisting of dedicated x86 hosts. Lab configuration is particularly important for testing various cases - there is no one-size-fits-all scenario for protocol testing. For example, EDGE is still running in production mobile networks but LTE is dominant and 5G deployment is in early stages. WiFi is very common these days. We want to test our protocol in all those environments. Of course, we can't buy every type of machine or have a very expensive network simulator for every type of environment, so using cheap hardware and an open source OS where we can configure similar environments is ideal.</p>
    <div>
      <h2>The QUIC Protocol Testing lab</h2>
      <a href="#the-quic-protocol-testing-lab">
        
      </a>
    </div>
    <p>The goal of the QUIC testing lab is to aid transport layer protocol development. To develop a transport protocol we need to have a way to control our network environment and a way to get as many different types of debugging data as possible. Also we need to get metrics for comparison with other protocols in production.</p><p>The QUIC Testing Lab has the following goals:</p><ul><li><p><b><i>Help with multiple transport protocol development</i></b>: Developing a new transport layer requires many iterations, from building and validating packets as per protocol spec, to making sure everything works fine under moderate load, to very harsh conditions such as low bandwidth and high packet loss. We need a way to run tests with various network conditions reproducibly in order to catch unexpected issues.</p></li><li><p><b><i>Debugging multiple transport protocol development</i></b>: Recording as much debugging info as we can is important for fixing bugs. Looking into packet captures definitely helps but we also need a detailed debugging log of the server and client to understand the what and why for each packet. For example, when a packet is sent, we want to know why. Is this because there is an application which wants to send some data? Or is this a retransmit of data previously known as lost? Or is this a loss probe which is not an actual packet loss but sent to see if the network is lossy?</p></li><li><p><b><i>Performance comparison between each protocol</i></b>: We want to understand the performance of a new protocol by comparison with existing protocols such as TCP, or with a previous version of the protocol under development. Also we want to test with varying parameters such as changing the congestion control mechanism, changing various timeouts, or changing the buffer sizes at various levels of the stack.</p></li><li><p><b><i>Finding a bottleneck or errors easily</i></b>: Running tests we may see an unexpected error - a transfer that timed out, or ended with an error, or a transfer was corrupted at the client side - each test needs to make sure every test is run correctly, by using a checksum of the original file to compare with what is actually downloaded, or by checking various error codes at the protocol of API level.</p></li></ul><p>When we have a test lab with separate hardware, we have benefits, as follows:</p><ul><li><p>Can configure the testing lab without public Internet access - safe and quiet.</p></li><li><p>Handy access to hardware and its console for maintenance purpose, or for adding or updating hardware.</p></li><li><p>Try other CPU architectures. For clients we use the Raspberry Pi for regular testing because this is ARM architecture (32bit or 64bit), similar to modern smartphones. So testing with ARM architecture helps for compatibility testing before going into a smartphone OS.</p></li><li><p>We can add a real smartphone for testing, such as Android or iPhone. We can test with WiFi but these devices also support Ethernet, so we can test them with a wired network for better consistency.</p></li></ul>
    <div>
      <h2>Lab Configuration</h2>
      <a href="#lab-configuration">
        
      </a>
    </div>
    <p>Here is a diagram of our QUIC Protocol Testing Lab:</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/1O1CdD682XoQE9Q68bPVkh/a63bd6b8a1bafa516719cfcf0a82c033/Screenshot-2019-07-01-00.35.06.png" />
            
            </figure><p>This is a conceptual diagram and we need to configure a switch for connecting each machine. Currently, we have Raspberry Pis (2 and 3) as an Origin and a Client. And small Intel x86 boxes for the Traffic Shaper and Edge server plus Ethernet switches for interconnectivity.</p><ul><li><p>Origin is simply serving HTTP and HTTPS test objects using a web server. Client may download a file from Origin directly to simulate a download direct from a customer's origin server.</p></li><li><p>Client will download a test object from Origin or Edge, using a different protocol. In typical a configuration Client connects to Edge instead of Origin, so this is to simulate an edge server in the real world. For TCP/HTTP we are using the curl command line client and for QUIC, <a href="https://github.com/cloudflare/quiche">quiche’s</a> http3_client with some modification.</p></li><li><p>Edge is running Cloudflare's web server to serve HTTP/HTTPS via TCP and also the QUIC protocol using quiche. Edge server is installed with the same Linux kernel used on Cloudflare's production machines in order to have the same low level network stack.</p></li><li><p>Traffic Shaper is sitting between Client and Edge (and Origin), controlling network conditions. Currently we are using FreeBSD and ipfw + dummynet. Traffic shaping can also be done using Linux' netem which provides additional network simulation features.</p></li></ul><p>The goal is to run tests with various network conditions, such as bandwidth, latency and packet loss upstream and downstream. The lab is able to run a plaintext HTTP test but currently our focus of testing is HTTPS over TCP and HTTP/3 over QUIC. Since QUIC is running over UDP, both TCP and UDP traffic need to be controlled.</p>
    <div>
      <h2>Test Automation and Visualization</h2>
      <a href="#test-automation-and-visualization">
        
      </a>
    </div>
    <p>In the lab, we have a script installed in Client, which can run a batch of testing with various configuration parameters - for each test combination, we can define a test configuration, including:</p><ul><li><p>Network Condition - Bandwidth, Latency, Packet Loss (upstream and downstream)</p></li></ul><p>For example using netem traffic shaper we can simulate LTE network as below,(<a href="https://www.cloudflare.com/learning/cdn/glossary/round-trip-time-rtt/">RTT</a>=50ms, BW=22Mbps upstream and downstream, with BDP queue size)</p>
            <pre><code>$ tc qdisc add dev eth0 root handle 1:0 netem delay 25ms
$ tc qdisc add dev eth0 parent 1:1 handle 10: tbf rate 22mbit buffer 68750 limit 70000</code></pre>
            <ul><li><p>Test Object sizes - 1KB, 8KB, … 32MB</p></li><li><p>Test Protocols: HTTPS (TCP) and QUIC (UDP)</p></li><li><p>Number of runs and number of requests in a single connection</p></li></ul><p>The test script outputs a CSV file of results for importing into other tools for data processing and visualization - such as Google Sheets, Excel or even a jupyter notebook. Also it’s able to post the result to a database (Clickhouse in our case), so we can query and visualize the results.</p><p>Sometimes a whole test combination takes a long time - the current standard test set with simulated 2G, 3G, LTE, WiFi and various object sizes repeated 10 times for each request may take several hours to run. Large object testing on a slow network takes most of the time, so sometimes we also need to run a limited test (e.g. testing LTE-like conditions only for a smoke test) for quick debugging.</p>
    <div>
      <h3>Chart using Google Sheets:</h3>
      <a href="#chart-using-google-sheets">
        
      </a>
    </div>
    <p>The following comparison chart shows the total transfer time in msec for TCP vs QUIC for different network conditions. The QUIC protocol used here is a development version one.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/2umJkG3YBHNnxyD2tJRHQA/c96ef4601d8d20c9760b45ad321e6135/Screen-Shot-2020-01-13-at-3.09.41-PM.png" />
            
            </figure>
    <div>
      <h2>Debugging and performance analysis using of a smartphone</h2>
      <a href="#debugging-and-performance-analysis-using-of-a-smartphone">
        
      </a>
    </div>
    <p>Mobile devices have become a crucial part of our day to day life, so testing the new transport protocol on mobile devices is critically important for mobile app performance. To facilitate that, we need to have a mobile test app which will proxy data over the new transport protocol under development. With this we have the ability to analyze protocol functionality and performance in mobile devices with different network conditions.</p><p>Adding a smartphone to the testbed mentioned above gives an advantage in terms of understanding real performance issues. The major smartphone operating systems, iOS and Android, have quite different networking stack. Adding a smartphone to testbed gives the ability to understand these operating system network stacks in depth which aides new protocol designs.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/RLA6wO7vjRol9o6nlzj34/7c1a7904a379b1e8079853c35597173c/Screen-Shot-2020-01-13-at-3.52.03-PM.png" />
            
            </figure><p>The above figure shows the network block diagram of another similar lab testbed used for protocol testing where a smartphone is connected both wired and wirelessly. A Linux netem based traffic shaper sits in-between the client and server shaping the traffic. Various networking profiles are fed to the traffic shaper to mimic real world scenarios. The client can be either an Android or iOS based smartphone, the server is a vanilla web server serving static files. Client, server and traffic shaper are all connected to the Internet along with the private lab network for management purposes.</p><p>The above lab has mobile devices for both Android or iOS  installed with a test app built with a proprietary client proxy software for proxying data over the new transport protocol under development. The test app also has the ability to make HTTP requests over TCP for comparison purposes.</p><p>The Android or iOS test app can be used to issue multiple HTTPS requests of different object sizes sequentially and concurrently using TCP and QUIC as underlying transport protocol. Later, TTOTAL (total transfer time) of each HTTPS request is used to compare TCP and QUIC performance over different network conditions. One such comparison is shown below,</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/1Eh2Kl4C9RI40oKf8Z3CJY/a318afd23e177895137ff481bba2dfe1/Screen-Shot-2020-01-13-at-4.08.23-PM.png" />
            
            </figure><p>The table above shows the total transfer time taken for TCP and QUIC requests over an LTE network profile fetching different objects with different concurrency levels using the test app. Here TCP goes over native OS network stack and QUIC goes over Cloudflare QUIC stack.</p><p>Debugging network performance issues is hard when it comes to mobile devices. By adding an actual smartphone into the testbed itself we have the ability to take packet captures at different layers. These are very critical in analyzing and understanding protocol performance.</p><p>It's easy and straightforward to capture packets and analyze them using the tcpdump tool on x86 boxes, but it's a challenge to capture packets on iOS and Android devices. On iOS device ‘rvictl’ lets us capture packets on an external interface. But ‘rvictl’ has some drawbacks such as timestamps being inaccurate. Since we are dealing with millisecond level events, timestamps need to be accurate to analyze the root cause of a problem.</p><p>We can capture packets on internal loopback interfaces on jailbroken iPhones and rooted Android devices. Jailbreaking a recent iOS device is nontrivial. We also need to make sure that autoupdate of any sort is disabled on such a phone otherwise it would disable the jailbreak and you have to start the whole process again. With a jailbroken phone we have root access to the device which lets us take packet captures as needed using tcpdump.</p><p>Packet captures taken using jailbroken iOS devices or rooted Android devices connected to the lab testbed help us analyze  performance bottlenecks and improve protocol performance.</p><p>iOS and Android devices different network stacks in their core operating systems. These packet captures also help us understand the network stack of these mobile devices, for example in iOS devices packets punted through loopback interface had a mysterious delay of 5 to 7ms.</p>
    <div>
      <h2>Conclusion</h2>
      <a href="#conclusion">
        
      </a>
    </div>
    <p>Cloudflare is actively involved in helping to drive forward the QUIC and HTTP/3 standards by testing and optimizing these new protocols in simulated real world environments. By simulating a wide variety of networks we are working on our mission of Helping Build a Better Internet. For everyone, everywhere.</p><p><i>Would like to thank SangJo Lee, Hiren Panchasara, Lucas Pardue and Sreeni Tellakula for their contributions.</i></p> ]]></content:encoded>
            <category><![CDATA[HTTP3]]></category>
            <category><![CDATA[QUIC]]></category>
            <category><![CDATA[TLS 1.3]]></category>
            <category><![CDATA[TCP]]></category>
            <guid isPermaLink="false">58abpUfUPAE7n3X9TDOpyt</guid>
            <dc:creator>Lohith Bellad</dc:creator>
            <dc:creator>Junho Choi</dc:creator>
        </item>
        <item>
            <title><![CDATA[Accelerating UDP packet transmission for QUIC]]></title>
            <link>https://blog.cloudflare.com/accelerating-udp-packet-transmission-for-quic/</link>
            <pubDate>Wed, 08 Jan 2020 17:08:00 GMT</pubDate>
            <description><![CDATA[ Significant work has gone into optimizing TCP, UDP hasn't received as much attention, putting QUIC at a disadvantage. Let's explore a few tricks that help mitigate this. ]]></description>
            <content:encoded><![CDATA[ <p><i>This was originally published on </i><a href="https://calendar.perfplanet.com/2019/accelerating-udp-packet-transmission-for-quic/"><i>Perf Planet's 2019 Web Performance Calendar</i></a><i>.</i></p><p><a href="/the-road-to-quic/">QUIC</a>, the new Internet transport protocol designed to accelerate HTTP traffic, is delivered on top of <a href="https://www.cloudflare.com/learning/ddos/glossary/user-datagram-protocol-udp/">UDP datagrams</a>, to ease deployment and avoid interference from network appliances that drop packets from unknown protocols. This also allows QUIC implementations to live in user-space, so that, for example, browsers will be able to implement new protocol features and ship them to their users without having to wait for operating systems updates.</p><p>But while a lot of work has gone into optimizing TCP implementations as much as possible over the years, including building offloading capabilities in both software (like in operating systems) and hardware (like in network interfaces), UDP hasn't received quite as much attention as TCP, which puts QUIC at a disadvantage. In this post we'll look at a few tricks that help mitigate this disadvantage for UDP, and by association QUIC.</p><p>For the purpose of this blog post we will only be concentrating on measuring throughput of QUIC connections, which, while necessary, is not enough to paint an accurate overall picture of the performance of the QUIC protocol (or its implementations) as a whole.</p>
    <div>
      <h3>Test Environment</h3>
      <a href="#test-environment">
        
      </a>
    </div>
    <p>The client used in the measurements is h2load, <a href="https://github.com/nghttp2/nghttp2/tree/quic">built with QUIC and HTTP/3 support</a>, while the server is NGINX, built with <a href="/experiment-with-http-3-using-nginx-and-quiche/">the open-source QUIC and HTTP/3 module provided by Cloudflare</a> which is based on quiche (<a href="https://github.com/cloudflare/quiche">github.com/cloudflare/quiche</a>), Cloudflare's own <a href="/enjoy-a-slice-of-quic-and-rust/">open-source implementation of QUIC and HTTP/3</a>.</p><p>The client and server are run on the same host (my laptop) running Linux 5.3, so the numbers don’t necessarily reflect what one would see in a production environment over a real network, but it should still be interesting to see how much of an impact each of the techniques have.</p>
    <div>
      <h3>Baseline</h3>
      <a href="#baseline">
        
      </a>
    </div>
    <p>Currently the code that implements QUIC in NGINX uses the <code>sendmsg()</code> system call to send a single UDP packet at a time.</p>
            <pre><code>ssize_t sendmsg(int sockfd, const struct msghdr *msg,
    int flags);</code></pre>
            <p>The <code>struct msghdr</code> carries a <code>struct iovec</code> which can in turn carry multiple buffers. However, all of the buffers within a single iovec will be merged together into a single UDP datagram during transmission. The kernel will then take care of encapsulating the buffer in a UDP packet and sending it over the wire.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/774r0FpU47qMQ5bbxIOPL5/3560c09b55949e3c406ad958498e7fd4/sendmsg.png" />
            
            </figure><p>The throughput of this particular implementation tops out at around 80-90 MB/s, as measured by h2load when performing 10 sequential requests for a 100 MB resource.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/4V8JtNxw1ogFryQ7xBVcgk/74bf3551df4837143a4cb2dbc53e3f84/sendmsg-chart.png" />
            
            </figure>
    <div>
      <h3>sendmmsg()</h3>
      <a href="#sendmmsg">
        
      </a>
    </div>
    <p>Due to the fact that <code>sendmsg()</code> only sends a single UDP packet at a time, it needs to be invoked quite a lot in order to transmit all of the QUIC packets required to deliver the requested resources, as illustrated by the following bpftrace command:</p>
            <pre><code>% sudo bpftrace -p $(pgrep nginx) -e 'tracepoint:syscalls:sys_enter_sendm* { @[probe] = count(); }'
Attaching 2 probes...
 
 
@[tracepoint:syscalls:sys_enter_sendmsg]: 904539</code></pre>
            <p>Each of those system calls causes an expensive context switch between the application and the kernel, thus impacting throughput.</p><p>But while <code>sendmsg()</code> only transmits a single UDP packet at a time for each invocation, its close cousin <code>sendmmsg()</code> (note the additional “m” in the name) is able to batch multiple packets per system call:</p>
            <pre><code>int sendmmsg(int sockfd, struct mmsghdr *msgvec,
    unsigned int vlen, int flags);</code></pre>
            <p>Multiple <code>struct mmsghdr</code> structures can be passed to the kernel as an array, each in turn carrying a single <code>struct msghdr</code> with its own <code>struct iovec</code> , with each element in the <code>msgvec</code> array representing a single UDP datagram.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/5QO4EdUwmU9MJhSCY2wwC1/ac6bc505e3b1f3b4d571910f13905131/sendmmsg.png" />
            
            </figure><p>Let's see what happens when NGINX is updated to use <code>sendmmsg()</code> to send QUIC packets:</p>
            <pre><code>% sudo bpftrace -p $(pgrep nginx) -e 'tracepoint:syscalls:sys_enter_sendm* { @[probe] = count(); }'
Attaching 2 probes...
 
 
@[tracepoint:syscalls:sys_enter_sendmsg]: 2437
@[tracepoint:syscalls:sys_enter_sendmmsg]: 15676</code></pre>
            <p>The number of system calls went down dramatically, which translates into an increase in throughput, though not quite as big as the decrease in syscalls:</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/6fv3ZaxYLZmteczFchjfiO/e0056c40dacea0422ed53edaf8158869/sendmmsg-chart.png" />
            
            </figure>
    <div>
      <h3>UDP segmentation offload</h3>
      <a href="#udp-segmentation-offload">
        
      </a>
    </div>
    <p>With <code>sendmsg()</code> as well as <code>sendmmsg()</code>, the application is responsible for separating each QUIC packet into its own buffer in order for the kernel to be able to transmit it. While the implementation in NGINX uses static buffers to implement this, so there is no overhead in allocating them, all of these buffers need to be traversed by the kernel during transmission, which can add significant overhead.</p><p>Linux supports a feature, Generic Segmentation Offload (GSO), which allows the application to pass a single "super buffer" to the kernel, which will then take care of segmenting it into smaller packets. The kernel will try to postpone the segmentation as much as possible to reduce the overhead of traversing outgoing buffers (some NICs even support hardware segmentation, but it was not tested in this experiment due to lack of capable hardware). Originally GSO was only supported for TCP, but support for UDP GSO was recently added as well, in Linux 4.18.</p><p>This feature can be controlled using the <code>UDP_SEGMENT</code> socket option:</p>
            <pre><code>setsockopt(fd, SOL_UDP, UDP_SEGMENT, &amp;gso_size, sizeof(gso_size)))</code></pre>
            <p>As well as via ancillary data, to control segmentation for each <code>sendmsg()</code> call:</p>
            <pre><code>cm = CMSG_FIRSTHDR(&amp;msg);
cm-&gt;cmsg_level = SOL_UDP;
cm-&gt;cmsg_type = UDP_SEGMENT;
cm-&gt;cmsg_len = CMSG_LEN(sizeof(uint16_t));
*((uint16_t *) CMSG_DATA(cm)) = gso_size;</code></pre>
            <p>Where <code>gso_size</code> is the size of each segment that form the "super buffer" passed to the kernel from the application. Once configured, the application can provide one contiguous large buffer containing a number of packets of <code>gso_size</code> length (as well as a final smaller packet), that will then be segmented by the kernel (or the NIC if hardware segmentation offloading is supported and enabled).</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/3vQo11I0RupCQ4msUqj0Ve/dcec6ac6c0bea7c737aa9fa822e69d0a/sendmsg-gso.png" />
            
            </figure><p><a href="https://github.com/torvalds/linux/blob/80a0c2e511a97e11d82e0ec11564e2c3fe624b0d/include/linux/udp.h#L94">Up to 64 segments</a> can be batched with the <code>UDP_SEGMENT</code> option.</p><p>GSO with plain <code>sendmsg()</code> already delivers a significant improvement:</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/4q2OxEsgZcsw2JXc8JcAfk/a64c9b48cad41378122e7d7c5a88e67a/gso-chart.png" />
            
            </figure><p>And indeed the number of syscalls also went down significantly, compared to plain <code>sendmsg()</code> :</p>
            <pre><code>% sudo bpftrace -p $(pgrep nginx) -e 'tracepoint:syscalls:sys_enter_sendm* { @[probe] = count(); }'
Attaching 2 probes...
 
 
@[tracepoint:syscalls:sys_enter_sendmsg]: 18824</code></pre>
            <p>GSO can also be combined with <code>sendmmsg()</code> to deliver an even bigger improvement. The idea being that each <code>struct msghdr</code> can be segmented in the kernel by setting the <code>UDP_SEGMENT</code> option using ancillary data, allowing an application to pass multiple “super buffers”, each carrying up to 64 segments, to the kernel in a single system call.</p><p>The improvement is again fairly significant:</p>
    <div>
      <h3>Evolving from AFAP</h3>
      <a href="#evolving-from-afap">
        
      </a>
    </div>
    <p>Transmitting packets as fast as possible is easy to reason about, and there's much fun to be had in optimizing applications for that, but in practice this is not always the best strategy when optimizing protocols for the Internet</p><p>Bursty traffic is more likely to cause or be affected by congestion on any given network path, which will inevitably defeat any optimization implemented to increase transmission rates.</p><p>Packet pacing is an effective technique to squeeze out more performance from a network flow. The idea being that adding a short delay between each outgoing packet will smooth out bursty traffic and reduce the chance of congestion, and packet loss. For TCP this was originally implemented in Linux via the fq packet scheduler, and later by the BBR congestion control algorithm implementation, which implements its own pacer.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/1PkaZcKDkzzjUDhFLRT1jw/a4247010827f1763bd7560894e30938e/afap.png" />
            
            </figure><p>Due to the nature of current QUIC implementations, which reside entirely in user-space, pacing of QUIC packets conflicts with any of the techniques explored in this post, because pacing each packet separately during transmission will prevent any batching on the application side, and in turn batching will prevent pacing, as batched packets will be transmitted as fast as possible once received by the kernel.</p><p>However Linux provides some facilities to offload the pacing to the kernel and give back some control to the application:</p><ul><li><p><b>SO_MAX_PACING_RATE</b>: an application can define this socket option to instruct the fq packet scheduler to pace outgoing packets up to the given rate. This works for UDP sockets as well, but it is yet to be seen how this can be integrated with QUIC, as a single UDP socket can be used for multiple QUIC connections (unlike TCP, where each connection has its own socket). In addition, this is not very flexible, and might not be ideal when implementing the BBR pacer.</p></li><li><p><b>SO_TXTIME / SCM_TXTIME</b>: an application can use these options to schedule transmission of specific packets at specific times, essentially instructing fq to delay packets until the provided timestamp is reached. This gives the application a lot more control, and can be easily integrated into sendmsg() as well as sendmmsg(). But it does not yet support specifying different times for each packet when GSO is used, as there is no way to define multiple timestamps for packets that need to be segmented (each segmented packet essentially ends up being sent at the same time anyway).</p></li></ul><p>While the performance gains achieved by using the techniques illustrated here are fairly significant, there are still open questions around how any of this will work with pacing, so more experimentation is required.</p> ]]></content:encoded>
            <category><![CDATA[QUIC]]></category>
            <category><![CDATA[UDP]]></category>
            <category><![CDATA[TCP]]></category>
            <category><![CDATA[HTTP3]]></category>
            <guid isPermaLink="false">3pwKBhG2s8cT4COiXLHTyT</guid>
            <dc:creator>Alessandro Ghedini</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[Magic Transit: Network functions at Cloudflare scale]]></title>
            <link>https://blog.cloudflare.com/magic-transit-network-functions/</link>
            <pubDate>Tue, 13 Aug 2019 13:00:00 GMT</pubDate>
            <description><![CDATA[ Today we announced Cloudflare Magic Transit, which makes Cloudflare’s network available to any IP traffic on the Internet. Up until now, Cloudflare has primarily operated proxy services: our servers terminate HTTP, TCP, and UDP sessions ]]></description>
            <content:encoded><![CDATA[ <p>Today we announced <a href="https://www.cloudflare.com/magic-transit">Cloudflare Magic Transit</a>, which makes Cloudflare’s network available to any IP traffic on the Internet. Up until now, Cloudflare has primarily operated proxy services: our servers terminate HTTP, TCP, and UDP sessions with Internet users and pass that data through new sessions they create with origin servers. With Magic Transit, we are now also operating at the IP layer: in addition to terminating sessions, our servers are applying a suite of network functions (DoS mitigation, firewalling, routing, and so on) on a packet-by-packet basis.</p><p>Over the past nine years, we’ve built a robust, scalable global network that currently spans 193 cities in over 90 countries and is ever growing. All Cloudflare customers benefit from this scale thanks to two important techniques. The first is anycast networking. Cloudflare was an <a href="/a-brief-anycast-primer/">early adopter</a> of anycast, using this routing technique to distribute Internet traffic across our data centers. It means that any data center can handle any customer’s traffic, and we can spin up new data centers without needing to acquire and provision new IP addresses. The second technique is homogeneous server architecture. Every server in each of our edge data centers is <a href="/cloudflare-architecture-and-how-bpf-eats-the-world/">capable of running every task</a>. We build our servers on commodity hardware, making it easy to quickly increase our processing capacity by adding new servers to existing data centers. Having no specialty hardware to depend on has also led us to develop an expertise in pushing the limits of what’s possible in networking using modern Linux kernel techniques.</p><p>Magic Transit is built on the same network using the same techniques, meaning our customers can now run their network functions at Cloudflare scale. Our fast, secure, reliable global edge becomes our customers’ edge. To explore how this works, let’s follow the journey of a packet from a user on the Internet to a Magic Transit customer’s network.</p>
    <div>
      <h2>Putting our DoS mitigation to work… for you!</h2>
      <a href="#putting-our-dos-mitigation-to-work-for-you">
        
      </a>
    </div>
    <p>In the <a href="/magic-transit/">announcement blog post</a> we describe an example deployment for Acme Corp. Let’s continue with this example here. When Acme brings their IP prefix 203.0.113.0/24 to Cloudflare, we start announcing that prefix to our transit providers, peers, and to Internet exchanges in each of our data centers around the globe. Additionally, Acme stops announcing the prefix to their own ISPs. This means that any IP packet on the Internet with a destination address within Acme’s prefix is delivered to a nearby Cloudflare data center, not to Acme’s router.</p><p>Let’s say I want to access Acme’s FTP server on 203.0.113.100 from my computer in Cloudflare’s office in Champaign, IL. My computer generates a TCP SYN packet with destination address 203.0.113.100 and sends it out to the Internet. Thanks to <a href="https://www.cloudflare.com/learning/cdn/glossary/anycast-network/">anycast</a>, that packet ends up at Cloudflare’s data center in Chicago, which is the closest data center (in terms of Internet routing distance) to Champaign. The packet arrives on the data center’s router, which uses <a href="https://en.wikipedia.org/wiki/Equal-cost_multi-path_routing">ECMP (Equal Cost Multi-Path) routing</a> to select which server should handle the packet and dispatches the packet to the selected server.</p><p>Once at the server, the packet flows through our <a href="/cloudflare-architecture-and-how-bpf-eats-the-world/">XDP- and iptables-based</a> DoS detection and mitigation functions. If this TCP SYN packet were determined to be part of an attack, it would be dropped and that would be the end of it. Fortunately for me, the packet is permitted to pass.</p><p>So far, this looks exactly like any other traffic on Cloudflare’s network. Because of our expertise in running a global anycast network we’re able to attract Magic Transit customer traffic to every data center and apply the <a href="/tag/ddos/">same DoS mitigation solution</a> that has been protecting Cloudflare for years. Our DoS solution has handled some of the largest attacks ever recorded, including a 942Gbps SYN flood in 2018. Below is a screenshot of a recent SYN flood of 300M packets per second. <a href="/how-cloudflares-architecture-allows-us-to-scale-to-stop-the-largest-attacks/">Our architecture lets us scale</a> to stop the largest attacks.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/BeJqqpkQ9uUUFLWFPjYe9/1c241da7ad7018a32c277d3bb3dfaee7/large-syn-flood.png" />
            
            </figure>
    <div>
      <h2>Network namespaces for isolation and control</h2>
      <a href="#network-namespaces-for-isolation-and-control">
        
      </a>
    </div>
    <p>The above looked identical to how all other Cloudflare traffic is processed, but this is where the similarities end. For our other services, the TCP SYN packet would now be dispatched to a local proxy process (e.g. our nginx-based HTTP/S stack). For Magic Transit, we instead want to dynamically provision and apply customer-defined network functions like firewalls and routing. We needed a way to quickly spin up and configure these network functions while also providing inter-network isolation. For that, we turned to network namespaces.</p><p>Namespaces are a collection of Linux kernel features for creating lightweight virtual instances of system resources that can be shared among a group of processes. Namespaces are a fundamental building block for containerization in Linux. Notably, Docker is built on Linux namespaces. A <i>network namespace</i> is an isolated instance of the Linux network stack, including its own network interfaces (with their own eBPF hooks), routing tables, netfilter configuration, and so on. Network namespaces give us a low-cost mechanism to rapidly apply customer-defined network configurations in isolation, all with built-in Linux kernel features so there’s no performance hit from userspace packet forwarding or proxying.</p><p>When a new customer starts using Magic Transit, we create a brand new network namespace for that customer on every server across our edge network (did I mention that every server can run every task?). We built a daemon that runs on our servers and is responsible for managing these network namespaces and their configurations. This daemon is constantly reading configuration updates from <a href="/helping-to-build-cloudflare-part-4/">Quicksilver</a>, our globally distributed key-value store, and applying customer-defined configurations for firewalls, routing, etc, <i>inside the customer’s namespace</i>. For example, if Acme wants to provision a firewall rule to allow FTP traffic (TCP ports 20 and 21) to 203.0.113.100, that configuration is propagated globally through Quicksilver and the Magic Transit daemon applies the firewall rule by adding an nftables rule to the Acme customer namespace:</p>
            <pre><code># Apply nftables rule inside Acme’s namespace
$ sudo ip netns exec acme_namespace nft add rule inet filter prerouting ip daddr 203.0.113.100 tcp dport 20-21 accept</code></pre>
            <p>Getting the customer’s traffic to their network namespace requires a little routing configuration in the default network namespace. When a network namespace is created, a pair of virtual ethernet (veth) interfaces is also created: one in the default namespace and one in the newly created namespace. This interface pair creates a “virtual wire” for delivering network traffic into and out of the new network namespace. In the default network namespace, we maintain a routing table that forwards Magic Transit customer IP prefixes to the veths corresponding to those customers’ namespaces. We use iptables to mark the packets that are destined for Magic Transit customer prefixes, and we have a routing rule that specifies that these specially marked packets should use the Magic Transit routing table.</p><p>(Why go to the trouble of marking packets in iptables and maintaining a separate routing table? Isolation. By keeping Magic Transit routing configurations separate we reduce the risk of accidentally modifying the default routing table in a way that affects how non-Magic Transit traffic flows through our edge.)</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/7qBuqejDrTQ3QFlEZ5IRzk/e7cdbc6f6d3c14a6a8dfe8d9d76abbb3/067C8C9E-3C73-4DA0-AD77-35DAC9A5F182.png" />
            
            </figure><p>Network namespaces provide a lightweight environment where a Magic Transit customer can run and manage network functions in isolation, letting us put full control in the customer’s hands.</p>
    <div>
      <h2>GRE + anycast = magic</h2>
      <a href="#gre-anycast-magic">
        
      </a>
    </div>
    <p>After passing through the edge network functions, the TCP SYN packet is finally ready to be delivered back to the customer’s network infrastructure. Because Acme Corp. does not have a network footprint in a colocation facility with Cloudflare, we need to deliver their network traffic over the public Internet.</p><p>This poses a problem. The destination address of the TCP SYN packet is 203.0.113.100, but the only network announcing the IP prefix 203.0.113.0/24 on the Internet is Cloudflare. This means that we can’t simply forward this packet out to the Internet—it will boomerang right back to us! In order to deliver this packet to Acme we need to use a technique called tunneling.</p><p>Tunneling is a method of carrying traffic from one network over another network. In our case, it involves encapsulating Acme’s IP packets inside of IP packets that can be delivered to Acme’s router over the Internet. There are a number of <a href="https://en.wikipedia.org/wiki/Tunneling_protocol#Common_tunneling_protocols">common tunneling protocols</a>, but <a href="https://en.wikipedia.org/wiki/Generic_Routing_Encapsulation">Generic Routing Encapsulation</a> (GRE) is often used for its simplicity and widespread vendor support.</p><p>GRE tunnel endpoints are configured both on Cloudflare’s servers (inside of Acme’s network namespace) and on Acme’s router. Cloudflare servers then encapsulate IP packets destined for 203.0.113.0/24 inside of IP packets destined for a publicly-routable IP address for Acme’s router, which decapsulates the packets and emits them into Acme’s internal network.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/6I9uQQIPjqBE6hGYsIyPsF/61e810c12480ad0ee7267f5354e1f510/97ED9A08-71AA-490B-BD35-1E6D288EE1A3.png" />
            
            </figure><p>Now, I’ve omitted an important detail in the diagram above: the IP address of Cloudflare’s side of the GRE tunnel. Configuring a GRE tunnel requires specifying an IP address for each side, and the outer IP header for packets sent over the tunnel must use these specific addresses. But Cloudflare has thousands of servers, each of which may need to deliver packets to the customer through a tunnel. So how many Cloudflare IP addresses (and GRE tunnels) does the customer need to talk to? The answer: just one, thanks to the magic of anycast.</p><p>Cloudflare uses anycast IP addresses for our GRE tunnel endpoints, meaning that any server in any data center is capable of encapsulating and decapsulating packets for the same GRE tunnel. <i>How is this possible? Isn’t a tunnel a point-to-point link?</i> The GRE protocol itself is stateless—each packet is processed independently and without requiring any negotiation or coordination between tunnel endpoints. While the tunnel is technically bound to an <i>IP address</i> it need not be bound to a <i>specific device</i>. Any device that can strip off the outer headers and then route the inner packet can handle any GRE packet sent over the tunnel. Actually, in the context of anycast the term “tunnel” is misleading since it implies a link between two fixed points. With Cloudflare’s Anycast GRE, a single “tunnel” gives you a conduit to every server in every data center on Cloudflare’s global edge.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/1422XZlV6HM7wgqKUYrBVx/4c7ddd86b938510b08dcdc8d43f9953f/228C64E9-DBE0-45AE-ACA9-8F1931998D4A.png" />
            
            </figure><p>One very powerful consequence of Anycast GRE is that it eliminates single points of failure. Traditionally, GRE-over-Internet can be problematic because an Internet outage between the two GRE endpoints fully breaks the “tunnel”. This means reliable data delivery requires going through the headache of setting up and maintaining redundant GRE tunnels terminating at different physical sites and rerouting traffic when one of the tunnels breaks. But because Cloudflare is encapsulating and delivering customer traffic from every server in every data center, there is no single “tunnel” to break. This means Magic Transit customers can enjoy the redundancy and reliability of terminating tunnels at multiple physical sites while only setting up and maintaining a single GRE endpoint, making their jobs simpler.</p>
    <div>
      <h2>Our scale is now your scale</h2>
      <a href="#our-scale-is-now-your-scale">
        
      </a>
    </div>
    <p>Magic Transit is a powerful new way to deploy network functions at scale. We’re not just giving you a virtual instance, we’re giving you a <i>global virtual edge</i>. Magic Transit takes the hardware appliances you would typically rack in your on-prem network and distributes them across every server in every data center in Cloudflare’s network. This gives you access to our global anycast network, our fleet of servers capable of running <i>your</i> tasks, and our engineering expertise building fast, reliable, secure networks. Our scale is now your scale.</p> ]]></content:encoded>
            <category><![CDATA[Speed & Reliability]]></category>
            <category><![CDATA[Magic Transit]]></category>
            <category><![CDATA[Network]]></category>
            <category><![CDATA[Anycast]]></category>
            <category><![CDATA[TCP]]></category>
            <category><![CDATA[Product News]]></category>
            <guid isPermaLink="false">7vElLpZoHQk7fk7D087Jvq</guid>
            <dc:creator>Nick Wondra</dc:creator>
        </item>
    </channel>
</rss>