
<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 10:09:56 GMT</lastBuildDate>
        <item>
            <title><![CDATA[Async QUIC and HTTP/3 made easy: tokio-quiche is now open-source]]></title>
            <link>https://blog.cloudflare.com/async-quic-and-http-3-made-easy-tokio-quiche-is-now-open-source/</link>
            <pubDate>Thu, 06 Nov 2025 14:00:00 GMT</pubDate>
            <description><![CDATA[ We’re excited to announce the open sourcing of tokio-quiche, our async QUIC library built on quiche and tokio. Relied upon in our services such as iCloud Private Relay and our next-generation Oxy-based proxies, tokio-quiche handles millions of HTTP/3 requests per second with low latency and high throughput.  ]]></description>
            <content:encoded><![CDATA[ <p>A little over 6 years ago, we presented <a href="https://blog.cloudflare.com/enjoy-a-slice-of-quic-and-rust/"><u>quiche</u></a>, our open source QUIC implementation written in Rust. Today we’re announcing the open sourcing of <a href="https://crates.io/crates/tokio-quiche"><b><u>tokio-quiche</u></b></a>, our battle-tested, asynchronous QUIC library combining both <b>quiche</b> and the Rust <b>Tokio</b> async runtime. Powering Cloudflare’s Proxy B in Apple iCloud Private Relay and our next-generation <a href="https://blog.cloudflare.com/introducing-oxy/"><u>Oxy-based</u></a> proxies, <b>tokio-quiche</b> handles millions of HTTP/3 requests per second with low latency and high throughput. tokio-quiche also powers <a href="https://blog.cloudflare.com/zero-trust-warp-with-a-masque/"><u>Cloudflare Warp’s MASQUE</u></a> client, replacing our WireGuard tunnels with QUIC-based tunnels, and the async version of <a href="https://blog.cloudflare.com/h3i/"><u>h3i</u></a>.</p><p>quiche was developed as a <a href="https://sans-io.readthedocs.io/how-to-sans-io.html"><u>sans-io</u></a> library, meaning that it implements the state machine required to handle the QUIC transport protocol while not making any assumptions about how its user intends to perform IO. This means that, with enough elbow grease, anyone can write an IO integration with quiche! This entails <code>connect</code>ing or <code>listen</code>ing on a UDP socket, managing sending and receiving UDP datagrams on that socket while feeding all network information to quiche. Given we need this integration to be async, we’d have to do all this while integrating with an async Rust runtime. tokio-quiche does all of that for you, no grease required.</p>
    <div>
      <h3>Lowering the barrier to entry</h3>
      <a href="#lowering-the-barrier-to-entry">
        
      </a>
    </div>
    <p>Originally, tokio-quiche was only used as the core of <a href="https://blog.cloudflare.com/introducing-oxy/"><u>Oxy’s</u></a> HTTP/3 <i>server</i>. But the spark to create tokio-quiche as a standalone library was our need for a MASQUE-capable HTTP/3 <i>client</i>. Our Zero Trust and Privacy Teams need MASQUE clients to tunnel data through WARP and our Privacy Proxies respectively, and we wanted to use the same technology to build both the client and server.</p><p>We initially open-sourced quiche to share our memory-safe QUIC and HTTP/3 implementation with as many stakeholders as possible. Our focus at the time was a low-level, sans-io design that could integrate into many types of software and be deployed widely. We achieved this goal, with quiche deployed in many different clients and servers. However, integrating sans-io libraries into applications is an error-prone and time-consuming process. Our aim with tokio-quiche is to lower the barrier of entry by providing much of the needed code ourselves.</p><p>Cloudflare alone embracing HTTP/3 is not of much use if others wanting to interact with our products and systems don't also adopt it. Open sourcing tokio-quiche makes integration with our systems more straightforward, and helps propel the industry into the new standard of HTTP. By contributing tokio-quiche back to the Rust ecosystem, we hope to promote the development and usage of HTTP/3, QUIC and new privacy preserving technologies.</p><p>tokio-quiche has been used internally for some years now. This gave us time to refine and battle-test it, demonstrating that it can handle millions of RPS. tokio-quiche is <b>not intended</b> to be a standalone HTTP/3 client or server, but implements low-level protocols and allows for higher-level projects in the future. The README contains examples of <a href="https://github.com/cloudflare/quiche/tree/master/tokio-quiche#starting-an-http3-server"><u>server</u></a> and <a href="https://github.com/cloudflare/quiche/tree/master/tokio-quiche#sending-an-http3-request"><u>client</u></a> event loops.</p>
    <div>
      <h3>It’s actors all the way down</h3>
      <a href="#its-actors-all-the-way-down">
        
      </a>
    </div>
    <p><a href="https://tokio.rs/"><u>Tokio</u></a> is a wildly popular asynchronous Rust runtime. It efficiently manages, schedules and executes the billions of asynchronous tasks which run on our edge. We use Tokio <a href="https://blog.cloudflare.com/introducing-oxy/"><u>extensively</u></a> <a href="https://blog.cloudflare.com/pingora-open-source/"><u>at</u></a> <a href="https://blog.cloudflare.com/20-percent-internet-upgrade/"><u>Cloudflare</u></a>, so we decided to tightly integrate quiche with it – thus the name, tokio-quiche. Under the hood, tokio-quiche uses <i>actors</i> to drive different parts of the QUIC and HTTP/3 state machine. Actors are small tasks with internal state that usually use message passing over channels to communicate with the outside world.</p><p>The actor model is a great abstraction to use for async-ifying sans-io libraries due to the conceptual similarities between the two. Both actors and sans-io libraries have some kind of internal state which they want exclusive access to. They both usually interact with the outside world by sending and receiving  “messages”. quiche’s “messages” are really raw byte buffers which represent incoming and outgoing network data. One of tokio-quiche’s “messages” is the <code>Incoming</code> struct which describes incoming UDP packets. Due to these similarities, async-ifying a sans-io library means: awaiting new messages or IO, translating the messages or IO into something the sans-io library understands, advancing the internal state machine, translating the state machine’s output to a message or IO, and finally sending the message or IO. (For more discussion on actors with Tokio, make sure to take a look at Alice Rhyl’s <a href="https://ryhl.io/blog/actors-with-tokio/"><u>excellent blog post</u></a> on the topic.)</p><p>The primary actor in tokio-quiche is the IO loop actor, which moves packets between quiche and the socket. Since QUIC is a transport protocol, it can carry any application protocol you want. <a href="https://datatracker.ietf.org/doc/rfc9114/"><u>HTTP/3</u></a> is quite common, but <a href="https://datatracker.ietf.org/doc/rfc9250/"><u>DNS over QUIC</u></a> and the upcoming <a href="https://blog.cloudflare.com/moq/"><u>Media over QUIC</u></a> are other examples. There's even <a href="https://www.rfc-editor.org/rfc/rfc9308.html"><u>an RFC</u></a> to help you create your own QUIC application! tokio-quiche exposes the <code>ApplicationOverQuic </code>trait to abstract over application protocols. The trait abstracts over quiche’s methods and the underlying I/O, allowing you to focus on your application logic. For example, our HTTP/3 debug and test client, <a href="https://blog.cloudflare.com/h3i/"><u>h3i</u></a>, is powered by a client-focused, non-HTTP/3 <code>ApplicationOverQuic</code> implementation.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/6AOaumZTjtZkzdY1iJLVTZ/c958b6937c2e4a455f3b98b0389388fd/image2.png" />
          </figure><p><sup>Server Architecture Diagram</sup></p><p>tokio-quiche ships with an HTTP/3-focused <code>ApplicationOverQuic</code> called <code>H3Driver</code>. <code>H3Driver</code> hooks up quiche’s HTTP/3 module to this IO loop to provide the building blocks for an async HTTP/3 client or server. The driver turns quiche’s raw HTTP/3 events into higher-level events and asynchronous body data streams, allowing you to respond to them in kind. <code>H3Driver</code> is itself generic, exposing <code>ServerH3Driver</code> and <code>ClientH3Driver</code> variants that each stack additional behavior on top of the core driver’s events.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/2zxI09AhiqrBSyk8fhcSnr/b78917274391ec855370e565985c7bfc/image4.png" />
          </figure><p><sup>Internal Data Flow</sup></p><p>Inside tokio-quiche, we spawn two important tasks that facilitate data movement from a socket to quiche. The first is the <code>InboundPacketRouter</code>, which owns the receiving half of the socket and routes inbound datagrams by their <a href="https://datatracker.ietf.org/doc/html/rfc9000#name-connection-id"><u>connection ID</u></a> (DCID) to a per-connection channel. The second task, the <code>IoWorker</code> actor, is the aforementioned IO loop and drives a single quiche <code>Connection</code>. It intersperses quiche calls with <code>ApplicationOverQuic</code> methods, ensuring you can inspect the connection before and after any IO interaction.</p><p>More blog posts on the creation of tokio-quiche are coming soon. We’ll discuss actor models and mutexes, UDP GRO and GSO, tokio task coop budgeting, and more.</p>
    <div>
      <h3>Next up: more on QUIC and beyond!</h3>
      <a href="#next-up-more-on-quic-and-beyond">
        
      </a>
    </div>
    <p>tokio-quiche is an important foundation for Cloudflare’s investment into the QUIC and HTTP/3 ecosystem for Tokio – but it is still only a building block with its own complexity. In the future, we plan to release the same easy-to-use HTTP client and server abstractions that power our Oxy proxies and WARP clients today. Stay tuned for more blog posts on QUIC and HTTP/3 at Cloudflare, including an open-source client for customers of our <a href="https://blog.cloudflare.com/privacy-edge-making-building-privacy-first-apps-easier/#privacy-preserving-proxying-built-into-applications"><u>Privacy Proxies</u></a> and a completely new service that’s handling millions of RPS with tokio-quiche!</p><p>For now, check out the <a href="https://crates.io/crates/tokio-quiche"><u>tokio-quiche crate</u></a> on crates.io and its <a href="https://github.com/cloudflare/quiche/tree/master/tokio-quiche"><u>source code</u></a> on GitHub to build your very own QUIC application. Could be a simple echo server, a DNS-over-QUIC client, a custom VPN, or even a fully-fledged HTTP server. Maybe you will beat us to the punch?</p> ]]></content:encoded>
            <category><![CDATA[Protocols]]></category>
            <category><![CDATA[QUICHE]]></category>
            <category><![CDATA[Privacy]]></category>
            <guid isPermaLink="false">pDSjJ9E3DaqTAtPDR0VV7</guid>
            <dc:creator>Pedro Mendes</dc:creator>
            <dc:creator>Leo Blöcher</dc:creator>
            <dc:creator>Evan Rittenhouse</dc:creator>
            <dc:creator>Fisher Darling</dc:creator>
        </item>
        <item>
            <title><![CDATA[How Oxy uses hooks for maximum extensibility]]></title>
            <link>https://blog.cloudflare.com/oxy-extensibility/</link>
            <pubDate>Fri, 26 May 2023 13:00:17 GMT</pubDate>
            <description><![CDATA[ Let's take a look from the perspective of an Oxy application developer, and then we can discuss the implementation of the framework and some of the interesting design decisions we made ]]></description>
            <content:encoded><![CDATA[ <p></p><p>We recently <a href="/introducing-oxy/">introduced Oxy</a>, our Rust framework for building proxies. Through a YAML file, Oxy allows applications to easily configure listeners (e.g. IP, <a href="/unlocking-quic-proxying-potential/">MASQUE</a>, HTTP/1), telemetry, and much more. However, when it comes to application logic, a programming language is often a better tool for the job. That’s why in this post we’re introducing Oxy’s rich <a href="https://en.wikipedia.org/wiki/Dependency_injection">dependency injection</a> capabilities for programmatically modifying all aspects of a proxy.</p><p>The idea of extending proxies with scripting is well established: we've had great past success with <a href="/pushing-nginx-to-its-limit-with-lua/">Lua in our OpenResty/NGINX deployments</a> and there are numerous web frameworks (e.g. <a href="https://expressjs.com/en/guide/using-middleware.html">Express</a>) with middleware patterns. While Oxy is geared towards the development of <a href="https://www.cloudflare.com/learning/cdn/glossary/reverse-proxy/">forward proxies</a>, they all share the model of a pre-existing request pipeline with a mechanism for integrating custom application logic. However, the use of Rust greatly helps developer productivity when compared to embedded scripting languages. Having confidence in the types and mutability of objects being passed to and returned from callbacks is wonderful.</p><p>Oxy exports a series of hook traits that “hook” into the lifecycle of a <i>connection</i>, not just a request. Oxy applications need to control almost every layer of the <a href="https://www.cloudflare.com/learning/ddos/glossary/open-systems-interconnection-model-osi/">OSI model</a>: how packets are received and sent, what tunneling protocols they could be using, what HTTP version they are using (if any), and even <a href="https://www.cloudflare.com/learning/dns/what-is-dns/">how DNS resolution is performed</a>. With these hooks you can extend Oxy in any way possible in a safe and performant way.</p><p>First, let's take a look from the perspective of an Oxy application developer, and then we can discuss the implementation of the framework and some of the interesting design decisions we made.</p>
    <div>
      <h2>Adding functionality with hooks</h2>
      <a href="#adding-functionality-with-hooks">
        
      </a>
    </div>
    <p>Oxy’s dependency injection is a barebones version of what Java or C# developers might be accustomed to. Applications simply implement the start method and return a struct with their hook implementations:</p>
            <pre><code>async fn start(
    _settings: ServerSettings&lt;(), ()&gt;,
    _parent_state: Metadata,
) -&gt; anyhow::Result&lt;Hooks&lt;Self&gt;&gt; {
    Ok(Hooks {
        ..Default::default()
    })
}</code></pre>
            <p>We can define a simple callback, <code>EgressHook::handle_connection</code>, that will forward all connections to the upstream requested by the client. Oxy calls this function before attempting to make an upstream connection.</p>
            <pre><code>#[async_trait]
impl&lt;Ext&gt; EgressHook&lt;Ext&gt; for MyEgressHook
where
    Ext: OxyExt,
{
    async fn handle_connection(
        &amp;self,
        upstream_addr: SocketAddr,
        _egress_ctx: EgressConnectionContext&lt;Ext&gt;,
    ) -&gt; ProxyResult&lt;EgressDecision&gt; {
        Ok(EgressDecision::ExternalDirect(upstream_addr))
    }
}

async fn start(
    _settings: ServerSettings&lt;(), ()&gt;,
    _parent_state: Metadata,
) -&gt; anyhow::Result&lt;Hooks&lt;Self&gt;&gt; {
    Ok(Hooks {
        egress: Some(Arc::new(MyEgressHook)),
        ..Default::default()
    })
}</code></pre>
            <p>Oxy simply proxies the connection, but we might want to consider restricting which upstream IPs our clients are allowed to connect to. The implementation above allows everything, but maybe we have internal services that we wish to prevent proxy users from accessing.</p>
            <pre><code>#[async_trait]
impl&lt;Ext&gt; EgressHook&lt;Ext&gt; for MyEgressHook
where
    Ext: OxyExt,
{
    async fn handle_connection(
        &amp;self,
        upstream_addr: SocketAddr,
        _egress_ctx: EgressConnectionContext&lt;Ext&gt;,
    ) -&gt; ProxyResult&lt;EgressDecision&gt; {
        if self.private_cidrs.find(upstream_addr).is_some() {
            return Ok(EgressDecision::Block);
        }

        Ok(EgressDecision::ExternalDirect(upstream_addr))
    }
}</code></pre>
            <p>This blocking strategy is crude. Sometimes it’s useful to allow certain clients to connect to internal services – a <a href="https://prometheus.io/docs/introduction/overview/">Prometheus</a> scraper is a good example. To authorize these connections, we’ll implement a simple Pre-Shared Key (PSK) authorization scheme – if the client sends the header <code>Proxy-Authorization: Preshared oxy-is-a-proxy</code>, then we’ll let them connect to private addresses via the proxy.</p><p>To do this, we need to attach some state to the connection as it passes through Oxy. Client headers only exist in the HTTP CONNECT phase, but we need access to the PSK during the egress phase. With Oxy, this can be done by leveraging its Opaque Extensions to attach arbitrary (yet fully typed) context data to a connection. Oxy initializes the data and passes it to each hook. We can mutate this data when we read headers from the client, and read it later during egress.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/6bUwt3l14VvDFGgFI2WK31/a6271793d05dad32e990564c817dd821/download-18.png" />
            
            </figure>
            <pre><code>#[derive(Default)]
struct AuthorizationResult {
    can_access_private_cidrs: Arc&lt;AtomicBool&gt;,
}

#[async_trait]
impl&lt;Ext&gt; HttpRequestHook&lt;Ext&gt; for MyHttpHook
where
    Ext: OxyExt&lt;IngressConnectionContext = AuthorizationResult&gt;,
{
    async fn handle_proxy_connect_request(
        self: Arc&lt;Self&gt;,
        connect_req_head: &amp;Parts,
        req_ctx: RequestContext&lt;Ext&gt;,
    ) -&gt; ConnectDirective {
        const PSK_HEADER: &amp;str = "Preshared oxy-is-a-proxy";

        // Grab the authorization header and update 
        // the ingress_ctx if the preshared key matches.
        if let Some(authorization_header) = 
          connect_req_head.headers.get("Proxy-Authorization") {
            if authorization_header.to_str().unwrap() == PSK_HEADER {
                req_ctx
                    .ingress_ctx()
                    .ext()
                    .can_access_private_cidrs
                    .store(true, Ordering::SeqCst);
            }
        }

        ConnectDirective::Allow
    }
}</code></pre>
            <p>From here, any hook in the pipeline can access this data. For our purposes, we can just update our existing <code>handle_connection</code> callback:</p>
            <pre><code>#[async_trait]
impl&lt;Ext&gt; EgressHook&lt;Ext&gt; for MyEgressHook
where
    Ext: OxyExt&lt;IngressConnectionContext = AuthorizationResult&gt;,
{
    async fn handle_connection(
        &amp;self,
        upstream_addr: SocketAddr,
        egress_ctx: EgressConnectionContext&lt;Ext&gt;,
    ) -&gt; ProxyResult&lt;EgressDecision&gt; {
        if self.private_cidrs.find(upstream_addr).is_some() {
            if !egress_ctx
                .ingress_ctx()
                .ext()
                .can_access_private_cidrs
                .load(Ordering::SeqCst)
            {
                return Ok(EgressDecision::Block);
            }
        }

        Ok(EgressDecision::ExternalDirect(upstream_addr))
    }
}</code></pre>
            <p>This is a somewhat contrived example, but in practice hooks and their extension types allow Oxy apps to fully customize all aspects of proxied traffic.</p><p>A real world example would be implementing the RFC 9209 <a href="https://www.rfc-editor.org/rfc/rfc9209.html#name-next-hop">next-hop</a> Proxy-Status header. This involves setting a header containing the IP address we connected to on behalf of the client. We can do this with two pre-existing callbacks and a little bit of state: first we save the upstream passed to <code>EgressHook::handle_connection_established</code> and then read the value in the <code>HttpRequestHook:handle_proxy_connect_response</code> in order to set the header on the CONNECT response.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/1PMF1dZjgYHNplkhvsVLvv/e6cf3f5a89e242e0232eb1edf4be23f1/download--1--12.png" />
            
            </figure>
            <pre><code>#[derive(Default)]
struct ConnectProxyConnectionContext {
    upstream_addr: OnceCell&lt;SocketAddr&gt;,
}

#[async_trait]
impl&lt;Ext&gt; EgressHook&lt;Ext&gt; for MyEgressHook
where
    Ext: OxyExt&lt;IngressConnectionContext = ConnectProxyConnectionContext&gt;,
{
    fn handle_connection_established(
        &amp;self,
        upstream_addr: SocketAddr,
        egress_ctx: EgressConnectionContext&lt;Ext&gt;,
    ) {
        egress_ctx
            .ingress_ctx()
            .ext()
            .upstream_addr
            .set(upstream_addr);
    }
}

#[async_trait]
impl&lt;Ext&gt; HttpRequestHook&lt;Ext&gt; for MyHttpRequestHook
where
    Ext: OxyExt&lt;IngressConnectionContext = ConnectProxyConnectionContext&gt;,
{
    async fn handle_proxy_connect_response(
        self: Arc&lt;Self&gt;,
        mut res: Response&lt;OxyBody&gt;,
        req_ctx: RequestContext&lt;Ext&gt;,
    ) -&gt; ProxyConnectResponseHandlingOutcome {
        let ingress = req_ctx.ingress_ctx();
        let ingress_ext = ingress.ext();

        if let Some(upstream_addr) = ingress_ext.upstream_addr.get() {
            res.headers_mut().insert(
                "Proxy-Status",
                HeaderValue::from_str(&amp;format!("next-hop=\"{upstream_addr}\"")).unwrap(),
            );
        }

        res.into()
    }
}</code></pre>
            <p>These examples only consider a few of the hooks along the HTTP CONNECT pipeline, but many real Oxy applications don’t even have L7 ingress! We will talk about the abundance of hooks later, but for now let’s look at their implementation.</p>
    <div>
      <h2>Hook implementation</h2>
      <a href="#hook-implementation">
        
      </a>
    </div>
    <p>Oxy exists to be used by multiple teams, all with different needs and requirements. It needs a pragmatic solution to extensibility that allows one team to be productive without incurring too much of a cost on others. Hooks and their Opaque Extensions provide effectively limitless customization to applications via a clean, strongly typed interface.</p><p>The implementation of hooks within Oxy is relatively simple – throughout the code there are invocations of hook callbacks:</p>
            <pre><code>if let Some(ref hook) = self.hook {
    hook.handle_connection_established(upstream_addr, &amp;egress_ctx)
        .await;
}</code></pre>
            <p>If a user-provided hook exists, we call it. Some hooks are more like events (e.g. <code>handle_connection_established</code>), and others have return values (e.g. <code>handle_connection</code>) which are matched on by Oxy for control flow. If a callback isn’t implemented, the default trait implementation is used. If a hook isn’t implemented at all, Oxy’s business logic just executes its default functionality. These levels of default behavior enable the minimal example we started with earlier.</p><p>While hooks solve the problem of integrating app logic into the framework, there is invariably a need to pass custom state around as we demonstrated in our PSK example. Oxy manages this custom state, passing it to hook invocations. As it is generic over the type defined by the application, this is where things get more interesting.</p>
    <div>
      <h3>Generics and opaque types</h3>
      <a href="#generics-and-opaque-types">
        
      </a>
    </div>
    <p>Every team that works with Oxy has unique business needs, so it is important that one team’s changes don’t cause a cascade of <a href="https://www.cloudflare.com/learning/cloud/how-to-refactor-applications/">refactoring</a> for the others. Given that these context fields are of a user-defined type, you might expect heavy usage of generics. With Oxy we took a different approach: a generic interface is presented to application developers, but within the framework the type is erased. Keeping generics out of the internal code means adding new extension types to the framework is painless.</p><p>Our implementation relies on the <a href="https://doc.rust-lang.org/std/any/index.html"><code>Any</code></a> trait. The framework treats the data as an opaque blob, but when it traverses the public API, the wrapped Any object is downcast into the concrete type defined by the user. The public API layer enforces that the user type must implement <a href="https://doc.rust-lang.org/std/default/trait.Default.html"><code>Default</code></a>, which allows Oxy to be wholly responsible for creating and managing instances of the type. Mutations are then done by users of the framework through <a href="https://doc.rust-lang.org/reference/interior-mutability.html">interior mutability</a>, usually with atomics and locks.</p><p>Crates like <a href="https://docs.rs/reqwest-middleware/latest/reqwest_middleware/">reqwest_middleware</a>, <a href="https://crates.io/crates/tracing">tracing</a> and <a href="https://crates.io/crates/http">http</a> have a similar extension mechanism.</p>
    <div>
      <h3>There’s a hook for that</h3>
      <a href="#theres-a-hook-for-that">
        
      </a>
    </div>
    <p>As you might have gathered, Oxy cares a lot about the productivity of Oxy app developers. The plethora of injection points lets users quickly add features and functionality without worrying about “irrelevant” proxy logic. Sane defaults help balance customizability with complexity.</p><p>Only a subset of callbacks will be invoked for a given packet: applications operating purely at L3 will see different hook callbacks fired compared to one operating at L7. This again is customizable – if desired, <a href="/introducing-oxy/#on-ramps">Oxy’s design</a> allows connections to be upgraded (or downgraded)  which would cause a different set of callbacks to be invoked.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/1gEBrgeFRuB3Nz5D78E1yJ/a78077d87e712c9c01e88deddf6f69c0/download--2--10.png" />
            
            </figure><p>The ingress phase is where the hooks controlling the upgrading of L3 and decapsulation of specific L4 protocols reside. For our L3 IP Tunnel, Oxy has powerful callbacks like <code>IpFlowHook::handle_flow</code> which allow applications to drop, upgrade or redirect flows. <code>IpFlowHook::handle_packet</code> gives that same level of control at the packet level – even allowing us to modify the byte array as it passes through.</p><p>Let’s consider the H2 <a href="https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt">Proxy Protocol</a> example in the above diagram. After Oxy has accepted the Proxy Protocol connection it fires <code>ProxyProtocolConnectionHook::handle_connection</code> with the parsed header, allowing applications to handle any <a href="https://en.wikipedia.org/wiki/Type%E2%80%93length%E2%80%93value">TLVs</a> of interest. Hook like these are common – Oxy handles the heavy lifting and then passes the application some useful information.</p><p>From here, L4 connections are funneled through the <code>IngressHook</code> which contains a callback we saw in our initial example: <code>IngressHook::handle_connection</code>. This works as you might expect, allowing applications to control whether to <code>Allow</code> or <code>Block</code> a connection as it ingresses. There is a counterpart: <code>IngressHook::handle_connection_close</code>, which when called gives applications insight into ingress connection statistics like loss, retransmissions, bytes transferred, etc.</p><p>Next up is the transformation phase, where we start to see some of our more powerful hooks. Oxy invokes <code>TunnelHook::should_intercept_https</code>, passing the <a href="https://en.wikipedia.org/wiki/Server_Name_Indication">SNI</a> along with the usual connection context. This enables applications to easily configure HTTPS interception based on hostname and any custom context data (e.g. ACLs). By default, Oxy effectively <a href="/oxy-fish-bumblebee-splicer-subsystems-to-improve-reliability/">splices</a> the ingress and egress sockets, but if applications wish to have complete control over the tunneling, that is possible with <code>TunnelHook::get_app_tunnel_pipeline</code>, where applications are simply provided the two sockets and can implement whatever interception capabilities they wish.</p><p>Of particular interest to those wishing to implement L7 firewalls, the <code>HttpRequestHookPipeline</code> has two very powerful callbacks:  <code>handle_request</code> and <code>handle_response</code>. Both of these offer a similar high level interface for streaming rewrites or scanning of HTTP bodies.</p><p>The <code>EgressHook</code> has the most callbacks, including some of the most powerful ones. For situations where hostnames are provided, DNS resolution must occur. At its simplest, Oxy allows applications to specify the nameservers used in resolution. If more control is required, Oxy provides a callback – <code>EgressHook::handle_upstream_ips</code> – which gives applications an opportunity to mutate the resolved IP addresses before Oxy connects. If applications want absolute control, they can turn to <code>EgressHook::dns_resolve_override</code> which is invoked with a hostname and expects a <code>Vec&lt;IpAddr&gt;</code> to be returned.</p><p>Much like the <code>IngressHook</code>, there is an <code>EgressHook::handle_connection</code> hook, but rather than just <code>Allow</code> or <code>Block</code>, applications can instruct Oxy to egress their connection externally, internally within Cloudflare, or even downgrade to IP packets. While it’s often best to defer to the framework for connection establishment, Oxy again offers complete control to those who want it with a few override callbacks, e.g. <code>tcp_connect_override</code>, <code>udp_connect_override</code>. This functionality is mainly leveraged by <a href="/cloudflare-servers-dont-own-ips-anymore/">our egress service</a>, but available to all Oxy applications if they need it.</p><p>Lastly, one of the newest additions, the <code>AppLifecycleHook</code>. Hopefully this sees orders of magnitude fewer invocations than the rest. The <code>AppLifecycleHook::state_for_restart</code> callback is invoked by Oxy during a graceful shutdown. Applications are then given the opportunity to serialize their state which will be passed to the child process. Graceful restarts are <a href="/graceful-upgrades-in-go/">a little more nuanced</a>, but this hook cleanly solves the problem of passing application state between releases of the application.</p><p>Right now we have around 64 public facing hooks and we keep adding more. The above diagram is (largely) accurate at time of writing but if a team needs a hook and there can be a sensible default for it, then it might as well be added. One of the primary drivers of the hook architecture for Oxy is that different teams can work on and implement the hooks that they need. Business logic is kept outside Oxy, so teams can readily leverage each other's work.</p><p>We would be remiss not to mention the issue of discoverability. For most cases, it isn’t an issue, however application developers may find when developing certain features that a more holistic understanding is necessary. This inevitably means looking into the Oxy source to fully understand when and where certain hook callbacks will be invoked. Reasoning about the order callbacks will be invoked is even thornier. Many of the hooks alter control flow significantly, so there’s always some risk that a change in Oxy could mean a change in the semantics of the applications built on top of it. To solve this, we’re experimenting with different ways to record hook execution orders when running integration tests, maybe through a <a href="https://doc.rust-lang.org/reference/procedural-macros.html">proc-macro</a> or compiler tooling.</p>
    <div>
      <h2>Conclusion</h2>
      <a href="#conclusion">
        
      </a>
    </div>
    <p>In this post we’ve just scratched the surface of what’s possible with hooks in Oxy. In our example we saw a glimpse of their power: just two simple hooks and a few lines of code, and we have a forward proxy with built-in metrics, tracing, graceful restarts and much, much more.</p><p>Oxy’s extensibility with hooks is “only” dependency injection, but we’ve found this to be an extremely powerful way to build proxies. It’s dependency injection at all layers of the networking stack, from IP packets and tunnels all the way up to proxied UDP streams over QUIC. The shared core with hooks approach has been a terrific way to build a proxy framework. Teams add generic code to the framework, such as new Opaque Extensions in specific code paths, and then use those injection points to implement the logic for everything from <a href="/icloud-private-relay/">iCloud Private Relay</a> to <a href="/warp-to-warp/">Cloudflare Zero Trust</a>. The generic capabilities are there for all teams to use, and there’s very little to no cost if you decide not to use them. We can’t wait to see what the future holds and for Oxy’s further adoption within Cloudflare.</p> ]]></content:encoded>
            <category><![CDATA[Oxy]]></category>
            <category><![CDATA[Deep Dive]]></category>
            <guid isPermaLink="false">3cCyxYT5XOZaYIbRNhxxY1</guid>
            <dc:creator>Will Bartlett</dc:creator>
            <dc:creator>Fisher Darling</dc:creator>
        </item>
    </channel>
</rss>