
<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, 11 Jul 2026 07:19:50 GMT</lastBuildDate>
        <item>
            <title><![CDATA[Introducing Meerkat: an experiment in global consensus]]></title>
            <link>https://blog.cloudflare.com/meerkat-introduction/</link>
            <pubDate>Wed, 08 Jul 2026 13:00:00 GMT</pubDate>
            <description><![CDATA[ Cloudflare Research is building a global consensus service called Meerkat that uses a new consensus algorithm called QuePaxa. We plan to use Meerkat to build a strongly consistent, fault-tolerant key-value store, and other applications. ]]></description>
            <content:encoded><![CDATA[ <p>Many internal services at Cloudflare need to read and modify the same control-plane state from across our 330+ global data centers. They need guarantees that different readers <i>never </i>see inconsistent state, and that the system remains available for writes even when some data centers or links fail. </p><p>But Cloudflare’s network runs across the entire Internet, and the Internet is an unpredictable place. Servers and data centers go down. Queues fill up. Links and cables get cut. These conditions make it difficult to run a globally available data system that guarantees strong consistency (e.g., that all readers are guaranteed to read all prior writes) because hostile conditions hinder distributed system replicas’ ability to reliably synchronize data with one another.</p><p>One way to synchronize data safely despite adverse network conditions is via a <i>consensus algorithm, </i>which<i> </i>allows a set of machines to agree on the same sequence of values, such as key-value store put and <code>get</code> operations, as long as a majority remains alive and able to communicate. </p><p>Unfortunately, commonly deployed consensus algorithms like <a href="https://raft.github.io/"><u>Raft</u></a> suffer in wide-area networks like Cloudflare’s because they rely on <i>leaders </i>and<i> timeouts</i>. The <i>leader</i> is the only replica allowed to make writes, and if it fails due to a crash or network degradation, the system becomes unavailable until some other replica <i>times out</i> and a new leader is elected. And these timeout values are hard to configure in networks with unpredictable latencies.</p><p>We have experienced multiple incidents caused by unavailable leaders in consensus-driven systems.</p><p>And so, for the past year, Cloudflare’s Research <a href="https://research.cloudflare.com/"><u>team</u></a> has been building a new distributed consensus service called <b>Meerkat</b> powered by a consensus algorithm called <a href="https://bford.info/pub/os/quepaxa/quepaxa.pdf"><u>QuePaxa</u></a>, published in 2023 by Tennage &amp; Băsescu et al. QuePaxa differs from Raft in that all replicas can perform writes at all times, and progress is never halted due to a timeout, which makes it well suited for Cloudflare’s network. We layer <i>applications</i>, like a transactional key-value store and leasing system, atop Meerkat’s consensus log. To our knowledge, this will be the first industrial deployment of QuePaxa at global scale.</p><p>Meerkat is an experimental consensus service that is still in development. It’s being designed initially to manage small pieces of control plane state (e.g., leadership for replicated databases) and so it will be kept internal-only for the immediate future. This post introduces Meerkat and lays the groundwork for the Meerkat-related blog posts to come. </p>
    <div>
      <h2>What we need from a global control-plane data system</h2>
      <a href="#what-we-need-from-a-global-control-plane-data-system">
        
      </a>
    </div>
    <p>Many Cloudflare services read and write <i>control-plane data</i>, data that helps those services operate correctly, from multiple machines distributed all over the world. One example of control-plane data is <i>placement information</i>: where certain resources (like an AI model instance) are stored. Another example is <i>leadership information</i>: which machine is currently allowed to perform writes to a database. </p><p>Control-plane data must be both <i>strongly</i> <i>consistent</i> and<i> accessible despite particular kinds of faults.</i></p><p>In this section we precisely describe our consistency and fault tolerance requirements for a Cloudflare consensus service. We use a key-value store for a running example of an application running atop our consensus service, though other applications (e.g., distributed leases/locks) are possible.</p>
    <div>
      <h3>Strong consistency</h3>
      <a href="#strong-consistency">
        
      </a>
    </div>
    <p>A distributed data system’s <a href="https://jepsen.io/consistency/models"><u>consistency</u></a> level describes what kinds of weird behavior the system is allowed to exhibit when it receives concurrent reads and writes. Consider a distributed key-value store that stores a single numeric value <code>x = 6</code> across multiple nodes. Also consider the following sequence of writes. These writes are submitted to different nodes on a best-effort basis, and could arrive in any order: </p><ol><li><p><code>x = x + 1</code></p></li><li><p><code>x = x / 2</code></p></li></ol><p>A system’s consistency level tells you what values of <code>x</code> a client might see when reading <code>x</code> after these writes. Consider the following sequence of operations and the possible execution orders under different consistency levels:</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/Ie5OrNyD2MJVN47XewDiD/52d0c61577c39f026ed49d5035a8d7cd/BLOG-3347_image4.png" />
          </figure><p>In a weak consistency level, writes can be re-ordered. In a stronger consistency model, writes can’t be reordered, but reads can. In the strongest possible consistency level, the operations are ordered exactly as they occurred in real time. This property is called <i>linearizability</i>.</p><p>At Cloudflare, many services want linearizability. Unlike weaker forms of consistency, linearizability relieves programmers from thinking about all the weird behaviors the data systems might exhibit. Instead, they can reason about the distributed system like they reason about local memory on a single-threaded machine: all reads after a write will see that write. For additional reading material on the dangers of weak consistency, check out this <a href="https://brooker.co.za/blog/2025/11/18/consistency.html"><u>post</u></a> by Marc Brooker.</p><p>(If you’re wondering, Meerkat’s key-value store also provides serializability, which we’ll write about in a future post.)</p>
    <div>
      <h3>Fault tolerance</h3>
      <a href="#fault-tolerance">
        
      </a>
    </div>
    <p>A system’s level of fault tolerance describes what kinds of faults the system can handle before catastrophes happen. Catastrophes are typically violations of properties the system aims to uphold, e.g., that two consecutive reads without an intervening write for the same key never see different values, or that the system remains available for writes. The faults include network failures or delays, machine crashes, and machine restarts. A system will typically explicitly handle some faults but not others (you can’t handle all faults, as the universe could always reach heat-death). For example, some key-value stores might guarantee to remain available for writes as long as two-thirds of the machines in the system can communicate and don’t crash, but make no promises if a machine is compromised and starts sending malicious messages.</p><p>Our desired fault tolerance properties are as follows:</p><p><b>First</b>, the data system should remain available for writes and reads from a client located in any of our data centers as long as the following are true:</p><ol><li><p>A majority of the machines in our system are alive and can communicate with one another. (Formally, we tolerate <code>f</code> faults in a system of <code>2f + 1</code> machines).</p></li><li><p>The client can contact <i>any</i> machine in the system that is connected to a majority of live machines.</p></li></ol><p>This means that a single failed machine, or network degradation on a single link, does not affect availability of the system<i>. </i>This property is not provided by Raft-based systems, as we’ll see later.</p><p><b>Second</b>, the data system remains <i>correct</i> as long as no actor in the system is actively malicious (and, of course, there are no bugs). We define <i>correctness</i> in terms of consensus <i>safety </i>later, but loosely speaking this means no two up-to-date machines will ever disagree about the world (e.g., one thinks that <code>key1=1</code> while another thinks that <code>key1=2</code>).</p><p>To summarize, the system must remain correct even if machines crash, machines restart, networks fail or degrade, data centers go down, and more (though we, like Raft-based systems, do not handle <a href="https://en.wikipedia.org/wiki/Byzantine_fault"><u>Byzantine faults</u></a>).</p>
    <div>
      <h2>Introducing Meerkat</h2>
      <a href="#introducing-meerkat">
        
      </a>
    </div>
    <p>Meerkat is a consensus service <i>upon which </i>we can build applications that exhibit the above properties (strong consistency and fault tolerance) like a key-value (KV) store. To understand how Meerkat works, we first outline Meerkat’s general architecture, and then describe how Meerkat’s choice of consensus algorithm helps provide strong consistency and fault tolerance.</p><p>Developers of services using Meerkat request a <i>cluster</i> of Meerkat <i>replicas</i>. Each replica is connected to every other replica. Each replica participates in the consensus algorithm and can receive both reads and writes. The developer can specify which data centers are allowed to host their replicas, and Meerkat places them automatically.</p><p>To interact with their cluster, a developer’s client sends an application-specific request to any replica in the cluster. A single replica may host many kinds of applications, but the simplest one is a key-value store, so the simplest application-specific request type is a KV <code>get</code> or <code>put</code>. The replica responds to the request with an application-specific response (e.g., the records requested with the <code>get</code>). Note that KV reads (<code>get</code>s) are guaranteed to read up-to-date information.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/2DHsJkxyuRlmVZUhg2WoPh/4e83ae8c01ce7758a2af8c90895475c8/BLOG-3347_image2.png" />
          </figure>
    <div>
      <h3>Meerkat’s log</h3>
      <a href="#meerkats-log">
        
      </a>
    </div>
    <p>Under the hood, the replica translates application requests (e.g., <code>get</code> and <code>put</code>) into <i>log events</i>. That replica distributes each log event to all other replicas using a consensus algorithm such that all replicas maintain the exact same log of events (in reality, a replica may lag behind, but shall never record different entries). These events are arbitrary — Meerkat’s core doesn’t care what’s in them. Meerkat <i>applications</i> care about log event contents. Each Meerkat replica “hosts” many Meerkat applications (e.g., key-value store) that read the log events and construct state. (Note that each replica belongs to exactly one cluster.)</p><p>For instance, the KV Meerkat application constructs an in-memory key-value store from the log events. So when a client sends a write like <code>put k1 v1</code>, the receiving replica places that write into a log event and distributes it to all replicas. If someone else subsequently writes <code>put k1 v11</code> to a different replica, this event is also distributed to all replicas. Since all functioning replicas have the same log, those replicas can apply the operations in the log in sequence to construct the exact same state. Note that <code>get</code> requests also create distributed log events (for linearizability, as explained in the next section).</p><p>Here is an example of how a replica’s KV store is updated as it receives log events:</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/5jTmSTMFIi20yFDIQtVf4l/4b1871e87d46668ecaa401a306755f60/BLOG-3347_image3.png" />
          </figure>
    <div>
      <h3>How Meerkat’s log enables strong consistency</h3>
      <a href="#how-meerkats-log-enables-strong-consistency">
        
      </a>
    </div>
    <p>Meerkat guarantees that if one client executes <code>put k1 v1</code>, a second client subsequently executes <code>put k1 v11</code>, and a third client subsequently executes <code>get k1</code> (with a consistent read), they will always read <code>v11</code>. It guarantees this even if each request is submitted to a different replica, and those replicas are distributed randomly across the world. This is linearizability. To see how Meerkat guarantees this, we must examine Meerkat’s log in more detail.</p><p>The Meerkat log is a sequence of slots. A slot is a box that can contain an event or not. A slot that contains an event is called a <i>decided </i>slot. All slots in the log are decided except the last slot, which is currently being decided. One of Meerkat’s invariants is that if any two replicas decide on the value for a slot, those values are the same. In other words, no two replicas will ever disagree on the value of a decided slot (though one replica may think the last slot is empty while another does not). This property helps guarantee the desired properties we described in the previous section.</p><p>To decide on the value of the last (empty) slot in the log, Meerkat replicas run a distributed <i>consensus algorithm</i>. A consensus algorithm allows a set of machines communicating over a network to agree on a decided slot value. Our consensus algorithm works as long as a majority of replicas (more than half) are alive.</p><p>So if the log currently contains two entries, and a client submits <code>put k1 v11</code> to a replica, that replica triggers a consensus algorithm for slot 3. But another client might have submitted <code>put k1 v111</code> to a different replica for slot 3. The consensus algorithm ensures that only one such <i>proposal</i> for slot 3 wins out. Specifically, it ensures that at least a majority of replicas agree on the same proposal, <i>deciding </i>it for slot 3. The non-majority can <i>never</i> decide a different proposal, but might miss the fact that slot 3 has been decided at all. </p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/n4FcN0VCaSpM0jU1ptJJ2/77161cc5c179f10ff64a956ee10ab3b1/BLOG-3347_image5.png" />
          </figure><p>To see how this provides linearizability for our key-value store, consider a write followed by a read. One replica Z proposes <code>put k1 v11</code> and this proposal is decided at slot 3 by a majority of replicas, but NOT replica Y. Subsequently, a reader executes <code>get k1</code> on replica Y. Replica Y believes slot 3 is empty, so proposes <code>get k1</code> at slot 3. Critically, a majority of replicas will not agree to place that event at slot 3, because that slot has already been decided. They will force replica Y to decide (by receiving older decisions) <code>put k1 v11</code> in slot 3, and to propose <code>get k1</code> for slot 4, thus <i>linearizing </i>the read after the write in the log. (And if that replica can’t contact a majority, it will be unable to complete the read.)</p>
    <div>
      <h3>How Meerkat’s consensus algorithm provides higher availability than Raft</h3>
      <a href="#how-meerkats-consensus-algorithm-provides-higher-availability-than-raft">
        
      </a>
    </div>
    <p>Deciding on log entries requires a distributed consensus algorithm. But which one? All valid consensus algorithms would provide the required consistency and correctness guarantees, but not all provide the same availability guarantees. </p><p>Specifically, many algorithms that rely on authoritative leaders do not provide our desired availability guarantees, because they can become unavailable when a single machine experiences issues. Consider <a href="https://raft.github.io/"><u>Raft</u></a>, one of the most well-known and probably <i>the</i> most implemented consensus algorithm. Raft relies on an authoritative leader: the only replica in the cluster that can drive consensus. As a result, all writes get forwarded to the leader. This design choice helps make Raft “<a href="https://raft.github.io/raft.pdf"><u>understandable</u></a>” and, coupled with <a href="https://dl.acm.org/doi/epdf/10.1145/3786663"><u>leases</u></a>, can make leader-served reads automatically linearizable (since they’re guaranteed to be up-to-date). But it also adds a single point of (temporary) failure.</p><p>In general, there are two problems with authoritative leaders. First, if the leader goes down, the system becomes unavailable (all writes block) until a new leader is elected. This is unacceptable for Meerkat. Second, if the leader stays up but slows down, either because it is overloaded or there are network delays, then performance degrades. The leader is a bottleneck because there is no alternative way to perform writes. </p><p>The first problem is exacerbated in wide-area networks. Consider that when a leader goes down, most algorithms choose a new leader using <i>timeouts</i>: if a non-leader replica hasn’t heard from the leader in some amount of time, they propose themselves as the leader. At that point, the old leader has been deposed, and the system cannot accept writes until a new leader has been elected. The problem is that when the timeout is shorter than the network delay between the original leader and that replica, replicas will constantly be timing out and thus blocking writes. And when the timeout is too long, the system reacts slowly to a failed leader, during which writes are also blocked. Plus, if multiple replicas propose themselves as leader at the same time, their “campaigns” can interfere with each other, causing them to constantly re-propose themselves as leader — all the while blocking writes. We have seen these exact issues with Cloudflare’s systems that use Raft because our wide-area network delays can and do vary wildly, making tuning timeouts especially difficult.</p><p>We chose a different consensus algorithm for Meerkat, called <a href="https://bford.info/pub/os/quepaxa/quepaxa.pdf"><u>QuePaxa</u></a>, that aims to avoid the “tyranny of timeouts” imposed by protocols like Raft. QuePaxa is a subtle protocol, but here are the highlights. A client can contact <i>any </i>replica, and that replica can drive consensus for the latest slot. There is a leader, but it is not required — its only advantage is that it can drive consensus with fewer round trips (one) than other replicas (3+). Critically, clients are free to contact multiple replicas <i>concurrently </i>for the same proposal, to increase the chance of the proposal being successful. Concurrent proposals do not destructively interfere:  replicas <i>work together</i> to decide one of the proposed values.</p><p>In short, QuePaxa has three advantages over Raft for our purposes:</p><ol><li><p>Because there is no required leader, the system <i>never</i> becomes unavailable or degraded due to a single replica (the leader) being down, unavailable, or degraded. Clients can perform writes as long as they can contact some healthy replica (anywhere in the world). </p></li><li><p>Because there is no leader, there are no leader elections that degrade the system. And concurrent proposals made by different replicas <i>constructively </i>interfere, unlike Raft’s leadership elections. This is ideal for Cloudflare’s network, in which latencies can vary wildly.</p></li><li><p>QuePaxa was designed for a less reliable network environment (“asynchrony”), and for networks in which an imaginary adversary can launch targeted attacks on replica connections. The authors found that it maintains much higher (~10x) throughput than Raft and Multi-Paxos during such conditions. These conditions more accurately resemble our own network than the conditions other algorithms assume.</p></li></ol><p>We will save the full description of QuePaxa for another post. Major shoutout to the authors of the QuePaxa paper for being available for feedback and questions about their work.</p>
    <div>
      <h2>Assessing Meerkat’s performance </h2>
      <a href="#assessing-meerkats-performance">
        
      </a>
    </div>
    <p>Meerkat has limitations. It is <i>not</i> designed to create general-purpose data systems like databases.</p><p>All consensus algorithms come with a cost: lots of round-trips. QuePaxa in particular takes one to three round trips (usually, although it can take more) between the initial proposer and a majority of replicas to decide on a proposal and add an event to the log. The difference is with the leader. It takes one if the leader is proposing (+ an extra broadcast to notify replicas of the decision) and three if a non-leader is proposing (+ extra broadcast). If multiple replicas make proposals at the same time, it can take more. These communication costs point to the important performance limitation of consensus algorithms in general: proposal decision latency is proportional to the latency between some majority of replicas. So if your replicas are far from one another, latency will increase — there’s no getting around that.</p><p>At first glance, it seems Meerkat’s write <i>and</i> read latency will be quite poor. Especially if all writes and<i> </i>reads<i> </i>(for consistency) must go through the log, and thus require so many round trips.</p><p>But there are a few ways to squeeze better performance out of Meerkat: </p><ol><li><p>Because developers have control over where their replicas live, they can choose to move replicas closer together, reducing round-trip latency (only applicable for services that don’t need truly global distribution).</p></li><li><p>Writes can be batched. So if a replica receives 10 writes in a span of 10ms, it can place all of those in a single proposal, improving throughput<i>.</i></p></li><li><p>Not all reads must trigger a consensus round. If a developer is OK with reading <i>stale </i>(but never inconsistent) data, they can read from <i>any </i>replica’s local data.</p></li><li><p>Multiple operations can be bundled into a single consensus round. For instance, our key-value store supports compare-and-swap-style writes in which writes execute only if a value has not changed since it was read. (In fact, it supports general transactions.)</p></li></ol><p>Still, Meerkat’s fundamental latency limitations remain, especially when it is run at global scale, as it was designed to do. These limitations make it perfect, in the short term, for control plane information that is written infrequently but must remain consistent.</p>
    <div>
      <h2>What’s next</h2>
      <a href="#whats-next">
        
      </a>
    </div>
    <p>Meerkat is not deployed to production, but we have run multiple proofs-of-concept with up to 50 replicas distributed around the world, to great success. <b>Leaders in our proof-of-concept clusters constantly fail, and the cluster keeps operating with no increase in error-rate.</b></p><p>We have a lot more to say about Meerkat. Over the course of the next year we’ll be writing Meerkat posts that discuss how QuePaxa really works, how we’re formally verifying some of our Rust implementation, how bootstrapping and cluster management works, how we find optimal replica placement, how we use deterministic simulation testing to find bugs, and more. We’ll also be preparing a manuscript for peer-review!</p><p>Follow along on the Cloudflare Blog as Meerkat progresses, and check out more of our projects at <a href="https://research.cloudflare.com/"><u>Cloudflare Research</u></a>.</p> ]]></content:encoded>
            <category><![CDATA[Research]]></category>
            <category><![CDATA[Network]]></category>
            <category><![CDATA[Database]]></category>
            <category><![CDATA[Distributed Systems]]></category>
            <category><![CDATA[Meerkat]]></category>
            <guid isPermaLink="false">7zbgYCOZDfvIIsVrPpZ8xJ</guid>
            <dc:creator>James Larisch</dc:creator>
            <dc:creator>Bob Halley</dc:creator>
            <dc:creator>João Pedro Leite</dc:creator>
        </item>
        <item>
            <title><![CDATA[How we prevent conflicts in authoritative DNS configuration using formal verification]]></title>
            <link>https://blog.cloudflare.com/topaz-policy-engine-design/</link>
            <pubDate>Fri, 08 Nov 2024 14:00:00 GMT</pubDate>
            <description><![CDATA[ We describe how Cloudflare uses a custom Lisp-like programming language and formal verifier (written in Racket and Rosette) to prevent logical contradictions in our authoritative DNS nameserver’s behavior. ]]></description>
            <content:encoded><![CDATA[ <p>Over the last year, Cloudflare has begun formally verifying the correctness of our internal DNS addressing behavior — the logic that determines which IP address a DNS query receives when it hits our authoritative nameserver. This means that for every possible DNS query for a <a href="https://developers.cloudflare.com/dns/manage-dns-records/reference/proxied-dns-records/"><u>proxied</u></a> domain we could receive, we try to mathematically prove properties about our DNS addressing behavior, even when different systems (owned by different teams) at Cloudflare have contradictory views on which IP addresses should be returned.</p><p>To achieve this, we formally verify the programs — written in a custom <a href="https://en.wikipedia.org/wiki/Lisp_(programming_language)"><u>Lisp</u></a>-like programming language — that our nameserver executes when it receives a DNS query. These programs determine which IP addresses to return. Whenever an engineer changes one of these programs, we run all the programs through our custom model checker (written in <a href="https://racket-lang.org/"><u>Racket</u></a> + <a href="https://emina.github.io/rosette/"><u>Rosette</u></a>) to check for certain bugs (e.g., one program overshadowing another) before the programs are deployed.</p><p>Our formal verifier runs in production today, and is part of a larger addressing system called Topaz. In fact, it’s likely you’ve made a DNS query today that triggered a formally verified Topaz program.</p><p>This post is a technical description of how Topaz’s formal verification works. Besides being a valuable tool for Cloudflare engineers, Topaz is a real-world example of <a href="https://en.wikipedia.org/wiki/Formal_verification"><u>formal verification</u></a> applied to networked systems. We hope it inspires other network operators to incorporate formal methods, where appropriate, to help make the Internet more reliable for all.</p><p>Topaz’s full technical details have been peer-reviewed and published in <a href="https://conferences.sigcomm.org/sigcomm/2024/"><u>ACM SIGCOMM 2024</u></a>, with both a <a href="https://research.cloudflare.com/publications/Larisch2024/"><u>paper</u></a> and short <a href="https://www.youtube.com/watch?v=hW7RjXVx7_Q"><u>video</u></a> available online. </p>
    <div>
      <h2>Addressing: how IP addresses are chosen</h2>
      <a href="#addressing-how-ip-addresses-are-chosen">
        
      </a>
    </div>
    <p>When a DNS query for a customer’s proxied domain hits Cloudflare’s nameserver, the nameserver returns an IP address — but how does it decide which address to return?</p><p>Let’s make this more concrete. When a customer, say <code>example.com</code>, signs up for Cloudflare and <a href="https://developers.cloudflare.com/dns/manage-dns-records/reference/proxied-dns-records/"><u>proxies</u></a> their traffic through Cloudflare, it makes Cloudflare’s nameserver <i>authoritative</i> for their domain, which means our nameserver has the <i>authority </i>to respond to DNS queries for <code>example.com</code>. Later, when a client makes a DNS query for <code>example.com</code>, the client’s recursive DNS resolver (for example, <a href="https://www.cloudflare.com/learning/dns/what-is-1.1.1.1/"><u>1.1.1.1</u></a>) queries our nameserver for the authoritative response. Our nameserver returns <b><i>some</i></b><i> </i>Cloudflare IP address (of our choosing) to the resolver, which forwards that address to the client. The client then uses the IP address to connect to Cloudflare’s network, which is a global <a href="https://www.cloudflare.com/en-gb/learning/cdn/glossary/anycast-network/"><u>anycast</u></a> network — every data center advertises all of our addresses.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/72EmlVrMTMBMhxrhZ50YI9/54e08160ea98c55bc8e2703d7c85927b/image3.png" />
          </figure><p><sup>Clients query Cloudflare’s nameserver (via their resolver) for customer domains. The nameserver returns Cloudflare IP addresses, advertised by our entire global network, which the client uses to connect to the customer domain. Cloudflare may then connect to the origin server to fulfill the user’s HTTPS request.</sup></p><p>When the customer has <a href="https://developers.cloudflare.com/byoip/"><u>configured a static IP address</u></a> for their domain, our nameserver’s choice of IP address is simple: it simply returns that static address in response to queries made for that domain.</p><p>But for all other customer domains, our nameserver could respond with virtually any IP address that we own and operate. We may return the <i>same</i> address in response to queries for <i>different</i> domains, or <i>different</i> addresses in response to different queries for the <i>same</i> domain. We do this for resilience, but also because decoupling names and IP addresses <a href="https://blog.cloudflare.com/addressing-agility"><u>improves flexibility</u></a>.</p><p>With all that in mind, let’s return to our initial question: given a query for a proxied domain without a static IP, which IP address should be returned? The answer: <b>Cloudflare chooses IP addresses to meet various business objectives. </b>For instance, we may choose IPs to:</p><ul><li><p>Change the IP address of a domain that is under attack.</p></li><li><p>Direct fractions of traffic to specific IP addresses to test new features or services.</p></li><li><p><a href="https://blog.cloudflare.com/cloudflare-incident-on-september-17-2024/"><u>Remap or “renumber”</u></a> domain names to new IP address space.</p></li></ul>
    <div>
      <h2>Topaz executes DNS objectives</h2>
      <a href="#topaz-executes-dns-objectives">
        
      </a>
    </div>
    <p>To change authoritative nameserver behavior — how we choose IPs —  a Cloudflare engineer encodes their desired DNS business objective as a declarative Topaz program. Our nameserver stores the list of all such programs such that when it receives a DNS query for a proxied domain, it executes the list of programs in sequence until one returns an IP address. It then returns that IP to the resolver.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/3gyUw7j0GlTXj0Vm637aCW/9aa353d512151d5878998e199a538973/image1.png" />
          </figure><p><sup>Topaz receives DNS queries (metadata included) for proxied domains from Cloudflare’s nameserver. It executes a list of policies in sequence until a match is found. It returns the resulting IP address to the nameserver, which forwards it to the resolver.</sup></p><p>What do these programs look like?</p><p>Each Topaz program has three primary components:</p><ol><li><p><b>Match function: </b>A program’s match function specifies under which circumstances the program should execute. It takes as input DNS query metadata (e.g., datacenter information, account information) and outputs a boolean. If, given a DNS query, the match function returns <i>true</i>, the program’s response function is executed.</p></li><li><p><b>Response function</b>: A program’s response function specifies <i>which</i> IP addresses should be chosen. It also takes as input all the DNS query metadata, but outputs a 3-tuple (IPv4 addresses, IPv6 addresses, and TTL). When a program’s match function returns true, its corresponding response function is executed. The resulting IP addresses and TTL are returned to the resolver that made the query. </p></li><li><p><b>Configuration</b>: A program’s configuration is a set of variables that parameterize that program’s match and response function. The match and response functions reference variables in the corresponding configuration, thereby separating the macro-level behavior of a program (match/response functions) from its nitty-gritty details (specific IP addresses, names, etc.). This separation makes it easier to understand how a Topaz program behaves at a glance, without getting bogged down by specific function parameters.</p></li></ol><p>Let’s walk through an example Topaz program. The goal of this program is to give all queried domains whose metadata field “tag1” is equal to “orange” a particular IP address. The program looks like this:</p>
            <pre><code>- name: orange
  config: |
    (config
      ([desired_tag1 "orange"]
       [ipv4 (ipv4_address “192.0.2.3”)]
       [ipv6 (ipv6_address “2001:DB8:1:3”)]
       [t (ttl 300]))
  match: |
    (= query_domain_tag1 desired_tag1) 
  response: |
    (response (list ipv4) (list ipv6) t)</code></pre>
            <p>Before we walk through the program, note that the program’s configuration, match, and response function are YAML strings, but more specifically they are topaz-lang expressions. Topaz-lang is the <a href="https://en.wikipedia.org/wiki/Domain-specific_language"><u>domain-specific language (DSL)</u></a> we created specifically for expressing Topaz programs. It is based on <a href="https://www.scheme.org/"><u>Scheme</u></a>, but is much simpler. It is dynamically typed, it is not <a href="https://en.wikipedia.org/wiki/Turing_completeness"><u>Turing complete</u></a>, and every expression evaluates to exactly one value (though functions can throw errors). Operators cannot define functions within topaz-lang, they can only add new DSL functions by writing functions in the host language (Go). The DSL provides basic types (numbers, lists, maps) but also Topaz-specific types, like IPv4/IPv6 addresses and TTLs.</p><p>Let’s now examine this program in detail. </p><ul><li><p>The <code>config</code> is a set of four <i>bindings</i> from name to value. The first binds the string <code>”orange”</code> to the name <code>desired_tag1</code>. The second binds the IPv4 address <code>192.0.2.3</code> to the name <code>ipv4</code>. The third binds the IPv6 address <code>2001:DB8:1:3</code> to the name <code>ipv6</code>. And the fourth binds the TTL (for which we added a topaz-lang type) <code>300</code> (seconds) to the name <code>t</code>.</p></li><li><p>The <code>match</code> function is an expression that <i>must</i> evaluate to a boolean. It can reference configuration values (e.g., <code>desired_tag1</code>), and can also reference DNS query fields. All DNS query fields use the prefix <code>query_</code> and are brought into scope at evaluation time. This program’s match function checks whether <code>desired_tag1</code> is equal to the tag attached to the queried domain, <code>query_domain_tag1</code>. </p></li><li><p>The <code>response</code> function is an expression that evaluates to the special <code>response</code> type, which is really just a 3-tuple consisting of: a list of IPv4 addresses, a list of IPv6 addresses, and a TTL. This program’s response function simply returns the configured IPv4 address, IPv6 address, and TTL (seconds).</p></li></ul><p>Critically, <i>all</i> Topaz programs are encoded as YAML and live in the same version-controlled file. Imagine this program file contained only the <code>orange</code> program above, but now, a new team wants to add a new program, which checks whether the queried domain’s “tag1” field is equal to “orange” AND that the domain’s “tag2” field is equal to true:</p>
            <pre><code>- name: orange_and_true
  config: |
    (config
      ([desired_tag1 "orange"]
       [ipv4 (ipv4_address “192.0.2.2”)]
       [ipv6 (ipv6_address “2001:DB8:1:2”)]
       [t (ttl 300)]))
  match: |
    (and (= query_domain_tag1 desired_tag1)
         query_domain_tag2)
  response: |
    (response (list ipv4) (list ipv6) t)</code></pre>
            <p>This new team must place their new <code>orange_and_true</code> program either below or above the <code>orange</code> program in the file containing the list of Topaz programs. For instance, they could place <code>orange_and_true</code> after <code>orange</code>, like so:</p>
            <pre><code>- name: orange
  config: …
  match: …
  response: …
- name: orange_and_true
  config: …
  match: …
  response: …</code></pre>
            <p>Now let’s add a third, more interesting Topaz program. Say a Cloudflare team wants to test a modified version of our CDN’s HTTP server on a small percentage of domains, and only in a subset of Cloudflare’s data centers. Furthermore, they want to distribute these queries across a specific IP prefix such that queries for the same domain get the same IP. They write the following:</p>
            <pre><code>- name: purple
  config: |
    (config
      ([purple_datacenters (fetch_datacenters “purple”)]
       [percentage 10]
       [ipv4_prefix (ipv4_prefix “203.0.113.0/24”)]
       [ipv6_prefix (ipv6_prefix “2001:DB8:3::/48”)]))
  match: |
    (let ([rand (rand_gen (hash query_domain))])
      (and (member? purple_datacenters query_datacenter)
           (&lt; (random_number (range 0 99) rand) percentage)))
  response: |
    (let ([hashed_domain (hash query_domain)]
          [ipv4_address (select_from ipv4_prefix hashed_domain)]
          [ipv6_address (select_from ipv6_prefix hashed_domain)])
      (response (list ipv4_address) (list ipv6_address) (ttl 1)))</code></pre>
            <p>This Topaz program is significantly more complicated, so let’s walk through it.</p><p>Starting with configuration: </p><ul><li><p>The first configuration value, <code>purple_datacenters</code>, is bound to the expression <code>(fetch_datacenters “purple”)</code>, which is a function that retrieves all Cloudflare data centers tagged “purple” via an internal HTTP API. The result of this function call is a list of data centers. </p></li><li><p>The second configuration value, <code>percentage</code>, is a number representing the fraction of traffic we would like our program to act upon.</p></li><li><p>The third and fourth names are bound to IP prefixes, v4 and v6 respectively (note the <code>built-in ipv4_prefix</code> and <code>ipv6_prefix</code> types).</p></li></ul><p>The match function is also more complicated. First, note the <code>let</code> form — this lets operators define local variables. We define one local variable, a random number generator called <code>rand</code> seeded with the hash of the queried domain name. The match expression itself is a conjunction that checks two things. </p><ul><li><p>First, it checks whether the query landed in a data center tagged “purple”. </p></li><li><p>Second, it checks whether a random number between 0 and 99 (produced by a generator seeded by the domain name) is less than the configured percentage. By seeding the random number generator with the domain, the program ensures that 10% of <i>domains</i> trigger a match. If we had seeded the RNG with, say, the query ID, then queries for the same domain would behave differently.</p></li></ul><p>Together, the conjuncts guarantee that the match expression evaluates to true for 10% of domains queried in “purple” data centers.</p><p>Now let’s look at the response function. We define three local variables. The first is a hash of the domain. The second is an IPv4 address selected from the configured IPv4 prefix. <code>select_from</code> always chooses the same IP address given the same prefix and hash — this ensures that queries for a given domain always receive the same IP address (which makes it easier to correlate queries for a single domain), but that queries for different domains can receive different IP addresses within the configured prefix. The third local variable is an IPv6 address selected similarly. The response function returns these IP addresses and a TTL of value 1 (second).</p>
    <div>
      <h2>Topaz programs are executed on the hot path</h2>
      <a href="#topaz-programs-are-executed-on-the-hot-path">
        
      </a>
    </div>
    <p>Topaz’s control plane validates the list of programs and distributes them to our global nameserver instances. As we’ve seen, the list of programs reside in a single, version-controlled YAML file. When an operator changes this file (i.e., adds a program, removes a program, or modifies an existing program), Topaz’s control plane does the following things in order:</p><ul><li><p>First, it validates the programs, making sure there are no syntax errors. </p></li><li><p>Second, it “finalizes” each program’s configuration by evaluating every configuration binding and storing the result. (For instance, to finalize the <code>purple</code> program, it evaluates <code>fetch_datacenters</code>, storing the resulting list. This way our authoritative nameservers never need to retrieve external data.) </p></li><li><p>Third, it <i>verifies</i> the finalized programs, which we will explain below. </p></li><li><p>Finally, it distributes the finalized programs across our network.</p></li></ul><p>Topaz’s control plane distributes the programs to all servers globally by writing the list of programs to <a href="https://blog.cloudflare.com/introducing-quicksilver-configuration-distribution-at-internet-scale/"><u>QuickSilver</u></a>, our edge key-value store. The Topaz service on each server detects changes in Quicksilver and updates its program list.</p><p>When our nameserver service receives a DNS query, it augments the query with additional metadata (e.g., tags) and then forwards the query to the Topaz service (both services run on every Cloudflare server) via Inter-Process Communication (IPC). Topaz, upon receiving a DNS query from the nameserver, walks through its program list, executing each program’s match function (using the topaz-lang interpreter) with the DNS query in scope (with values prefixed with <code>query_</code>). It walks the list until a match function returns <code>true</code>. It then executes that program’s response function, and returns the resulting IP addresses and TTL to our nameserver. The nameserver packages these addresses and TTL in valid DNS format, and then returns them to the resolver. </p>
    <div>
      <h2>Topaz programs are formally verified</h2>
      <a href="#topaz-programs-are-formally-verified">
        
      </a>
    </div>
    <p>Before programs are distributed to our global network, they are formally verified. Each program is passed through our formal verification tool which throws an error if a program has a bug, or if two programs (e.g., the <code>orange_and_true</code> and <code>orange</code> programs) conflict with one another.</p><p>The Topaz formal verifier (<a href="https://en.wikipedia.org/wiki/Model_checking"><u>model-checker</u></a>) checks three properties.</p><p>First, it checks that each program is <i>satisfiable </i>— that there exists <i>some</i> DNS query that causes each program’s match function to return <code>true</code>. This property is useful for detecting internally-inconsistent programs that will simply never match. For instance, if a program’s match expression was <code>(and true false)</code>, there exists no query that will cause this to evaluate to true, so the verifier throws an error.</p><p>Second, it checks that each program is <i>reachable </i>— that there exists some DNS query that causes each program’s match function to return <code>true</code> <i>given all preceding programs.</i> This property is useful for detecting “dead” programs that are completely overshadowed by higher-priority programs. For instance, recall the ordering of the <code>orange</code> and <code>orange_and_true</code> programs:</p>
            <pre><code>- name: orange
  config: …
  match: (= query_domain_tag1 "orange")  
  response: …
- name: orange_and_true
  config: …
  match: (and (= query_domain_tag1 "orange") query_domain_tag2)
  response: …</code></pre>
            <p>The verifier would throw an error because the <code>orange_and_true</code> program is unreachable. For all DNS queries for which <code>query_domain_tag1</code> is ”orange”, regardless of <code>metadata2</code>, the <code>orange</code> program will <i>always</i> match, which means the <code>orange_and_true</code> program will <i>never</i> match. To resolve this error, we’d need to swap these two programs like we did above.</p><p>Finally, and most importantly, the verifier checks for program <i>conflicts</i>: queries that cause any two programs to both match. If such a query exists, it throws an error (and prints the relevant query), and the operators are forced to resolve the conflict by changing their programs. However, it only checks whether specific programs conflict — those that are explicitly marked <i>exclusive. </i>Operators mark their program as exclusive if they want to be sure that no other exclusive program could match on the same queries.</p><p>To see what conflict detection looks like, consider the corrected ordering of the <code>orange_and_true</code> and <code>orange</code> programs, but note that the two programs have now been marked exclusive:</p>
            <pre><code>- name: orange_and_true
  exclusive: true
  config: ...
  match: (and (= query_domain_tag1 "orange") query_domain_tag2)
  response: ...
- name: orange
  exclusive: true
  config: ...
  match: (= query_domain_tag1 "orange") 
  response: ...</code></pre>
            <p>After marking these two programs exclusive, the verifier will throw an error. Not only will it say that these two programs can contradict one another, but it will provide a sample query as proof:</p>
            <pre><code>Checking: no exclusive programs match the same queries: check FAILED!
Intersecting programs found:
programs "orange_and_true" and "orange" both match any query...
  to any domain...
    with tag1: "orange"
    with tag2: true
</code></pre>
            <p>The teams behind the <code>orange</code> and <code>orange_and_true</code> programs respectively <i>must</i> resolve this conflict before these programs are deployed, and can use the above query to help them do so. To resolve the conflict, the teams have a few options. The simplest option is to remove the exclusive setting from one program, and acknowledge that it is simply not possible for these programs to be <code>exclusive</code>. In that case, the order of the two programs matters (one must have higher priority). This is fine! Topaz allows developers to write certain programs that <i>absolutely cannot </i>overlap with other programs (using <code>exclusive</code>), but sometimes that is just not possible. And when it’s not, at least program priority is <i>explicit.</i></p><p><i>Note: in practice, we place all exclusive programs at the top of the program file. This makes it easier to reason about interactions between exclusive and non-exclusive programs.</i></p><p>In short, verification is powerful not only because it catches bugs (e.g., satisfiability and reachability), but it also highlights the consequences of program changes. It helps operators understand the impact of their changes by providing immediate feedback. If two programs conflict, operators are forced to resolve it before deployment, rather than after an incident.</p><p><b>Bonus: verification-powered diffs. </b>One of the newest features we’ve added to the verifier is one we call <i>semantic diffs</i>. It’s in early stages, but the key insight is that operators often just want to <i>understand</i> the impact of changes, even if these changes are deemed safe. To help operators, the verifier compares the old and new versions of the program file. Specifically, it looks for any query that matched program <i>X</i> in the old version, but matches a different program <i>Y</i> in the new version (or vice versa). For instance, if we changed <code>orange_and_true</code> thus:</p>
            <pre><code>- name: orange_and_true
  config: …
  match: (and (= query_domain_tag1 "orange") (not query_domain_tag2))
  response: …</code></pre>
            <p>Our verifier would emit:</p>
            <pre><code>Generating a report to help you understand your changes...
NOTE: the queries below (if any) are just examples. Other such queries may exist.

* program "orange_and_true" now MATCHES any query...
  to any domain...
    with tag1: "orange"
    with tag2: false</code></pre>
            <p>While not exhaustive, this information helps operators understand whether their changes are doing what they intend or not, <i>before</i> deployment. We look forward to expanding our verifier’s diff capabilities going forward.</p>
    <div>
      <h2>How Topaz’s verifier works, and its tradeoffs</h2>
      <a href="#how-topazs-verifier-works-and-its-tradeoffs">
        
      </a>
    </div>
    <p>How does the verifier work? At a high-level, the verifier checks that, for all possible DNS queries, the three properties outlined above are satisfied. A Satisfiability Modulo Theories (SMT) solver — which we explain below — makes this seemingly impossible operation feasible. (It doesn't literally loop over all DNS queries, but it is equivalent to doing so — it provides exhaustive proof.)</p><p>We implemented our formal verifier in <a href="https://emina.github.io/rosette/"><u>Rosette</u></a>, a solver-enhanced domain-specific language written in the <a href="https://racket-lang.org/"><u>Racket</u></a> programming language. Rosette makes writing a verifier more of an engineering exercise, rather than a formal logic test: if you can express the interpreter for your language in Racket/Rosette, you get verification “for free”, in some sense. We wrote a topaz-lang interpreter in Racket, then crafted our three properties using the Rosette DSL.</p><p>How does Rosette work? Rosette translates our desired properties into formulae in <a href="https://en.wikipedia.org/wiki/First-order_logic"><u>first-order logic</u></a>. At a high level, these formulae are like equations from algebra class in school, with “unknowns” or variables. For instance, when checking whether the orange program is reachable (with the <code>orange_and_true</code> program ordered before it), Rosette produces the formula <code>((NOT orange_and_true.match) AND orange.match)</code>. The “unknowns” here are the DNS query parameters that these match functions operate over, e.g., <code>query_domain_tag1</code>. To solve this formula, Rosette interfaces with an <a href="https://en.wikipedia.org/wiki/Satisfiability_modulo_theories"><u>SMT solver</u></a> (like <a href="https://github.com/Z3Prover/z3"><u>Z3</u></a>), which is specifically designed to solve these types of formulae by efficiently finding values to assign to the DNS query parameters that make the formulae true. Once the SMT solver finds satisfying values, Rosette translates them into a Racket data structure: in our case, a sample DNS query. In this example, once it finds a satisfying DNS query, it would report that the <code>orange</code> program is indeed reachable.</p><p>However, verification is not free. The primary cost is maintenance. The model checker’s interpreter (Racket) must be kept in lockstep with the main interpreter (Go). If they fall out-of-sync, the verifier loses the ability to accurately detect bugs. Furthermore, functions added to topaz-lang must be compatible with formal verification.</p><p>Also, not all functions are easily verifiable, which means we must restrict the kinds of functions that program authors can write. Rosette can only verify functions that operate over integers and bit-vectors. This means we only permit functions whose operations can be converted into operations over integers and bit-vectors. While this seems restrictive, it actually gets us pretty far. The main challenge is strings: Topaz does not support programs that, for example, manipulate or work with substrings of the queried domain name. However, it does support simple operations on closed-set strings. For instance, it supports checking if two domain names are equal, because we can convert all strings to a small set of values representable using integers (which are easily verifiable).</p><p>Fortunately, thanks to our design of Topaz programs, the verifier need not be compatible with all Topaz program code. The verifier only ever examines Topaz <i>match</i> functions, so only the functions specified in match functions need to be verification-compatible. We encountered other challenges when working to make our model accurate, like modeling randomness — if you are interested in the details, we encourage you to read the <a href="https://research.cloudflare.com/publications/Larisch2024/"><u>paper</u></a>.</p><p>Another potential cost is verification speed. We find that the verifier can ensure our existing seven programs satisfy all three properties within about six seconds, which is acceptable because verification happens only at build time. We verify programs centrally, before programs are deployed, and only when programs change. </p><p>We also ran microbenchmarks to determine how fast the verifier can check more programs — we found that, for instance, it would take the verifier about 300 seconds to verify 50 programs. While 300 seconds is still acceptable, we are looking into verifier optimizations that will reduce the time further.</p>
    <div>
      <h2>Bringing formal verification from research to production</h2>
      <a href="#bringing-formal-verification-from-research-to-production">
        
      </a>
    </div>
    <p>Topaz’s verifier began as a <a href="https://research.cloudflare.com/"><u>research</u></a> project, and has since been deployed to production. It formally verifies all changes made to the authoritative DNS behavior specified in Topaz.</p><p>For more in-depth information on Topaz, see both our research <a href="https://research.cloudflare.com/publications/Larisch2024/"><u>paper</u></a> published at SIGCOMM 2024 and the <a href="https://www.youtube.com/watch?v=hW7RjXVx7_Q"><u>recording</u></a> of the talk.</p><p>We thank our former intern, Tim Alberdingk-Thijm, for his invaluable work on Topaz’s verifier.</p> ]]></content:encoded>
            <category><![CDATA[DNS]]></category>
            <category><![CDATA[Research]]></category>
            <category><![CDATA[Addressing]]></category>
            <category><![CDATA[Formal Methods]]></category>
            <guid isPermaLink="false">5LVsblxj2Git54IRxadpyg</guid>
            <dc:creator>James Larisch</dc:creator>
            <dc:creator>Suleman Ahmad</dc:creator>
            <dc:creator>Marwan Fayed</dc:creator>
        </item>
    </channel>
</rss>