
<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>Wed, 08 Apr 2026 20:39:29 GMT</lastBuildDate>
        <item>
            <title><![CDATA[Email Routing subdomain support, new APIs and security protocols]]></title>
            <link>https://blog.cloudflare.com/email-routing-subdomains/</link>
            <pubDate>Thu, 26 Oct 2023 13:10:06 GMT</pubDate>
            <description><![CDATA[ It's been two years since we announced Email Routing, our solution to create custom email addresses for your domains and route incoming emails to your preferred mailbox. Since then, the team has worked hard to evolve the product and add more powerful features to meet our users' expectations.  ]]></description>
            <content:encoded><![CDATA[ <p></p><p>It's been two years since we announced Email Routing, our solution to create custom email addresses for your domains and route incoming emails to your preferred mailbox. Since then, the team has worked hard to evolve the product and add more powerful features to meet our users' expectations. Examples include <a href="/announcing-route-to-workers/">Route to Workers</a>, which allows you to <a href="https://developers.cloudflare.com/email-routing/email-workers/">process your Emails programmatically</a> using Workers scripts, <a href="/email-routing-leaves-beta/">Public APIs</a>, Audit Logs, or <a href="/dmarc-management/">DMARC Management</a>.</p><p>We also made significant progress in supporting more email security extensions and protocols, protecting our customers from unwanted traffic, and keeping our IP space reputation for email egress impeccable to maximize our deliverability rates to whatever inbox upstream provider you chose.</p><p>Since <a href="/email-routing-leaves-beta/">leaving beta</a>, Email Routing has grown into one of our most popular products; it’s used by more than one million different customer zones globally, and we forward around 20 million messages daily to every major email platform out there. Our product is mature, robust enough for general usage, and suitable for any production environment. And it keeps evolving: today, we announce three new features that will help make Email Routing more secure, flexible, and powerful than ever.</p>
    <div>
      <h2>New security protocols</h2>
      <a href="#new-security-protocols">
        
      </a>
    </div>
    <p>The SMTP email protocol has been around since the early 80s. Naturally, it wasn't designed with the best security practices and requirements in mind, at least not the ones that the Internet expects today. For that reason, several protocol revisions and extensions have been standardized and adopted by the community over the years. Cloudflare is known for being an early adopter of promising emerging technologies; Email Routing already <a href="https://developers.cloudflare.com/email-routing/postmaster/">supports</a> things like SPF, DKIM signatures, DMARC policy enforcement, TLS transport, STARTTLS, and IPv6 egress, to name a few. Today, we are introducing support for two new standards to help <a href="https://www.cloudflare.com/zero-trust/products/email-security/">increase email security</a> and improve deliverability to third-party upstream email providers.</p>
    <div>
      <h3>ARC</h3>
      <a href="#arc">
        
      </a>
    </div>
    <p><a href="https://arc-spec.org/">Authenticated Received Chain</a> (ARC) is an email authentication system designed to allow an intermediate email server (such as Email Routing) to preserve email authentication results. In other words, with ARC, we can securely preserve the results of validating sender authentication mechanisms like SPF and DKIM, which we support when the email is received, and transport that information to the upstream provider when we forward the message. ARC establishes a chain of trust with all the hops the message has passed through. So, if it was tampered with or changed in one of the hops, it is possible to see where by following that chain.</p><p>We began rolling out ARC support to Email Routing a few weeks ago. Here’s how it works:</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/67xk7IFzgYjOSwQEqUSbY/d48e08b735580f20fcafca988bb43748/pasted-image-0--1--2.png" />
            
            </figure><p>As you can see, <code>joe@example.com</code> sends an Email to <code>henry@domain.example</code>, an Email Routing address, which in turn is forwarded to the final address, <code>example@gmail.com</code>.</p><p>Email Routing will use <code>@example.com</code>’s DMARC policy to check the SPF and DKIM alignments (SPF, DKIM, and DMARC <a href="https://www.cloudflare.com/learning/email-security/dmarc-dkim-spf/">help authenticate</a> email senders by verifying that the emails came from the domain that they claim to be from.) It then stores this authentication result by adding a <code>Arc-Authentication-Results</code> header in the message:</p>
            <pre><code>ARC-Authentication-Results: i=1; mx.cloudflare.net; dkim=pass header.d=cloudflare.com header.s=example09082023 header.b=IRdayjbb; dmarc=pass header.from=example.com policy.dmarc=reject; spf=none (mx.cloudflare.net: no SPF records found for postmaster@example.com) smtp.helo=smtp.example.com; spf=pass (mx.cloudflare.net: domain of joe@example.com designates 2a00:1440:4824:20::32e as permitted sender) smtp.mailfrom=joe@example.com; arc=none smtp.remote-ip=2a00:1440:4824:20::32e</code></pre>
            <p>Then we take a snapshot of all the headers and the body of the original message, and we generate an <code>Arc-Message-Signature</code> header with a DKIM-like cryptographic signature (in fact ARC uses the same DKIM keys):</p>
            <pre><code>ARC-Message-Signature: i=1; a=rsa-sha256; s=2022; d=email.cloudflare.net; c=relaxed/relaxed; h=To:Date:Subject:From:reply-to:cc:resent-date:resent-from:resent-to :resent-cc:in-reply-to:references:list-id:list-help:list-unsubscribe :list-subscribe:list-post:list-owner:list-archive; t=1697709687; bh=sN/+...aNbf==;</code></pre>
            <p>Finally, before forwarding the message to <code>example@gmail.com</code>, Email Routing generates the <code>Arc-Seal</code> header, another DKIM-like signature, composed out of the <code>Arc-Authentication-Results</code> and <code>Arc-Message-Signature</code>, and cryptographically “seals” the message:</p>
            <pre><code>ARC-Seal: i=1; a=rsa-sha256; s=2022; d=email.cloudflare.net; cv=none; b=Lx35lY6..t4g==;</code></pre>
            <p>When Gmail receives the message from Email Routing, it not only normally authenticates the last hop domain.example domain (Email Routing uses <a href="https://developers.cloudflare.com/email-routing/postmaster/#sender-rewriting">SRS</a>), but it also checks the ARC seal header, which provides the authentication results of the original sender.</p><p>ARC increases the traceability of the message path through email intermediaries, allowing for more informed delivery decisions by those who receive emails as well as higher deliverability rates for those who transport them, like Email Routing. It has been adopted by all the major email providers like <a href="https://support.google.com/a/answer/175365?hl=en">Gmail</a> and Microsoft. You can read more about the ARC protocol in the <a href="https://datatracker.ietf.org/doc/html/rfc8617">RFC8617</a>.</p>
    <div>
      <h3>MTA-STS</h3>
      <a href="#mta-sts">
        
      </a>
    </div>
    <p>As we said earlier, SMTP is an old protocol. Initially Email communications were done in the clear, in plain-text and unencrypted. At some point in time in the late 90s, the email providers community standardized STARTTLS, also known as Opportunistic TLS. The <a href="https://datatracker.ietf.org/doc/html/rfc3207">STARTTLS extension</a> allowed a client in a SMTP session to upgrade to TLS encrypted communications.</p><p>While at the time this seemed like a step forward in the right direction, we later found out that because STARTTLS can start with an unencrypted plain-text connection, and that can be hijacked, the protocol is <a href="https://lwn.net/Articles/866481/">susceptible to man-in-the-middle attacks</a>.</p><p>A few years ago MTA Strict Transport Security (<a href="https://datatracker.ietf.org/doc/html/rfc8461">MTA-STS</a>) was introduced by email service providers including Microsoft, Google and Yahoo as a solution to protect against downgrade and man-in-the-middle attacks in SMTP sessions, as well as solving the lack of security-first communication standards in email.</p><p>Suppose that <code>example.com</code> uses Email Routing. Here’s how you can enable MTA-STS for it.</p><p>First, log in to the <a href="https://dash.cloudflare.com/">Cloudflare dashboard</a> and select your account and zone. Then go to <b>DNS</b> &gt; <b>Records</b> and create a new CNAME record with the name “<code>_mta-sts</code>” that points to Cloudflare’s record “<code>_mta-sts.mx.cloudflare.net</code>”. Make sure to disable the proxy mode.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/4czTYhSi9X5kPU3TZ0m861/e7d8162ff6f40494ce6d11fbf5899dad/pasted-image-0-2.png" />
            
            </figure><p>Confirm that the record was created:</p>
            <pre><code>$ dig txt _mta-sts.example.com
_mta-sts.example.com.	300	IN	CNAME	_mta-sts.mx.cloudflare.net.
_mta-sts.mx.cloudflare.net. 300	IN	TXT	"v=STSv1; id=20230615T153000;"</code></pre>
            <p>This tells the other end client that is trying to connect to us that we support MTA-STS.</p><p>Next you need an HTTPS endpoint at <code>mta-sts.example.com</code> to serve your policy file. This file defines the mail servers in the domain that use MTA-STS. The reason why HTTPS is used here instead of DNS is because not everyone uses DNSSEC yet, so we want to avoid another MITM attack vector.</p><p>To do this you need to deploy a very simple Worker that allows Email clients to pull Cloudflare’s Email Routing <a href="https://mta-sts.mx.cloudflare.net/.well-known/mta-sts.txt">policy</a> file using the <a href="https://en.wikipedia.org/wiki/Well-known_URI">“well-known” URI</a> convention. Go to your <b>Account</b> &gt; <b>Workers &amp; Pages</b> and press <b>Create Application</b>. Pick the “MTA-STS” template from the list.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/6BBFtG8hiHehJw74L2DbHX/d2afee1d61f266382082c08681e05e1a/pasted-image-0--2--2.png" />
            
            </figure><p>This Worker simply proxies <code>https://mta-sts.mx.cloudflare.net/.well-known/mta-sts.txt</code> to your own domain. After deploying it, go to the Worker configuration, then <b>Triggers</b> &gt; <b>Custom Domains</b> and <b>Add Custom Domain</b>.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/7MWHc7AuevDzxafJ0gfaFb/f659d8c0ae8c30f9a1457bc4b20f3535/customdomains.png" />
            
            </figure><p>You can then confirm that your policy file is working:</p>
            <pre><code>$ curl https://mta-sts.example.com/.well-known/mta-sts.txt
version: STSv1
mode: enforce
mx: *.mx.cloudflare.net
max_age: 86400</code></pre>
            <p>This says that we enforce MTA-STS. Capable email clients will only deliver email to this domain over a secure connection to the specified MX servers. If no secure connection can be established the email will not be delivered.</p><p>Email Routing also supports MTA-STS upstream, which greatly improves security when forwarding your Emails to service providers like <a href="https://support.google.com/a/answer/9261504?hl=en">Gmail</a> or <a href="https://learn.microsoft.com/en-us/purview/enhancing-mail-flow-with-mta-sts">Microsoft</a>, and others.</p><p>While enabling MTA-STS involves a few steps today, we plan to simplify things for you and automatically configure MTA-STS for your domains from the Email Routing dashboard as a future improvement.</p>
    <div>
      <h2>Sending emails and replies from Workers</h2>
      <a href="#sending-emails-and-replies-from-workers">
        
      </a>
    </div>
    <p>Last year we announced <a href="https://developers.cloudflare.com/email-routing/email-workers/">Email Workers</a>, allowing anyone using Email Routing to associate a Worker script to an Email address rule, and programmatically process their incoming emails in any way they want. <a href="https://developers.cloudflare.com/workers/">Workers</a> is our serverless compute platform, it provides hundreds of features and APIs, like <a href="https://developers.cloudflare.com/workers/databases/">databases</a> and <a href="https://developers.cloudflare.com/r2/api/workers/workers-api-reference/">storage</a>. Email Workers opened doors to a flood of use-cases and applications that weren’t possible before like implementing allow/block lists, advanced rules, notifications to messaging applications, honeypot aggregators and more.</p><p>Still, you could only act on the incoming email event. You could read and process the email message, you could even manipulate and create some headers, but you couldn’t rewrite the body of the message or create new emails from scratch.</p><p>Today we’re announcing two new powerful Email Workers APIs that will further enhance what you can do with Email Routing and Workers.</p>
    <div>
      <h3>Send emails from Workers</h3>
      <a href="#send-emails-from-workers">
        
      </a>
    </div>
    <p>Now you can send an email from any Worker, from scratch, whenever you want, not just when you receive incoming messages, to any email address verified on Email Routing under your account. Here are a few practical examples where sending email from Workers to your verified addresses can be helpful:</p><ul><li><p>Daily digests with the news from your favorite publications.</p></li><li><p>Alert messages whenever the weather conditions are adverse.</p></li><li><p>Automatic notifications when systems complete tasks.</p></li><li><p>Receive a message composed of the inputs of a form online on a contact page.</p></li></ul><p>Let's see a simple example of a Worker sending an email. First you need to create “<code>send_email</code>” bindings in your wrangler.toml configuration:</p>
            <pre><code>send_email = [
    {type = "send_email", name = "EMAIL_OUT"}
 ]</code></pre>
            <p>And then creating a new message and sending it in a Workers is as simple as:</p>
            <pre><code>import { EmailMessage } from "cloudflare:email";
import { createMimeMessage } from "mimetext";

export default {
 async fetch(request, env) {
   const msg = createMimeMessage();
   msg.setSender({ name: "Workers AI story", addr: "joe@example.com" });
   msg.setRecipient("mary@domain.example");
   msg.setSubject("An email generated in a worker");
   msg.addMessage({
       contentType: 'text/plain',
       data: `Congratulations, you just sent an email from a worker.`
   });

   var message = new EmailMessage(
     "joe@example.com",
     "mary@domain.example",
     msg.asRaw()
   );
   try {
     await env.EMAIL_OUT.send(message);
   } catch (e) {
     return new Response(e.message);
   }

   return new Response("email sent!");
 },
};</code></pre>
            <p>This example makes use of <a href="https://muratgozel.github.io/MIMEText/">mimetext</a>, an open-source raw email message generator.</p><p>Again, for security reasons, you can only send emails to the addresses for which you confirmed ownership in Email Routing under your Cloudflare account. If you’re looking for sending email campaigns or newsletters to destination addresses that you do not control or larger subscription groups, you should consider other options like our <a href="/sending-email-from-workers-with-mailchannels/">MailChannels integration</a>.</p><p>Since sending Emails from Workers is not tied to the EmailEvent, you can send them from any type of Worker, including <a href="https://developers.cloudflare.com/workers/configuration/cron-triggers/">Cron Triggers</a> and <a href="https://developers.cloudflare.com/durable-objects/">Durable Objects</a>, whenever you want, you control all the logic.</p>
    <div>
      <h3>Reply to emails</h3>
      <a href="#reply-to-emails">
        
      </a>
    </div>
    <p>One of our most-requested features has been to provide a way to programmatically respond to incoming emails. It has been possible to do this with Email Workers in a very limited capacity by returning a permanent SMTP error message — but this may or may not be visible to the end user depending on the client implementation.</p>
            <pre><code>export default {
  async email(message, env, ctx) {
      message.setReject("Address not allowed");
  }
}
</code></pre>
            <p>As of today, you can now truly reply to incoming emails with another new message and implement smart auto-responders programmatically, adding any content and context in the main body of the message. Think of a customer support email automatically generating a ticket and returning the link to the sender, an out-of-office reply with instructions when you're on vacation, or a detailed explanation of why you rejected an email. Here’s a code example:</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/4NgbXFwy3Xw0VHLemZ4smZ/682a581c21af850880fada5bbc17e99f/Screenshot-2023-10-26-at-12.05.33.png" />
            
            </figure><p>To mitigate security risks and abuse, replying to incoming emails has a few requirements:</p><ul><li><p>The incoming email has to have valid DMARC.</p></li><li><p>The email can only be replied to once.</p></li><li><p>The <code>In-Reply-To</code> header of the reply message must match the <code>Message-ID</code> of the incoming message.</p></li><li><p>The recipient of the reply must match the incoming sender.</p></li><li><p>The outgoing sender domain must match the same domain that received the email.</p></li></ul><p>If these and other internal conditions are not met, then <code>reply()</code> will fail with an exception, otherwise you can freely compose your reply message and send it back to the original sender.</p><p>For more information the documentation to these APIs is available in our <a href="https://developers.cloudflare.com/email-routing/email-workers/runtime-api/">Developer Docs</a>.</p>
    <div>
      <h2>Subdomains support</h2>
      <a href="#subdomains-support">
        
      </a>
    </div>
    <p>This is a big one.</p><p>Email Routing is a <a href="https://developers.cloudflare.com/fundamentals/concepts/accounts-and-zones/#zones">zone-level</a> feature. A zone has a <a href="https://www.cloudflare.com/learning/dns/top-level-domain/">top-level domain</a> (the same as the zone name) and it can have subdomains (managed under the DNS feature.) As an example, I can have the <code>example.com</code>  zone, and then the <code>mail.example.com</code> and <code>corp.example.com</code> subdomains under it. However, we can only use Email Routing with the top-level domain of the zone, <code>example.com</code> in this example. While this is fine for the vast majority of use cases, some customers — particularly bigger organizations with complex email requirements — have asked for more flexibility.</p><p>This changes today. Now you can use Email Routing with any subdomain of any zone in your account. To make this possible we redesigned the dashboard UI experience to make it easier to get you started and manage all your Email Routing domains and subdomains, rules and destination addresses in one single place. Let’s see how it works.</p><p>To add Email Routing features to a new subdomain, log in to the <a href="https://dash.cloudflare.com/">Cloudflare dashboard</a> and select your account and zone. Then go to <b>Email</b> &gt; <b>Email Routing</b> &gt; <b>Settings</b> and click “Add subdomain”.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/1WwS0LP1o8Ijlk0IzcqzCE/8528ed0f90a34029777d66b411d9e696/prev-req-rec.png" />
            
            </figure><p>Once the subdomain is added and the DNS records are configured, you can see it in the <b>Settings</b> list under the <b>Subdomains</b> section:</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/7gwBTgYQ36QxcvCGHfBqEd/450707647df2a8277eb0dc66e966088e/Domain.png" />
            
            </figure><p>Now you can go to <b>Email</b> &gt; <b>Email Routing</b> &gt; <b>Routing rules</b> and create new custom addresses that will show you the option of using either the top domain of the zone or any other configured subdomain.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/1KJ9AIM6MpcaYeV5IrVZQw/1e306de0bd46177eb2601e8e4e600930/Screenshot-2023-10-25-at-11.55.31-AM.png" />
            
            </figure><p>After the new custom address for the subdomain is created you can see it in the list with all the other addresses, and manage it from there.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/6vEJFroWoVivSr9n6SwPVl/28a4938f201e4153c964895d4687f1b2/custom-addresses.png" />
            
            </figure><p>It’s this easy.</p>
    <div>
      <h2>Final words</h2>
      <a href="#final-words">
        
      </a>
    </div>
    <p>We hope you enjoy the new features that we are announcing today. Still, we want to be clear: there are no changes in pricing, and Email Routing is still free for Cloudflare customers.</p><p>Ever since Email Routing was launched, we’ve been listening to customers’ feedback and trying to adjust our roadmap to both our requirements and their own ideas and requests. Email shouldn't be difficult; our goal is to listen, learn and keep improving the <a href="https://www.cloudflare.com/zero-trust/solutions/email-security-services/">email security service</a> with better, more powerful features.</p><p>You can find detailed information about the new features and more in our Email Routing <a href="https://developers.cloudflare.com/email-routing">Developer Docs</a>.</p><p>If you have any questions or feedback about Email Routing, please come see us in the <a href="https://community.cloudflare.com/new-topic?category=Feedback/Previews%20%26%20Betas&amp;tags=email">Cloudflare Community</a> and the <a href="https://discord.gg/cloudflaredev">Cloudflare Discord</a>.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/1OKqc3VieWKGRFBDtPU7io/18e8d2db548d341b0cb78a111aaa8480/Email-Routing-spot.png" />
            
            </figure><p></p> ]]></content:encoded>
            <category><![CDATA[Email Routing]]></category>
            <category><![CDATA[Email Workers]]></category>
            <category><![CDATA[Cloudflare Workers]]></category>
            <category><![CDATA[Developer Platform]]></category>
            <category><![CDATA[Developers]]></category>
            <guid isPermaLink="false">54W5SKQEt6kELFJMaWSRyh</guid>
            <dc:creator>Celso Martinho</dc:creator>
            <dc:creator>André Cruz</dc:creator>
            <dc:creator>Nelson Duarte</dc:creator>
        </item>
        <item>
            <title><![CDATA[You can now use WebGPU in Cloudflare Workers]]></title>
            <link>https://blog.cloudflare.com/webgpu-in-workers/</link>
            <pubDate>Wed, 27 Sep 2023 13:00:56 GMT</pubDate>
            <description><![CDATA[ Today, we are introducing WebGPU support to Cloudflare Workers. This blog will explain why it's important, why we did it, how you can use it, and what comes next ]]></description>
            <content:encoded><![CDATA[ <p></p><p>The browser as an app platform is real and stronger every day; long gone are the Browser Wars. Vendors and standard bodies have done amazingly well over the last years, working together and advancing web standards with new <a href="https://www.cloudflare.com/learning/security/api/what-is-an-api/">APIs</a> that allow developers to build fast and powerful applications, finally comparable to those we got used to seeing in the native OS' environment.</p><p>Today, browsers can render web pages and run code that interfaces with an <a href="https://developer.mozilla.org/en-US/docs/Web/API">extensive catalog of modern Web APIs</a>. Things like networking, rendering accelerated graphics, or even accessing low-level hardware features like USB devices are all now possible within the browser sandbox.</p><p>One of the most exciting new browser APIs that browser vendors have been rolling out over the last months is WebGPU, a modern, low-level GPU programming interface designed for high-performance 2D and 3D graphics and general purpose GPU compute.</p><p>Today, we are introducing <a href="https://developer.chrome.com/blog/webgpu-release/">WebGPU</a> support to Cloudflare Workers. This blog will explain why it's important, why we did it, how you can use it, and what comes next.</p>
    <div>
      <h3>The history of the GPU in the browser</h3>
      <a href="#the-history-of-the-gpu-in-the-browser">
        
      </a>
    </div>
    <p>To understand why WebGPU is a big deal, we must revisit history and see how browsers went from relying only on the CPU for everything in the early days to taking advantage of GPUs over the years.</p><p>In 2011, <a href="https://en.wikipedia.org/wiki/WebGL">WebGL 1</a>, a limited port of <a href="https://www.khronos.org/opengles/">OpenGL ES 2.0</a>, was introduced, providing an API for fast, accelerated 3D graphics in the browser for the first time. By then, this was somewhat of a revolution in enabling gaming and 3D visualizations in the browser. Some of the most popular 3D animation frameworks, like <a href="https://threejs.org/">Three.js</a>, launched in the same period. Who doesn't remember going to the (now defunct) <a href="https://en.wikipedia.org/wiki/Google_Chrome_Experiments">Google Chrome Experiments</a> page and spending hours in awe exploring the demos? Another option then was using the Flash Player, which was still dominant in the desktop environment, and their <a href="https://en.wikipedia.org/wiki/Stage3D">Stage 3D</a> API.</p><p>Later, in 2017, based on the learnings and shortcomings of its predecessor, WebGL 2 was a significant upgrade and brought more advanced GPU capabilities like shaders and more flexible textures and rendering.</p><p>WebGL, however, has proved to be a steep and complex learning curve for developers who want to take control of things, do low-level 3D graphics using the GPU, and not use 3rd party abstraction libraries.</p><p>Furthermore and more importantly, with the advent of <a href="https://www.cloudflare.com/learning/ai/what-is-machine-learning/">machine learning</a> and cryptography, we discovered that GPUs are great not only at drawing graphics but can be used for other applications that can take advantage of things like high-speed data or blazing-fast matrix multiplications, and one can use them to perform general computation. This became known as <a href="https://en.wikipedia.org/wiki/General-purpose_computing_on_graphics_processing_units">GPGPU</a>, short for general-purpose computing on graphics processing units.</p><p>With this in mind, in the native desktop and mobile operating system worlds, developers started using more advanced frameworks like <a href="https://en.wikipedia.org/wiki/CUDA">CUDA</a>, <a href="https://developer.apple.com/metal/">Metal</a>, <a href="https://en.wikipedia.org/wiki/DirectX#DirectX_12">DirectX 12</a>, or <a href="https://www.vulkan.org/learn#key-resources">Vulkan</a>. WebGL stayed behind. To fill this void and bring the browser up to date, in 2017, companies like Google, Apple, Intel, Microsoft, Kronos, and Mozilla created the <a href="https://www.w3.org/community/gpu/">GPU for Web Community Working Group</a> to collaboratively design the successor of WebGL and create the next modern 3D graphics and computation capabilities APIs for the Web.</p>
    <div>
      <h3>What is WebGPU</h3>
      <a href="#what-is-webgpu">
        
      </a>
    </div>
    <p>WebGPU was developed with the following advantages in mind:</p><ul><li><p><b>Lower Level Access</b> - WebGPU provides lower-level, direct access to the GPU vs. the high-level abstractions in WebGL. This enables more control over GPU resources.</p></li><li><p><b>Multi-Threading</b> - WebGPU can leverage multi-threaded rendering and compute, allowing improved CPU/GPU parallelism compared to WebGL, which relies on a single thread.</p></li><li><p><b>Compute Shaders</b> - First-class support for general-purpose compute shaders for GPGPU tasks, not just graphics. WebGL compute is limited.</p></li><li><p><b>Safety</b> - WebGPU ensures memory and GPU access safety, avoiding common WebGL pitfalls.</p></li><li><p><b>Portability</b> - WGSL shader language targets cross-API portability across GPU vendors vs. GLSL in WebGL.</p></li><li><p><b>Reduced Driver Overhead</b> - The lower level Vulkan/Metal/D3D12 basis improves overhead vs. OpenGL drivers in WebGL.</p></li><li><p><b>Pipeline State Objects</b> - Predefined pipeline configs avoid per-draw driver overhead in WebGL.</p></li><li><p><b>Memory Management</b> - Finer-grained buffer and resource management vs. WebGL.</p></li></ul><p>The “too long didn't read” version is that WebGPU provides lower-level control over the GPU hardware with reduced overhead. It's safer, has multi-threading, is focused on compute, not just graphics, and has portability advantages compared to WebGL.</p><p>If these aren't reasons enough to get excited, developers are also looking at WebGPU as an option for native platforms, not just the Web. For instance, you can use this <a href="https://github.com/webgpu-native/webgpu-headers/blob/main/webgpu.h">C API</a> that mimics the JavaScript specification. If you think about this and the power of WebAssembly, you can effectively have a truly platform-agnostic GPU hardware layer that you can use to <a href="https://developer.chrome.com/blog/webgpu-cross-platform/">develop</a> platforms for any operating system or browser.</p>
    <div>
      <h3>More than just graphics</h3>
      <a href="#more-than-just-graphics">
        
      </a>
    </div>
    <p>As explained above, besides being a graphics API, WebGPU makes it possible to perform tasks such as:</p><ul><li><p><b>Machine Learning</b> - Implement ML applications like <a href="https://www.cloudflare.com/learning/ai/what-is-neural-network/">neural networks</a> and computer vision algorithms using WebGPU compute shaders and matrices.</p></li><li><p><b>Scientific Computing</b> - Perform complex scientific computation like physics simulations and mathematical modeling using the GPU.</p></li><li><p><b>High Performance Computing</b> - Unlock breakthrough performance for parallel workloads by connecting WebGPU to languages like Rust, C/C++ via <a href="https://webassembly.org/">WebAssembly</a>.</p></li></ul><p><a href="https://gpuweb.github.io/gpuweb/wgsl/">WGSL</a>, the shader language for WebGPU, is what enables the general-purpose compute feature. Shaders, or more precisely, <a href="https://www.khronos.org/opengl/wiki/Compute_Shader">compute shaders</a>, have no user-defined inputs or outputs and are used for computing arbitrary information. Here are <a href="https://webgpufundamentals.org/webgpu/lessons/webgpu-compute-shaders.html">some examples</a> of simple WebGPU compute shaders if you want to learn more.</p>
    <div>
      <h3>WebGPU in Workers</h3>
      <a href="#webgpu-in-workers">
        
      </a>
    </div>
    <p>We've been watching WebGPU since the API was published. Its general-purpose compute features perfectly fit our Workers' ecosystem and capabilities and align well with our vision of providing our customers multiple compute and hardware options and bringing GPU workloads to our global network, close to clients.</p><p>Cloudflare also has a track record of pioneering support for emerging web standards on our network and services, accelerating their adoption for our customers. Examples of these are <a href="https://developers.cloudflare.com/workers/runtime-apis/web-crypto/">Web Crypto API</a>, <a href="/introducing-http2/">HTTP/2</a>, <a href="/http3-the-past-present-and-future/">HTTP/3</a>, <a href="/introducing-tls-1-3/">TLS 1.3</a>, or <a href="/early-hints/">Early hints</a>, but <a href="https://developers.cloudflare.com/workers/runtime-apis/">there are more</a>.</p><p>Bringing WebGPU to Workers was both natural and timely. Today, we are announcing that we have released a version of <a href="https://github.com/cloudflare/workerd">workerd</a>, the open-sourced JavaScript / Wasm runtime that powers Cloudflare Workers, with <a href="https://github.com/cloudflare/workerd/tree/main/src/workerd/api/gpu">WebGPU support</a>, that you can start playing and developing applications with, locally.</p><p>Starting today anyone can run this on their personal computer and experiment with WebGPU-enabled workers. Implementing local development first allows us to put this API in the hands of our customers and developers earlier and get feedback that will guide the development of this feature for production use.</p><p>But before we dig into code examples, let's explain how we built it.</p>
    <div>
      <h3>How we built WebGPU on top of Workers</h3>
      <a href="#how-we-built-webgpu-on-top-of-workers">
        
      </a>
    </div>
    
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/2rem1ZyAcVa3LO7ue6OTC7/debccf54201fe93a9221a6dd01bc5338/image2-22.png" />
            
            </figure><p>To implement the WebGPU API, we took advantage of <a href="https://dawn.googlesource.com/dawn/">Dawn</a>, an open-source library backed by Google, the same used in Chromium and Chrome, that provides applications with an implementation of the WebGPU standard. It also provides the <a href="https://github.com/webgpu-native/webgpu-headers/blob/main/webgpu.h">webgpu.h</a> headers file, the de facto reference for all the other implementations of the standard.</p><p>Dawn can interoperate with Linux, MacOS, and Windows GPUs by interfacing with each platform's native GPU frameworks. For example, when an application makes a WebGPU draw call, Dawn will convert that draw command into the equivalent Vulkan, Metal, or Direct3D 12 API call, depending on the platform.</p><p>From an application standpoint, Dawn handles the interactions with the underlying native graphics APIs that communicate directly with the GPU drivers. Dawn essentially acts as a middle layer that translates the WebGPU API calls into calls for the platform's native graphics API.</p><p>Cloudflare <a href="/workerd-open-source-workers-runtime/">workerd</a> is the underlying open-source runtime engine that executes Workers code. It shares most of its code with the same runtime that powers Cloudflare Workers' production environment but with some changes designed to make it more portable to other environments. We then have release cycles that aim to synchronize both codebases; more on that later. Workerd is also used with <a href="https://github.com/cloudflare/workers-sdk">wrangler</a>, our command-line tool for building and interacting with Cloudflare Workers, to support local development.</p><p>The WebGPU code that interfaces with the Dawn library can be found <a href="https://github.com/cloudflare/workerd/tree/main/src/workerd/api/gpu">here</a>, and can easily be enabled with a flag, checked <a href="https://github.com/cloudflare/workerd/blob/main/src/workerd/api/global-scope.c%2B%2B#L728">here</a>.</p>
            <pre><code>jsg::Ref&lt;api::gpu::GPU&gt; Navigator::getGPU(CompatibilityFlags::Reader flags) {
  // is this a durable object?
  KJ_IF_MAYBE (actor, IoContext::current().getActor()) {
    JSG_REQUIRE(actor-&gt;getPersistent() != nullptr, TypeError,
                "webgpu api is only available in Durable Objects (no storage)");
  } else {
    JSG_FAIL_REQUIRE(TypeError, "webgpu api is only available in Durable Objects");
  };

  JSG_REQUIRE(flags.getWebgpu(), TypeError, "webgpu needs the webgpu compatibility flag set");

  return jsg::alloc&lt;api::gpu::GPU&gt;();
}</code></pre>
            <p>The WebGPU API can only be accessed using <a href="https://developers.cloudflare.com/durable-objects/">Durable Objects</a>, which are essentially global singleton instances of Cloudflare Workers. There are two important reasons for this:</p><ul><li><p>WebGPU code typically wants to store the state between requests, for example, loading an <a href="https://www.cloudflare.com/learning/ai/what-is-artificial-intelligence/">AI model</a> into the GPU memory once and using it multiple times for inference.</p></li><li><p>Not all Cloudflare servers have GPUs yet, so although the worker that receives the request is typically the closest one available, the Durable Object that uses WebGPU will be instantiated where there are GPU resources available, which may not be on the same machine.</p></li></ul><p>Using Durable Objects instead of regular Workers allow us to address both of these issues.</p>
    <div>
      <h3>The WebGPU Hello World in Workers</h3>
      <a href="#the-webgpu-hello-world-in-workers">
        
      </a>
    </div>
    <p>Wrangler uses Miniflare 3, a <a href="/wrangler3/">fully-local simulator for Workers</a>, which in turn is powered by workerd. This means you can start experimenting and doing WebGPU code locally on your machine right now before we prepare things in our production environment.</p><p>Let’s get coding then.</p><p>Since Workers doesn't render graphics yet, we started with implementing the general-purpose GPU (GPGPU) APIs in the <a href="https://www.w3.org/TR/webgpu/">WebGPU specification</a>. In other words, we fully support the part of the API that the <a href="https://www.w3.org/TR/webgpu/#gpucomputepipeline">compute shaders and the compute pipeline</a> require, but we are not yet focused on fragment or vertex shaders used in rendering pipelines.</p><p>Here’s a typical “hello world” in WebGPU. This Durable Object script will output the name of the GPU device that workerd found in your machine to your console.</p>
            <pre><code>const adapter = await navigator.gpu.requestAdapter();
const adapterInfo = await adapter.requestAdapterInfo(["device"]);
console.log(adapterInfo.device);</code></pre>
            <p>A more interesting example, though, is a simple compute shader. In this case, we will fill a results buffer with an incrementing value taken from the iteration number via <code>global_invocation_id</code>.</p><p>For this, we need two buffers, one to store the results of the computations as they happen (<code>storageBuffer</code>) and another to copy the results at the end (<code>mappedBuffer</code>).</p><p>We then dispatch four workgroups, meaning that the increments can happen in parallel. This parallelism and programmability are two key reasons why compute shaders and GPUs provide an advantage for things like machine learning inference workloads. Other advantages are:</p><ul><li><p><b>Bandwidth</b> - GPUs have a very high memory bandwidth, up to 10-20x more than CPUs. This allows fast reading and writing of all the model parameters and data needed for inference.</p></li><li><p><b>Floating-point performance</b> - GPUs are optimized for high floating point operation throughput, which are used extensively in neural networks. They can deliver much higher <a href="https://www.tomshardware.com/reviews/gpu-hierarchy,4388.html">TFLOPs than CPUs</a>.</p></li></ul><p>Let’s look at the code:</p>
            <pre><code>// Create device and command encoder
const adapter = await navigator.gpu.requestAdapter();
const device = await adapter.requestDevice();
const encoder = device.createCommandEncoder();

// Storage buffer
const storageBuffer = device.createBuffer({
  size: 4 * Float32Array.BYTES_PER_ELEMENT, // 4 float32 values
  usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC,
});

// Mapped buffer
const mappedBuffer = device.createBuffer({
  size: 4 * Float32Array.BYTES_PER_ELEMENT,
  usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST,
});

// Create shader that writes incrementing numbers to storage buffer
const computeShaderCode = `
    @group(0) @binding(0)
    var&lt;storage, read_write&gt; result : array&lt;f32&gt;;

    @compute @workgroup_size(1)
    fn main(@builtin(global_invocation_id) gid : vec3&lt;u32&gt;) {
      result[gid.x] = f32(gid.x);
    }
`;

// Create compute pipeline
const computePipeline = device.createComputePipeline({
  layout: "auto",
  compute: {
    module: device.createShaderModule({ code: computeShaderCode }),
    entryPoint: "main",
  },
});

// Bind group
const bindGroup = device.createBindGroup({
  layout: computePipeline.getBindGroupLayout(0),
  entries: [{ binding: 0, resource: { buffer: storageBuffer } }],
});

// Dispatch compute work
const computePass = encoder.beginComputePass();
computePass.setPipeline(computePipeline);
computePass.setBindGroup(0, bindGroup);
computePass.dispatchWorkgroups(4);
computePass.end();

// Copy from storage to mapped buffer
encoder.copyBufferToBuffer(
  storageBuffer,
  0,
  mappedBuffer,
  0,
  4 * Float32Array.BYTES_PER_ELEMENT //mappedBuffer.size
);

// Submit and read back result
const gpuBuffer = encoder.finish();
device.queue.submit([gpuBuffer]);

await mappedBuffer.mapAsync(GPUMapMode.READ);
console.log(new Float32Array(mappedBuffer.getMappedRange()));
// [0, 1, 2, 3]</code></pre>
            <p>Now that we covered the basics of WebGPU and compute shaders, let's move to something more demanding. What if we could perform machine learning inference using Workers and GPUs?</p>
    <div>
      <h3>ONNX WebGPU demo</h3>
      <a href="#onnx-webgpu-demo">
        
      </a>
    </div>
    <p>The <a href="https://github.com/microsoft/onnxruntime">ONNX runtime</a> is a popular open-source cross-platform, high performance machine learning inferencing accelerator. <a href="https://github.com/webonnx/wonnx">Wonnx</a> is a GPU-accelerated version of the same engine, written in Rust, that can be compiled to WebAssembly and take advantage of WebGPU in the browser. We are going to run it in Workers using a combination of <a href="https://github.com/cloudflare/workers-rs">workers-rs</a>, our Rust bindings for Cloudflare Workers, and the workerd WebGPU APIs.</p><p>For this demo, we are using <a href="https://www.kdnuggets.com/2016/09/deep-learning-reading-group-squeezenet.html">SqueezeNet</a>. This small image classification model can run under lower resources but still achieves similar levels of accuracy on the <a href="https://en.wikipedia.org/wiki/ImageNet">ImageNet</a> image classification validation dataset as larger models like <a href="https://en.wikipedia.org/wiki/AlexNet">AlexNet</a>.</p><p>In essence, our worker will receive any uploaded image and attempt to classify it according to the 1000 ImageNet classes. Once ONNX runs the machine learning model using the GPU, it will return the list of classes with the highest probability scores. Let’s go step by step.</p><p>First we load the model from R2 into the GPU memory the first time the Durable Object is called:</p>
            <pre><code>#[durable_object]
pub struct Classifier {
    env: Env,
    session: Option&lt;wonnx::Session&gt;,
}

impl Classifier {
    async fn ensure_session(&amp;mut self) -&gt; Result&lt;()&gt; {
        match self.session {
            Some(_) =&gt; worker::console_log!("DO already has a session"),
            None =&gt; {
                // No session, so this should be the first request. In this case
                // we will fetch the model from R2, build a wonnx session, and
                // store it for subsequent requests.
                let model_bytes = fetch_model(&amp;self.env).await?;
                let session = wonnx::Session::from_bytes(&amp;model_bytes)
                    .await
                    .map_err(|err| err.to_string())?;
                worker::console_log!("session created in DO");
                self.session = Some(session);
            }
        };
        Ok(())
    }
}</code></pre>
            <p>This is only required once, when the Durable Object is instantiated. For subsequent requests, we retrieve the model input tensor, call the existing session for the inference, and return to the calling worker the result tensor converted to JSON:</p>
            <pre><code>        let request_data: ArrayBase&lt;OwnedRepr&lt;f32&gt;, Dim&lt;[usize; 4]&gt;&gt; =
            serde_json::from_str(&amp;req.text().await?)?;
        let mut input_data = HashMap::new();
        input_data.insert("data".to_string(), request_data.as_slice().unwrap().into());

        let result = self
            .session
            .as_ref()
            .unwrap() // we know the session exists
            .run(&amp;input_data)
            .await
            .map_err(|err| err.to_string())?;
...
        let probabilities: Vec&lt;f32&gt; = result
            .into_iter()
            .next()
            .ok_or("did not obtain a result tensor from session")?
            .1
            .try_into()
            .map_err(|err: TensorConversionError| err.to_string())?;

        let do_response = serde_json::to_string(&amp;probabilities)?;
        Response::ok(do_response)</code></pre>
            <p>On the Worker script itself, we load the uploaded image and pre-process it into a model input tensor:</p>
            <pre><code>    let image_file: worker::File = match req.form_data().await?.get("file") {
        Some(FormEntry::File(buf)) =&gt; buf,
        Some(_) =&gt; return Response::error("`file` part of POST form must be a file", 400),
        None =&gt; return Response::error("missing `file`", 400),
    };
    let image_content = image_file.bytes().await?;
    let image = load_image(&amp;image_content)?;</code></pre>
            <p>Finally, we call the GPU Durable Object, which runs the model and returns the most likely classes of our image:</p>
            <pre><code>    let probabilities = execute_gpu_do(image, stub).await?;
    let mut probabilities = probabilities.iter().enumerate().collect::&lt;Vec&lt;_&gt;&gt;();
    probabilities.sort_unstable_by(|a, b| b.1.partial_cmp(a.1).unwrap());
    Response::ok(LABELS[probabilities[0].0])</code></pre>
            <p>We packaged this demo in a public repository, so you can also run it. Make sure that you have a <a href="https://www.rust-lang.org/">Rust</a> compiler, <a href="https://nodejs.org/en">Node.js</a>, <a href="https://git-scm.com/">Git</a> and <a href="https://curl.se/">curl</a> installed, then clone the repository:</p>
            <pre><code>git clone https://github.com/cloudflare/workers-wonnx.git
cd workers-wonnx</code></pre>
            <p>Upload the model to the local R2 simulator:</p>
            <pre><code>npx wrangler@latest r2 object put model-bucket-dev/opt-squeeze.onnx --local --file models/opt-squeeze.onnx</code></pre>
            <p>And then run the Worker locally:</p>
            <pre><code>npx wrangler@latest dev</code></pre>
            <p>With the Worker running and waiting for requests you can then open another terminal window and upload one of the image examples in the same repository using curl:</p>
            <pre><code>&gt; curl -F "file=@images/pelican.jpeg" http://localhost:8787
n02051845 pelican</code></pre>
            <p>If everything goes according to plan the result of the curl command will be the most likely class of the image.</p>
    <div>
      <h3>Next steps and final words</h3>
      <a href="#next-steps-and-final-words">
        
      </a>
    </div>
    <p>Over the upcoming weeks, we will merge the workerd WebGPU code in the Cloudflare Workers production environment and make it available globally, on top of our growing GPU nodes fleet. We didn't do it earlier because that environment is subject to strict security and isolation requirements. For example, we can't break the <a href="https://developers.cloudflare.com/workers/learning/security-model/">security model</a> of our process sandbox and have V8 talking to the GPU hardware directly, that would be a problem; we must create a configuration where another process is closer to the GPU and use IPC (inter-process communication) to talk to it. Other things like managing resource allocation and billing are being sorted out.</p><p>For now, we wanted to get the good news out that we will support WebGPU in Cloudflare Workers and ensure that you can start playing and coding with it today and learn from it. WebGPU and general-purpose computing on GPUs is still in its early days. We presented a machine-learning demo, but we can imagine other applications taking advantage of this new feature, and we hope you can show us some of them.</p><p>As usual, you can talk to us on our <a href="https://discord.cloudflare.com/">Developers Discord</a> or the <a href="https://community.cloudflare.com/c/developers/39">Community forum</a>; the team will be listening. We are eager to hear from you and learn about what you're building.</p> ]]></content:encoded>
            <category><![CDATA[Birthday Week]]></category>
            <category><![CDATA[Cloudflare Workers]]></category>
            <category><![CDATA[Standards]]></category>
            <category><![CDATA[Developers]]></category>
            <category><![CDATA[Developer Platform]]></category>
            <guid isPermaLink="false">4osLizDbNHndEk9BG23KFi</guid>
            <dc:creator>André Cruz</dc:creator>
            <dc:creator>Celso Martinho</dc:creator>
        </item>
        <item>
            <title><![CDATA[How we built DMARC Management using Cloudflare Workers]]></title>
            <link>https://blog.cloudflare.com/how-we-built-dmarc-management/</link>
            <pubDate>Fri, 17 Mar 2023 13:00:00 GMT</pubDate>
            <description><![CDATA[ At Cloudflare, we use the Workers platform and our product stack to build new services. Read how we made the new DMARC Management solution entirely on top of our APIs.
 ]]></description>
            <content:encoded><![CDATA[ <p></p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/3P7EqcZydcPUKVhQNdVwkr/55e20a63d7ae1ce2ff638c2818d7da58/How-we-built-DMARC-Management.png" />
            
            </figure>
    <div>
      <h3>What are DMARC reports</h3>
      <a href="#what-are-dmarc-reports">
        
      </a>
    </div>
    <p><a href="https://www.cloudflare.com/en-gb/learning/dns/dns-records/dns-dmarc-record/">DMARC</a> stands for Domain-based Message Authentication, Reporting, and Conformance. It's an email authentication protocol that helps protect against email <a href="https://www.cloudflare.com/learning/access-management/phishing-attack/">phishing</a> and <a href="https://www.cloudflare.com/learning/email-security/what-is-email-spoofing/">spoofing</a>.</p><p>When an email is sent, DMARC allows the domain owner to set up a DNS record that specifies which authentication methods, such as <a href="https://www.cloudflare.com/learning/dns/dns-records/dns-spf-record/">SPF</a> (Sender Policy Framework) and <a href="https://www.cloudflare.com/learning/dns/dns-records/dns-dkim-record/">DKIM</a> (DomainKeys Identified Mail), are used to verify the email's authenticity. When the email fails these authentication checks DMARC instructs the recipient's email provider on how to handle the message, either by quarantining it or rejecting it outright.</p><p>DMARC has become increasingly important in today's Internet, where email phishing and spoofing attacks are becoming more sophisticated and prevalent. By implementing DMARC, domain owners can protect their brand and their customers from the negative impacts of these attacks, including loss of trust, reputation damage, and financial loss.</p><p>In addition to <a href="https://www.cloudflare.com/learning/dns/dns-records/protect-domains-without-email/">protecting</a> against phishing and spoofing attacks, DMARC also provides <a href="https://www.rfc-editor.org/rfc/rfc7489">reporting</a> capabilities. Domain owners can receive reports on email authentication activity, including which messages passed and failed DMARC checks, as well as where these messages originated from.</p><p>DMARC management involves the configuration and maintenance of DMARC policies for a domain. Effective DMARC management requires ongoing monitoring and analysis of email authentication activity, as well as the ability to make adjustments and updates to DMARC policies as needed.</p><p>Some key components of effective DMARC management include:</p><ul><li><p>Setting up DMARC policies: This involves configuring the domain's DMARC record to specify the appropriate authentication methods and policies for handling messages that fail authentication checks. Here’s what a DMARC DNS record looks like:</p></li></ul><p><code>v=DMARC1; p=reject; rua=mailto:dmarc@example.com</code></p><p>This specifies that we are going to use DMARC version 1, our policy is to reject emails if they fail the DMARC checks, and the email address to which providers should send DMARC reports.</p><ul><li><p>Monitoring email authentication activity: DMARC reports are an important tool for domain owners to ensure <a href="https://www.cloudflare.com/zero-trust/products/email-security/">email security</a> and deliverability, as well as compliance with industry standards and regulations. By regularly monitoring and analyzing DMARC reports, domain owners can <a href="https://www.cloudflare.com/learning/email-security/how-to-prevent-phishing/">identify email threats</a>, optimize email campaigns, and improve overall email authentication.</p></li><li><p>Making adjustments as needed: Based on analysis of DMARC reports, domain owners may need to make adjustments to DMARC policies or authentication methods to ensure that email messages are properly authenticated and protected from phishing and spoofing attacks.</p></li><li><p>Working with email providers and third-party vendors: Effective DMARC management may require collaboration with email providers and third-party vendors to ensure that DMARC policies are being properly implemented and enforced.</p></li></ul><p>Today we launched <a href="/dmarc-management">DMARC management</a>. This is how we built it.</p>
    <div>
      <h3>How we built it</h3>
      <a href="#how-we-built-it">
        
      </a>
    </div>
    <p>As a leading provider of cloud-based security and performance solutions, we at Cloudflare take a specific approach to test our products. We "dogfood" our own tools and services, which means we use them to run our business. This helps us identify any issues or bugs before they affect our customers.</p><p>We use our own products internally, such as <a href="https://workers.cloudflare.com/">Cloudflare Workers</a>, a serverless platform that allows developers to run their code on our global network. Since its launch in 2017, the Workers ecosystem has grown significantly. Today, there are thousands of developers building and deploying applications on the platform. The power of the Workers ecosystem lies in its ability to enable developers to build sophisticated applications that were previously impossible or impractical to run so close to clients. Workers can be used to build APIs, generate dynamic content, optimize images, perform real-time processing, and much more. The possibilities are virtually endless. We used Workers to power services like <a href="/technology-behind-radar2/">Radar 2.0</a>, or software packages like <a href="/welcome-to-wildebeest-the-fediverse-on-cloudflare/">Wildebeest</a>.</p><p>Recently our <a href="https://developers.cloudflare.com/email-routing/">Email Routing</a> product joined forces with Workers, enabling <a href="/announcing-route-to-workers/">processing incoming emails</a> via Workers scripts. As the <a href="https://developers.cloudflare.com/email-routing/email-workers/">documentation</a> states: “With Email Workers you can leverage the power of Cloudflare Workers to implement any logic you need to <a href="https://www.cloudflare.com/learning/email-security/what-is-email-routing/">process your emails</a> and create complex rules. These rules determine what happens when you receive an email.” Rules and verified addresses can all be configured via our <a href="https://developers.cloudflare.com/api/operations/email-routing-destination-addresses-list-destination-addresses">API</a>.</p><p>Here’s how a simple Email Worker looks like:</p>
            <pre><code>export default {
  async email(message, env, ctx) {
    const allowList = ["friend@example.com", "coworker@example.com"];
    if (allowList.indexOf(message.headers.get("from")) == -1) {
      message.setReject("Address not allowed");
    } else {
      await message.forward("inbox@corp");
    }
  }
}</code></pre>
            <p>Pretty straightforward, right?</p><p>With the ability to programmatically process incoming emails in place, it seemed like the perfect way to handle incoming DMARC report emails in a scalable and efficient manner, letting Email Routing and Workers do the heavy lifting of receiving an unbound number of emails from across the globe. A high level description of what we needed is:</p><ol><li><p>Receive email and extract report</p></li><li><p>Publish relevant details to analytics platform</p></li><li><p>Store the raw report</p></li></ol><p>Email Workers enable us to do #1 easily. We just need to create a worker with an email() handler. This handler will receive the <a href="https://www.rfc-editor.org/rfc/rfc5321">SMTP</a> envelope elements, a pre-parsed version of the email headers, and a stream to read the entire raw email.</p><p>For #2 we can also look into the Workers platform, and we will find the <a href="https://developers.cloudflare.com/analytics/analytics-engine/">Workers Analytics Engine</a>. We just need to define an appropriate schema, which depends both on what’s present in the reports and the queries we plan to do later. Afterwards we can query the data using either the <a href="https://developers.cloudflare.com/analytics/graphql-api/">GraphQL</a> or <a href="https://developers.cloudflare.com/analytics/analytics-engine/sql-api/">SQL</a> API.</p><p>For #3 we don’t need to look further than our <a href="https://www.cloudflare.com/developer-platform/products/r2/">R2 object storage</a>. It is <a href="https://developers.cloudflare.com/r2/examples/demo-worker/">trivial</a> to access R2 from a Worker. After extracting the reports from the email we will store them in R2 for posterity.</p><p>We built this as a managed service that you can enable on your zone, and added a dashboard interface for convenience, but in reality all the tools are available for you to deploy your own DMARC reports processor on top of Cloudflare Workers, in your own account, without having to worry about servers, scalability or performance.</p>
    <div>
      <h3>Architecture</h3>
      <a href="#architecture">
        
      </a>
    </div>
    
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/3d55aU3WlGrgQcuc1TKPAF/a02a34f819174b82e768b0aed5053708/Screenshot-2023-03-16-at-4.18.08-PM.png" />
            
            </figure><p><a href="https://developers.cloudflare.com/email-routing/email-workers/">Email Workers</a> is a feature of our Email Routing product. The Email Routing component runs in all our nodes, so any one of them is able to process incoming mail, which is important because we announce the Email ingress BGP prefix from all our datacenters. Sending emails to an Email Worker is as easy as setting a rule in the Email Routing dashboard.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/4tgjcPuXeJ3lJ9yLxQoPxz/7c9f34dd7ae03aeb2293e64df9ebf8e6/pasted-image-0--4--3.png" />
            
            </figure><p>When the Email Routing component receives an email that matches a rule to be delivered to a Worker, it will contact our internal version of the recently open-sourced <a href="https://github.com/cloudflare/workerd">workerd</a> runtime, which also runs on all nodes. The RPC schema that governs this interaction is defined in a <a href="https://github.com/capnproto/capnproto">Capnproto</a> schema, and allows the body of the email to be streamed to Edgeworker as it’s read. If the worker script decides to forward this email, Edgeworker will contact Email Routing using a capability sent in the original request.</p>
            <pre><code>jsg::Promise&lt;void&gt; ForwardableEmailMessage::forward(kj::String rcptTo, jsg::Optional&lt;jsg::Ref&lt;Headers&gt;&gt; maybeHeaders) {
  auto req = emailFwdr-&gt;forwardEmailRequest();
  req.setRcptTo(rcptTo);

  auto sendP = req.send().then(
      [](capnp::Response&lt;rpc::EmailMetadata::EmailFwdr::ForwardEmailResults&gt; res) mutable {
    auto result = res.getResponse().getResult();
    JSG_REQUIRE(result.isOk(), Error, result.getError());
  });
  auto&amp; context = IoContext::current();
  return context.awaitIo(kj::mv(sendP));
}
</code></pre>
            <p>In the context of DMARC reports this is how we handle the incoming emails:</p><ol><li><p>Fetch the recipient of the email being processed, this is the RUA that was used. RUA is a DMARC configuration parameter that indicates where aggregate DMARC processing feedback should be reported pertaining to a certain domain. This recipient can be found in the “to” attribute of the message.</p></li></ol>
            <pre><code>const ruaID = message.to</code></pre>
            <ol><li><p>Since we handle DMARC reports for an unbounded number of domains, we use Workers KV to store some information about each one and key this information on the RUA. This also lets us know if we should be receiving these reports.</p></li></ol>
            <pre><code>const accountInfoRaw = await env.KV_DMARC_REPORTS.get(dmarc:${ruaID})</code></pre>
            <ol><li><p>At this point, we want to read the entire email into an arrayBuffer in order to parse it. Depending on the size of the report we may run into the limits of the free Workers plan. If this happens, we recommend that you switch to the <a href="https://www.cloudflare.com/workers-unbound-beta/">Workers Unbound</a> resource model which does not have this issue.</p></li></ol>
            <pre><code>const rawEmail = new Response(message.raw)
const arrayBuffer = await rawEmail.arrayBuffer()</code></pre>
            <ol><li><p>Parsing the raw email involves, among other things, parsing its MIME parts. There are multiple libraries available that allow one to do this. For example, you could use <a href="https://www.npmjs.com/package/postal-mime">postal-mime</a>:</p></li></ol>
            <pre><code>const parser = new PostalMime.default()
const email = await parser.parse(arrayBuffer)</code></pre>
            <ol><li><p>Having parsed the email we now have access to its attachments. These attachments are the DMARC reports themselves and they can be compressed. The first thing we want to do is store them in their compressed form in <a href="https://developers.cloudflare.com/r2/data-access/workers-api/workers-api-usage/">R2</a> for long-term storage. They can be useful later on for re-processing or investigating interesting reports. Doing this is as simple as calling put() on the R2 binding. In order to facilitate retrieval later we recommend that you spread the report files across directories based on the current time.</p></li></ol>
            <pre><code>await env.R2_DMARC_REPORTS.put(
    `${date.getUTCFullYear()}/${date.getUTCMonth() + 1}/${attachment.filename}`,
    attachment.content
  )</code></pre>
            <ol><li><p>We now need to look into the attachment mime type. The raw form of DMARC reports is XML, but they can be compressed. In this case we need to decompress them first. DMARC reporter files can use multiple compression algorithms. We use the MIME type to know which one to use. For <a href="https://en.wikipedia.org/wiki/Zlib">Zlib</a> compressed reports <a href="https://www.npmjs.com/package/pako">pako</a> can be used while for ZIP compressed reports <a href="https://www.npmjs.com/package/unzipit">unzipit</a> is a good choice.</p></li><li><p>Having obtained the raw XML form of the report, <a href="https://www.npmjs.com/package/fast-xml-parser">fast-xml-parser</a> has worked well for us in parsing them. Here’s how the DMARC report XML looks:</p></li></ol>
            <pre><code>&lt;feedback&gt;
  &lt;report_metadata&gt;
    &lt;org_name&gt;example.com&lt;/org_name&gt;
    &lt;emaildmarc-reports@example.com&lt;/email&gt;
   &lt;extra_contact_info&gt;http://example.com/dmarc/support&lt;/extra_contact_info&gt;
    &lt;report_id&gt;9391651994964116463&lt;/report_id&gt;
    &lt;date_range&gt;
      &lt;begin&gt;1335521200&lt;/begin&gt;
      &lt;end&gt;1335652599&lt;/end&gt;
    &lt;/date_range&gt;
  &lt;/report_metadata&gt;
  &lt;policy_published&gt;
    &lt;domain&gt;business.example&lt;/domain&gt;
    &lt;adkim&gt;r&lt;/adkim&gt;
    &lt;aspf&gt;r&lt;/aspf&gt;
    &lt;p&gt;none&lt;/p&gt;
    &lt;sp&gt;none&lt;/sp&gt;
    &lt;pct&gt;100&lt;/pct&gt;
  &lt;/policy_published&gt;
  &lt;record&gt;
    &lt;row&gt;
      &lt;source_ip&gt;192.0.2.1&lt;/source_ip&gt;
      &lt;count&gt;2&lt;/count&gt;
      &lt;policy_evaluated&gt;
        &lt;disposition&gt;none&lt;/disposition&gt;
        &lt;dkim&gt;fail&lt;/dkim&gt;
        &lt;spf&gt;pass&lt;/spf&gt;
      &lt;/policy_evaluated&gt;
    &lt;/row&gt;
    &lt;identifiers&gt;
      &lt;header_from&gt;business.example&lt;/header_from&gt;
    &lt;/identifiers&gt;
    &lt;auth_results&gt;
      &lt;dkim&gt;
        &lt;domain&gt;business.example&lt;/domain&gt;
        &lt;result&gt;fail&lt;/result&gt;
        &lt;human_result&gt;&lt;/human_result&gt;
      &lt;/dkim&gt;
      &lt;spf&gt;
        &lt;domain&gt;business.example&lt;/domain&gt;
        &lt;result&gt;pass&lt;/result&gt;
      &lt;/spf&gt;
    &lt;/auth_results&gt;
  &lt;/record&gt;
&lt;/feedback&gt;</code></pre>
            <ol><li><p>We now have all the data in the report at our fingertips. What we do from here on depends a lot on how we want to present the data. For us, the goal was to display meaningful data extracted from them in our Dashboard. Therefore we needed an Analytics platform where we could push the enriched data. Enter, <a href="https://developers.cloudflare.com/analytics/analytics-engine/">Workers Analytics Engine</a>. The Analytics engine is perfect for this task since it allows us to <a href="https://developers.cloudflare.com/analytics/analytics-engine/get-started/#3-write-data-from-your-worker">send</a> data to it from a worker, and exposes a <a href="https://developers.cloudflare.com/analytics/graphql-api/">GraphQL API</a> to interact with the data afterwards. This is how we obtain the data to show in our dashboard.</p></li></ol><p>In the future, we are also considering integrating <a href="https://developers.cloudflare.com/queues/">Queues</a> in the workflow to asynchronously process the report and avoid waiting for the client to complete it.</p><p>We managed to implement this project end-to-end relying only on the Workers infrastructure, proving that it’s possible, and advantageous, to build non-trivial apps without having to worry about scalability, performance, storage and security issues.</p>
    <div>
      <h3>Open sourcing</h3>
      <a href="#open-sourcing">
        
      </a>
    </div>
    <p>As we mentioned before, we built a managed service that you can enable and use, and we will manage it for you. But, everything we did can also be deployed by you, in your account, so that you can manage your own DMARC reports. It’s easy, and free. To help you with that, we are releasing an open-source version of a Worker that processes DMARC reports in the way described above: <a href="https://github.com/cloudflare/dmarc-email-worker">https://github.com/cloudflare/dmarc-email-worker</a></p><p>If you don’t have a dashboard where to show the data, you can also <a href="https://developers.cloudflare.com/analytics/analytics-engine/worker-querying/">query</a> the Analytics Engine from a Worker. Or, if you want to store them in a relational database, then there’s <a href="https://developers.cloudflare.com/d1/platform/client-api/">D1</a> to the rescue. The possibilities are endless and we are excited to find out what you’ll build with these tools.</p><p>Please contribute, make your own, we’ll be listening.</p>
    <div>
      <h3>Final words</h3>
      <a href="#final-words">
        
      </a>
    </div>
    <p>We hope that this post has furthered your understanding of the Workers platform. Today Cloudflare takes advantage of this platform to build most of our services, and we think you should too.</p><p>Feel free to contribute to our open-source version and show us what you can do with it.</p><p>The Email Routing is also working on expanding the Email Workers API more functionally, but that deserves another blog soon.</p> ]]></content:encoded>
            <category><![CDATA[Security Week]]></category>
            <category><![CDATA[Email Security]]></category>
            <category><![CDATA[Security]]></category>
            <category><![CDATA[DMARC]]></category>
            <guid isPermaLink="false">HNhMxPSjzPXyTdtRLp51K</guid>
            <dc:creator>André Cruz</dc:creator>
            <dc:creator>Nelson Duarte</dc:creator>
        </item>
        <item>
            <title><![CDATA[Email Routing leaves Beta]]></title>
            <link>https://blog.cloudflare.com/email-routing-leaves-beta/</link>
            <pubDate>Tue, 25 Oct 2022 13:00:00 GMT</pubDate>
            <description><![CDATA[ Today Email Routing leaves Beta and an update on all the new things we've been adding to the service, including behind-the-scenes and not-so-visible improvements ]]></description>
            <content:encoded><![CDATA[ <p></p><p>Email Routing was <a href="/introducing-email-routing/">announced</a> during Birthday Week in 2021 and has been available for free to every Cloudflare customer since early this year. When we launched in beta, we set out to make a difference and provide the most <a href="/migrating-to-cloudflare-email-routing/">uncomplicated</a>, more powerful <a href="https://www.cloudflare.com/learning/email-security/what-is-email-routing/">email forwarding service</a> on the Internet for all our customers, for free.</p><p>We feel we've met and <a href="https://w3techs.com/technologies/details/em-cloudflare">surpassed</a> our goals for the first year. Cloudflare Email Routing is now one of our most popular features and a top leading email provider. We are processing email traffic for more than 550,000 inboxes and forwarding an average of two million messages daily, and still growing month to month.</p><p>In February, we also announced that we were <a href="/why-we-are-acquiring-area-1/">acquiring</a> Area1. Merging their team, products, and know-how with Cloudflare was a significant step in strengthening our <a href="https://www.cloudflare.com/zero-trust/products/email-security/">Email Security</a> capabilities.</p><p>All this is good, but what about more features, you ask?</p><p>The team has been working hard to enhance Email Routing over the last few months. <b>Today Email Routing leaves beta.</b></p><p>Also, we feel that this could be a good time to give you an update on all the new things we've been adding to the service, including behind-the-scenes and not-so-visible improvements.</p><p>Let’s get started.</p>
    <div>
      <h3>Public API and Terraform</h3>
      <a href="#public-api-and-terraform">
        
      </a>
    </div>
    <p>Cloudflare has a strong API-first philosophy. All of our services expose their primitives in our vast API catalog and gateway, which we then “dogfood” extensively. For instance, our customer's configuration dashboard is built entirely on top of these APIs.</p><p>The Email Routing APIs didn't quite make it to this catalog on day one and were kept private and undocumented for a while. This summer we made those APIs <a href="https://api.cloudflare.com/#email-routing-destination-addresses-properties">available</a> on the public Cloudflare API catalog. You can programmatically use them to manage your destination emails, rules, and other Email Routing settings. The methods' definitions and parameters are documented, and we provide <a href="https://curl.se/">curl</a> examples if you want to get your hands dirty quickly.</p><p>Even better, if you're an infrastructure as code type of user and use Terraform to configure your systems automatically, we have you covered too. The latest releases of <a href="https://registry.terraform.io/providers/cloudflare/cloudflare/">Cloudflare's Terraform provider</a> now <a href="https://github.com/cloudflare/terraform-provider-cloudflare/tree/master/internal/provider">incorporate</a> the Email Routing API resources, which you can use with <a href="https://www.terraform.io/language/syntax/configuration">HCL</a>.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/oPdbQSpCrGdInWwSmc3Gz/bfa929155775e78998b86f6149b6ed9d/image4-11.png" />
            
            </figure>
    <div>
      <h3>IPv6 egress</h3>
      <a href="#ipv6-egress">
        
      </a>
    </div>
    <p>IPv6 adoption is on a <a href="https://radar.cloudflare.com/reports/ipv6">sustained growth</a> path. Our latest IPv6 adoption report shows that we're nearing the 30% penetration figure globally, with some countries, where mobile usage is prevalent, exceeding the 50% mark. Cloudflare has offered full IPv6 support <a href="/introducing-cloudflares-automatic-ipv6-gatewa/">since 2011</a> as it aligns entirely with our mission to help build a better Internet.</p><p>We are IPv6-ready across the board in our network and our products, and Email Routing has had IPv6 ingress support since day one.</p>
            <pre><code>➜  ~ dig celso.io MX +noall +answer
celso.io.		300	IN	MX	91 isaac.mx.cloudflare.net.
celso.io.		300	IN	MX	2 linda.mx.cloudflare.net.
celso.io.		300	IN	MX	2 amir.mx.cloudflare.net.
➜  ~ dig linda.mx.cloudflare.net AAAA +noall +answer
linda.mx.cloudflare.net. 300	IN	AAAA	2606:4700:f5::b
linda.mx.cloudflare.net. 300	IN	AAAA	2606:4700:f5::c
linda.mx.cloudflare.net. 300	IN	AAAA	2606:4700:f5::d</code></pre>
            <p>More recently, we closed the loop and added egress IPv6 as well. Now we also use IPv6 when sending emails to upstream servers. If the MX server to which an email is being forwarded supports IPv6, then we will try to use it. <a href="https://en.wikipedia.org/wiki/Comparison_of_webmail_providers">Gmail</a> is one good example of a high traffic destination that has IPv6 MX records.</p>
            <pre><code>➜  ~ dig gmail.com MX +noall +answer
gmail.com.		3362	IN	MX	30 alt3.gmail-smtp-in.l.google.com.
gmail.com.		3362	IN	MX	5 gmail-smtp-in.l.google.com.
gmail.com.		3362	IN	MX	10 alt1.gmail-smtp-in.l.google.com.
gmail.com.		3362	IN	MX	20 alt2.gmail-smtp-in.l.google.com.
gmail.com.		3362	IN	MX	40 alt4.gmail-smtp-in.l.google.com.
➜  ~ dig gmail-smtp-in.l.google.com AAAA +noall +answer
gmail-smtp-in.l.google.com. 116	IN	AAAA	2a00:1450:400c:c03::1a</code></pre>
            <p>We’re happy to report that we’re now delivering most of our email to upstreams using IPv6.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/fpqLS2x7AzUJBHUfBd1Vw/65e0089ca141515c51b2ff2df5a4716e/image1-22.png" />
            
            </figure>
    <div>
      <h3>Observability</h3>
      <a href="#observability">
        
      </a>
    </div>
    <p>Email Routing is effectively another system that sits in the middle of the life of an email message. No one likes to navigate blindly, especially when using and depending on critical services like email, so it's our responsibility to provide as much observability as possible about what's going on when messages are transiting through our network.</p><p>End to end email deliverability is a complex topic and often challenging to troubleshoot due to the nature of the protocol and the number of systems and hops involved. We added two widgets, Analytics and Detailed Logs, which will hopefully provide the needed <a href="/email-routing-insights/">insights</a> and help increase visibility.</p><p>The Analytics section of Email Routing shows general statistics about the number of emails received during the selected timeframe, how they got handled to the upstream destination addresses, and a convenient time-series chart.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/5idsdXSP16hDLOxayDOGqi/6b62d3ce039cdd9d94abc0e69405594f/image5-4.png" />
            
            </figure><p>On the Activity Log, you can get detailed information about what happened to each individual message that was received and then delivered to the destination. That information includes the sender and the custom address used, the timestamp, and the delivery attempt result. It also has the details of our SPF, DMARC, and DKIM validations. We also provide filters to help you find what you're looking for in case your message volume is higher.</p><p>More recently, the Activity Log now also shows <a href="https://en.wikipedia.org/wiki/Bounce_message">bounces</a>. A bounce message happens when the upstream SMTP server accepts the delivery, but then, for any reason (exceeded quota, virus checks, forged messages, or other issues), the recipient inbox decides to reject it and return a new message back with an error to the latest <a href="https://en.wikipedia.org/wiki/Message_transfer_agent">MTA</a> in the chain, read from the <a href="https://www.rfc-editor.org/rfc/rfc5322.html#section-3.6.7">Return-Path</a> headers, which is us.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/7wyT0gd7l6GHjl7LVFjCUR/67628c835e2f8f76f0d6a16ef99011be/image8-4.png" />
            
            </figure>
    <div>
      <h3>Audit Logs</h3>
      <a href="#audit-logs">
        
      </a>
    </div>
    <p><a href="https://developers.cloudflare.com/fundamentals/account-and-billing/account-security/review-audit-logs/">Audit Logs</a> are available on all plan types and summarize the history of events, like login and logout actions, or zone configuration changes, made within your Cloudflare account. Accounts with multiple members or companies that must comply with regulatory obligations rely on Audit logs for tracking and evidence reasons.</p><p>Email Routing now integrates with Audit Logs and records all configuration changes, like adding a new address, changing a rule, or editing the catch-all address. You can find the Audit Logs on the dashboard under "Manage Account" or use our API to download the list.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/15USXKo9itSQkX8h8JS0kE/ea244f54b31e72c73be3416ee42ace4e/image6-7.png" />
            
            </figure>
    <div>
      <h3>Anti-spam</h3>
      <a href="#anti-spam">
        
      </a>
    </div>
    <p>Unsolicited and malicious messages plague the world of email and are a big problem for end users. They affect the user experience and efficiency of email, and often carry security risks that can lead to scams, identity theft, and manipulation.</p><p>Since day one, we have supported and validated <a href="https://www.cloudflare.com/learning/dns/dns-records/dns-spf-record/">SPF</a> (Sender Policy Framework) records,  <a href="https://www.cloudflare.com/learning/dns/dns-records/dns-dkim-record/">DKIM</a> (DomainKeys Identified Mail) signatures, and <a href="https://www.cloudflare.com/learning/dns/dns-records/dns-dmarc-record/">DMARC</a> (Domain-based Message Authentication) policies in incoming messages. These steps are important and mitigate some risks associated with authenticating the origin of an email from a specific legitimate domain, but they don't solve the problem completely. You can still have bad actors generating spam or <a href="https://www.cloudflare.com/learning/email-security/how-to-identify-a-phishing-email/">phishing</a> Attacks from other domains who ignore SPF or DKIM completely.</p><p>Anti-spam techniques today are often based on blocking emails whose origin (the IP address of the client trying to deliver the message) confidence score isn't great. This is commonly known in the industry as IP reputation. Other companies specialize in maintaining reputation lists for IPs and email domains, also known as <a href="https://en.wikipedia.org/wiki/Domain_Name_System-based_blocklist">RBL</a> lists, which are then shared across providers and used widely.</p><p>Simply put, an IP or a domain gets a bad reputation when it starts sending unsolicited or malicious emails. If your IP or domain has a bad reputation, you'll have a hard time delivering Emails from them to any major email provider. A bad reputation goes away when the IP or domain stops acting bad.</p><p>Cloudflare is a security company that knows a few things about IP <a href="https://developers.cloudflare.com/ruleset-engine/rules-language/fields/#field-cf-threat_score">threat scores</a> and reputation. Working with the Area1 team and learning from them, we added support to flag and block emails received from what we consider bad IPs at the SMTP level. Our approach uses a combination of heuristics and reputation databases, including some RBL lists, which we constantly update.</p><p>This measure benefits not only those customers that receive a lot of spam, who will now get another layer of <a href="https://www.cloudflare.com/learning/dns/dns-records/protect-domains-without-email/">protection</a> and filtering, but also everyone else using Email Routing. The reputation of our own IP space and forwarding domain, which we use to deliver messages to other email providers, will improve, and with it, so will our deliverability success rate.</p>
    <div>
      <h3>IDN support</h3>
      <a href="#idn-support">
        
      </a>
    </div>
    <p><a href="https://datatracker.ietf.org/doc/html/rfc5891">Internationalized domain names</a>, or IDNs for short, are domains that contain at least one non-ASCII character. To accommodate backward compatibility with older Internet protocols and applications, the IETF approved the IDNA protocol (Internationalized Domain Names in Applications), which was then adopted by <a href="https://chromium.googlesource.com/chromium/src/+/main/docs/idn.md">many browsers</a>, <a href="https://www.cloudflare.com/learning/dns/glossary/what-is-a-domain-name-registrar/">top-level domain registrars</a> and other service providers.</p><p>Cloudflare was <a href="/non-latinutf8-domains-now-fully-supported/">one of the first</a> platforms to adopt IDNs back in 2012.  Supporting internationalized domain names on email, though, is challenging. Email uses DNS, SMTP, and other standards (like TLS and DKIM signatures) stacked on top of each other. IDNA conversions need to work end to end, or something will break.</p><p>Email Routing didn’t support IDNs until now. Starting today, Email Routing can be used with IDNs and everything will work end to end as expected.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/2w5ochqMtILwVjTYbA0Pb/659ce2e551b0ea0e8540045dd48839e7/image3-10.png" />
            
            </figure>
    <div>
      <h3>8-bit MIME transport</h3>
      <a href="#8-bit-mime-transport">
        
      </a>
    </div>
    <p>The SMTP protocol supports extensions since the <a href="https://www.rfc-editor.org/rfc/rfc2821">RFC 2821</a> revision. When an email client connects to an SMTP server, it announces its capabilities on the EHLO command.</p>
            <pre><code>➜  ~ telnet linda.mx.cloudflare.net 25
Trying 162.159.205.24...
Connected to linda.mx.cloudflare.net.
Escape character is '^]'.
220 mx.cloudflare.net Cloudflare Email ESMTP Service ready
EHLO celso.io
250-mx.cloudflare.net greets celso.io
250-STARTTLS
250-8BITMIME
250 ENHANCEDSTATUSCODES</code></pre>
            <p>This tells our client that we support the <a href="https://www.ietf.org/rfc/rfc3207.txt">Secure SMTP</a> over TLS, <a href="https://www.rfc-editor.org/rfc/rfc2034.html">Enhanced Error Codes</a>, and the <a href="https://www.rfc-editor.org/rfc/rfc6152">8-bit MIME Transport</a>, our latest addition.</p><p>Most modern clients and servers support the 8BITMIME extension, making transmitting binary files easier and more efficient without additional conversions to and from 7-bit.</p><p>Email Routing now supports transmitting 8BITMIME SMTP messages end to end and handles DKIM signatures accordingly.</p>
    <div>
      <h3>Other fixes</h3>
      <a href="#other-fixes">
        
      </a>
    </div>
    <p>We’ve been making other smaller improvements to Email Routing too:</p><ul><li><p>We ported our SMTP server to use <a href="https://boringssl.googlesource.com/boringssl/">BoringSSL</a>, Cloudflare’s SSL/TLS <a href="/make-ssl-boring-again/">implementation of choice</a>, and now support more ciphers when clients connect to us using STARTTLS and when we connect to upstream servers.</p></li><li><p>We made a number of improvements when we added our own <a href="https://datatracker.ietf.org/doc/html/rfc6376">DKIM signatures</a> in the messages. We keep our <a href="https://www.rust-lang.org/">Rust</a> ?DKIM <a href="https://github.com/cloudflare/dkim">implementation</a> open source on GitHub, and we also <a href="https://github.com/lettre/lettre/commits/master">contribute</a> to <a href="https://github.com/lettre/lettre">Lettre</a>, a Rust mailer library that we use.</p></li><li><p>When a destination address domain has multiple MX records, we now try them all in their preference value order, as described in the <a href="https://datatracker.ietf.org/doc/html/rfc974">RFC</a>, until we get a good delivery, or we fail.</p></li></ul>
    <div>
      <h3>Route to Workers update</h3>
      <a href="#route-to-workers-update">
        
      </a>
    </div>
    <p>We announced <a href="/announcing-route-to-workers/">Route to Workers</a> in May this year. Route to Workers enables everyone to programmatically process their emails and use them as triggers for any other action. In other words, you can choose to process any incoming email with a Cloudflare Worker script and then implement any logic you wish before you deliver it to a destination address or drop it. Think about it as programmable email.</p><p>The good news, though, is that we're near completing the project. The APIs, the dashboard configuration screens, the SMTP service, and the necessary <a href="https://github.com/cloudflare/workerd/blob/main/src/workerd/io/worker-interface.capnp">Cap'n Proto interface</a> to Workers are mostly complete, and "all" we have left now is adding the Email Workers primitives to the runtime and testing the hell out of everything before we ship.</p><p>Thousands of users are waiting for Email Workers to start creating advanced email processing workflows, and we're excited about the possibilities this will open. We promise we're working hard to open the public beta as soon as possible.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/xNqMMpydzI8i8kWisriPT/d01bb6f42e9fe4bad92e8fec3796f6b4/image7-4.png" />
            
            </figure>
    <div>
      <h3>What’s next?</h3>
      <a href="#whats-next">
        
      </a>
    </div>
    <p>We keep looking at ways to improve email and will add more features and support to emerging protocols and extensions. Two examples are <a href="https://en.wikipedia.org/wiki/Authenticated_Received_Chain">ARC</a> (Authenticated Received Chain), a new signature-based authentication system designed with email forwarding services in mind, and <a href="https://datatracker.ietf.org/doc/html/rfc4952">EAI</a> (Email Address Internationalization), which we will be supporting soon.</p><p>In the meantime, you can start using Email Routing with your own domain if you haven't yet, it only <a href="/migrating-to-cloudflare-email-routing/">takes a few minutes</a> to set up, and it's free. Our <a href="https://developers.cloudflare.com/email-routing/">Developers Documentation page</a> has details on how to get started, troubleshooting, and technical information.</p><p>Ping us on our <a href="https://discord.com/invite/cloudflaredev">Discord server</a>, <a href="https://community.cloudflare.com/new-topic?category=Feedback/Previews%20%26%20Betas&amp;tags=email">community forum</a>, or <a href="https://twitter.com/cloudflare">Twitter</a> if you have suggestions or questions, the team is listening.</p> ]]></content:encoded>
            <category><![CDATA[Email]]></category>
            <category><![CDATA[Email Routing]]></category>
            <category><![CDATA[Email Workers]]></category>
            <guid isPermaLink="false">eSf4sLZdb5Gb9Y7mVbjOl</guid>
            <dc:creator>Celso Martinho</dc:creator>
            <dc:creator>André Cruz</dc:creator>
            <dc:creator>Nelson Duarte</dc:creator>
        </item>
        <item>
            <title><![CDATA[The benefits of serving stale DNS entries when using Consul]]></title>
            <link>https://blog.cloudflare.com/the-benefits-of-serving-stale-dns-entries-when-using-consul/</link>
            <pubDate>Mon, 08 Mar 2021 14:00:00 GMT</pubDate>
            <description><![CDATA[ We use Consul for service discovery, and we’ve deployed a cluster that spans several of our data centers. We were aware from the start that the DNS query latencies were not great from certain parts of the world that were furthest away from these data centers. ]]></description>
            <content:encoded><![CDATA[ 
    <div>
      <h2>Introduction</h2>
      <a href="#introduction">
        
      </a>
    </div>
    <p>We use <a href="https://www.consul.io/">Consul</a> for service discovery, and we’ve deployed a cluster that spans several of our data centers. This cluster exposes HTTP and DNS interfaces so that clients can query the Consul catalog and search for a particular service and the majority of the clients use DNS. We were aware from the start that the DNS query latencies were not great from certain parts of the world that were furthest away from these data centers. This, together with the fact that we use <a href="https://en.wikipedia.org/wiki/DNS_over_TLS">DNS over TLS</a>, results in some long latencies. The TTL of these names being low makes it even more impractical when resolving these names in the hot path.</p><p>The usual way to solve these issues is by caching values so that at least subsequent requests are resolved quickly, and this is exactly what our resolver of choice, <a href="https://www.nlnetlabs.nl/projects/unbound/about/">Unbound</a>, is configured to do. The problem remains when the cache expires. When it expires, the next client will have to wait while Unbound resolves the name using the network. To have a low recovery time in case some service needs to failover and clients need to use another address we use a small TTL (30 seconds) in our records and as such cache expirations happen often.</p><p>There are at least two strategies to deal with this: prefetching and stale cache.</p>
    <div>
      <h2>Prefetching</h2>
      <a href="#prefetching">
        
      </a>
    </div>
    <p>When prefetching is turned on, the server tries to refresh DNS records in the background before they expire. In practice, the way this works is: if an entry is served from cache and the TTL is less than 10% of the lifetime of the records, the server responds to the client, but in the background, it dispatches a job to refresh the record across the network. If it all goes as planned, the entry is refreshed in the cache before the TTL expires, and no client is ever left waiting for the network resolution.</p><p>This works very well for records that are accessed frequently, but poses issues in two situations: records that are accessed infrequently and records that have a small TTL. In both cases it is unlikely that a prefetch would be triggered, and so the cache expires and the next client will have to wait for the network resolution.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/6NcCnqSXw360PiHjNzeq2y/4e2305dada9a66609edb4fc4264d20e5/image1-6.png" />
            
            </figure><p>In our case, the records have a 30 second TTL, so frequently no requests come in during the yellow "prefetch" stage, which bumps the records out of the cache and causes the next client to have to wait for the slow network resolution. Prefetching was already turned on in our Unbound configuration but this still was not enough for our use case.</p>
    <div>
      <h2>Stale cache</h2>
      <a href="#stale-cache">
        
      </a>
    </div>
    <p>Another option is to serve stale records from the cache while we dispatch a job to refresh the record in the background without keeping the client waiting for the response. This would provide benefits in two different scenarios:</p><ul><li><p>General high latency - even after the TTL expires subsequent attempts will be returned from cache if the response takes too long to arrive.</p></li><li><p>Consul/network blips - if for some reason Consul is unavailable/unreachable, there is still a period of time that expired responses can be used instead of failing resolution.</p></li></ul><p>The first case is exactly our current scenario.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/4ixBixKeeIwU63f485mr2U/18844f87438211049fcfcf923e12c231/image7.png" />
            
            </figure><p>Being able to serve stale entries from the cache for a period after the TTL expires affords us some time in which we can return a result to a client either immediately or after some small amount of time has passed. The latter is the behaviour that we are looking for which also makes us compliant with <a href="https://www.rfc-editor.org/rfc/rfc8767">RFC 8767</a>, “Serving Stale Data to Improve DNS Resiliency”, published March 2020.</p>
    <div>
      <h3>Previous dealings with Unbound's stale cache</h3>
      <a href="#previous-dealings-with-unbounds-stale-cache">
        
      </a>
    </div>
    <p>Stale cache had been tried before at Cloudflare in 2017, and at that time, we encountered two problems:</p><p><b>1. Unbound could serve stale records from cache indefinitely</b>We could solve this through configuration that did not exist in 2017, but we had to test it.</p><p><b>2. Unbound serves negative/incomplete information from the expired cache</b>It was reported that Unbound would serve incomplete chains of responses to clients (for example, a CNAME record with no corresponding A/AAAA record).</p><p>This turned out to be unconfirmed but it is a failure mode that nevertheless we wanted to make sure Unbound could handle since it would be possible for parts of the response chain to be purged from the stale cache before others.</p>
    <div>
      <h3>Test Configuration</h3>
      <a href="#test-configuration">
        
      </a>
    </div>
    <p>As mentioned before we would need to test a current version of Unbound, come up with an up-to-date stale cache configuration, and test the scenarios that posed problems when we last tried to use it plus any others that we came up with. The Unbound version used for tests was 1.13.0 and the stale cache was configured thus:</p><ul><li><p><i>serve-expired: yes</i> - enable the stale cache.</p></li><li><p><i>serve-expired-ttl: 3600</i> - this limits the serving of expired resources to one hour after they expire and no response from upstream was obtained.</p></li><li><p><i>serve-expired-client-timeout: 500</i> - serve from stale cache only when upstream takes longer than 500ms to respond.</p></li></ul><p>The option <i>serve-expired-ttl-reset</i> was considered, which would cause the records to never expire, even with the upstream down indefinitely as long as there are regular requests for them, but it was deemed too dangerous since it could potentially last forever and applies to all entries.</p><p>Example consul zone: <b>service1.sd</b>.</p><p>While resolving this name we end up with:</p>
            <pre><code>;; ANSWER SECTION:
service1.sd. 120 IN CNAME service1.query.consul.
service1.query.consul. 30 IN A 192.0.2.1
 
;; Query time: 230 msec</code></pre>
            <p>If we dump Unbound's cache (unbound-control dump_cache), we can find the entries there:</p>
            <pre><code>service1.query.consul. 23  IN  A   192.0.2.1
service1.sd.   113 IN  CNAME   service1.query.consul.</code></pre>
            <p>If we wait for the TTL of these entries to expire and dump the cache again, we notice that they don't show up anymore:</p>
            <pre><code>➜ unbound-control dump_cache | grep -e query.consul -e service1
➜</code></pre>
            <p>But if we block access to the Consul resolver, we can see that still the entries are returned:</p>
            <pre><code>;; ANSWER SECTION:
service1.sd. 119 IN CNAME service1.query.consul.
service1.query.consul. 30 IN A 192.0.2.1
 
;; Query time: 503 msec</code></pre>
            <p>Notice that Unbound waited 500ms before returning a response, which was the value we configured. This also shows that the dump cache method does not return expired entries even though they are present. So far so good.</p>
    <div>
      <h5><b>Test - Unbound could serve stale records from cache indefinitely</b></h5>
      <a href="#test-unbound-could-serve-stale-records-from-cache-indefinitely">
        
      </a>
    </div>
    <p>After 3600 seconds pass we again try to resolve the same name:</p>
            <pre><code>➜ dig  service1.sd. @127.0.0.1
 
; &lt;&lt;&gt;&gt; DiG 9.10.6 &lt;&lt;&gt;&gt; service1.sd. @127.0.0.1
;; global options: +cmd
;; connection timed out; no servers could be reached</code></pre>
            <p>Dig gave up on waiting for a response from Unbound. So we see that after the configured time, Unbound will indeed stop serving the stale record, even if it has been requested recently. This is due to <i>serve-expired-ttl: 3600</i> and <i>serve-expired-ttl-reset</i> being set to "no" by default. These options were not available when we first tried Unbound’s stale cache.</p>
    <div>
      <h5><b>Test - Unbound serves negative/incomplete information from the expired cache</b></h5>
      <a href="#test-unbound-serves-negative-incomplete-information-from-the-expired-cache">
        
      </a>
    </div>
    <p>Since the record we are resolving includes a CNAME and an A record with different TTLs, we will try to see if Unbound can be made to return an incomplete chain (CNAME with no A record).</p><p>We start by having both entries in cache, and block connections to the Consul resolver. We then remove from the cache the A entry:</p>
            <pre><code>$ /usr/sbin/unbound-control flush_type service1.query.consul. A
ok</code></pre>
            <p>Now we attempt to query:</p>
            <pre><code>$ dig  service1.sd. @127.0.0.1
...
;; -&gt;&gt;HEADER&lt;&lt;- opcode: QUERY, status: SERVFAIL, id: 9532
;; Query time: 22 msec</code></pre>
            <p>Nothing was returned which is the result we were expecting.</p><p>Another scenario we wanted to test was to see if when <i>serve-expired-ttl</i> + A's TTL had passed, the CNAME would be returned on its own.</p>
            <pre><code>➜ dig  service1.sd. @127.0.0.1
;; -&gt;&gt;HEADER&lt;&lt;- opcode: QUERY, status: NOERROR, id: 48918
;; ANSWER SECTION:
service1.sd. 48 IN CNAME service1.query.consul.
service1.query.consul. 30 IN A 192.0.2.1
;; Query time: 430 msec
;; WHEN: Thu Dec 31 17:07:55 WET 2020
 
➜ dig  service1.sd. @127.0.0.1
;; -&gt;&gt;HEADER&lt;&lt;- opcode: QUERY, status: SERVFAIL, id: 42432
;; Query time: 509 msec
;; WHEN: Thu Dec 31 17:07:56 WET 2020</code></pre>
            <p>We lowered <i>serve-expired-ttl</i> to 40 to help testing the issue and we were able to see that as soon as the A entry was deemed not serve-able, the CNAME was also not returned, which is the behaviour that we want.</p><p>One last thing that we wanted to test was if the cache was really refreshed in the background while the stale entry was returned. <a href="https://developer.apple.com/download/more/?q=Additional%20Tools">Network Link Conditioner</a> was used to simulate high latency:</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/2LzVKSaIVIOaKuMuHbq9Fa/578619d90974ef897ae8e5d58ca9fe12/image16.png" />
            
            </figure>
            <pre><code>➜ dig service1.service.consul @CONSUL_ADDRESS
 
;; ANSWER SECTION:
service1.service.consul. 60 IN A 192.0.2.10
service1.service.consul. 60 IN A 192.0.2.12
 
;; Query time: 3053 msec</code></pre>
            <p>We can see that the artificial latency is working. We can now test if changes to the address are actually reflected on the consul record even though a stale record is served at first:</p>
            <pre><code>➜ dig service2.service.consul @127.0.0.1
...
;; ANSWER SECTION:
service2.service.consul. 30 IN A 192.0.2.100
 
;; Query time: 501 msec
;; SERVER: 127.0.0.1#53(127.0.0.1)
;; WHEN: Sat Jan 02 13:00:36 WET 2021
;; MSG SIZE  rcvd: 89
 
➜ dig service2.service.consul @127.0.0.1
...
;; ANSWER SECTION:
service2.service.consul. 58 IN A 192.0.2.200
 
;; Query time: 0 msec
;; SERVER: 127.0.0.1#53(127.0.0.1)
;; WHEN: Sat Jan 02 13:00:39 WET 2021
;; MSG SIZE  rcvd: 89</code></pre>
            <p>We can see that the first attempt was served from the stale cache, since the address was already changed on Consul to <i>192.0.2.200</i>, and the query time was 500ms. The next attempt was served from the fresh cache, hence the 0ms response time, and so we know that the cache was refreshed in the background.</p>
    <div>
      <h4><b>Test notes</b></h4>
      <a href="#test-notes">
        
      </a>
    </div>
    <p>All the test scenarios completed successfully, even though we did find an <a href="https://github.com/NLnetLabs/unbound/issues/394">issue</a> that has since been fixed upstream. We realised that the prefetching logic blocked the cache while it attempted to refresh the entry, which prevented stale entries from being served if the upstream was unresponsive. This fix is included in version 1.13.1.</p>
    <div>
      <h2>Deployment status</h2>
      <a href="#deployment-status">
        
      </a>
    </div>
    <p>This change has already been deployed to all of our data centers, and while the change addressed our original use case, there were some interesting side-effects as can be seen from the graphs below.</p>
    <div>
      <h3>Decreased in-flight requests</h3>
      <a href="#decreased-in-flight-requests">
        
      </a>
    </div>
    
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/5SEqkHk3xrVWSixpgdbdZa/7efa3f7c24e00772122d5b2e41efc353/image2-2.png" />
            
            </figure><p>The graph above is from the Toronto data center which received the change at approximately 14:00 UTC. We can observe an attenuated curve, which indicates that spikes in the number of in-flight requests were probably caused by slow/unresponsive upstreams which are now served a stale response after a short while.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/VG1RTOCYpAKbaGFm1wIzo/42901205b6abb8db0fb4d2056555eb42/image5-2.png" />
            
            </figure><p>The same graph from the Perth data center, which received the change on a later day.</p>
    <div>
      <h3>Decrease in P99 latency</h3>
      <a href="#decrease-in-p99-latency">
        
      </a>
    </div>
    
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/3sHaBdYvhCW0SdPdPLm03F/14d71c18a9e1e6ae6fa99f15e4edab38/image15.png" />
            
            </figure><p>The graph above is from the Lisbon data center_,_ which received the change at approximately 12:37 UTC. We can see that the long tail (P99) of request latencies decreases dramatically as we are now able to serve a response to clients in more situations without having them wait for a slow upstream network resolution that might not even succeed.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/4205xzfOSup4fdebmcwW5G/d1c7d53dfb89066328eb9db64d3abf95/image10-2.png" />
            
            </figure><p>The effect is even more dramatic on the Paris data center.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/61OoMQx3e3PjZVzVvQoSUe/c70e5b82c7349897757ec5658ef36d03/image8-2.png" />
            
            </figure><p>The decrease in P99 latencies directly correlates to the start of serving stale entries, even though there are not many of them. In the graph above we can observe the number of stale records served per second for the same data center, using the last 3 minutes of datapoints.</p>
    <div>
      <h3>Decreased general error rate</h3>
      <a href="#decreased-general-error-rate">
        
      </a>
    </div>
    
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/49nNtzXobSQtD0tgnddiJL/7505715c40eeb1d2767a6d7e33ea86bc/image12.png" />
            
            </figure><p>This graph is from our Perth data center, which received the change on February 8. This is explained by now being able to serve some requests to unresponsive/slow upstreams, which previously would have resulted in a failed request.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/6Pj6PgH2tsWfgRjFIOvZ7P/83f7dfaa8623dc3acc694ff04953bddc/image19.png" />
            
            </figure><p>Graph from the Vientiane data center, which received the change on February 1.</p>
    <div>
      <h3>Less jitter on the rate of load balancer queries by RRDNS</h3>
      <a href="#less-jitter-on-the-rate-of-load-balancer-queries-by-rrdns">
        
      </a>
    </div>
    
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/6LFLwd8W5wriMKlsZZdCXG/d06e8adc686b4aec2c4cca858beafd56/image11.png" />
            
            </figure><p>Another graph from the Perth data center, which received the change on February 8. An explanation for this change is that resolvers usually retry after a small amount of time when they don't get any response, and so by returning a stale response instead of keeping the client waiting (and retrying), we prevent a surge of new queries for expired and slow to resolve records.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/4Nlj6ruekdbxc5e86cDIVd/ef1c0ea3d9de713c60fd28ec247258a8/image18.png" />
            
            </figure><p>A graph from the Doha data center, which received the change on February 1.</p>
    <div>
      <h3>Miscellaneous Unbound metrics</h3>
      <a href="#miscellaneous-unbound-metrics">
        
      </a>
    </div>
    <p>This change also had a positive effect on several Unbound-specific metrics, below are several graphs from the Perth data center that received the change on February 8.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/6tvgfdPirOIApYCRxmzujb/945dea0b77381302fd9a931ccd8389ee/image6-2.png" />
            
            </figure>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/1TnCvyvE1SxFM02ulD7wOA/dbc2b15548cf92494dcc439d17d2f2ab/image13.png" />
            
            </figure>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/1KnVnBNkHrtUnkuC4X4rlN/6dbafa822edc14fbb03c077636e59b7c/image14.png" />
            
            </figure>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/1K0X5VnGlDWyfj4GXhHwKR/f676986d1c901998425a855e10ac4a2e/image4-4.png" />
            
            </figure>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/78YTgVILYTXNqtkZCgPZhu/5a6aeb35e06bb620000291a16bf69412/image9-1.png" />
            
            </figure>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/3lbt8d6fKHtHIi84k0SnyW/f2cd288ec63f34e2be4c5f938b658028/image17.png" />
            
            </figure><p>How it started vs how it's going</p>
    <div>
      <h3>Further improvements</h3>
      <a href="#further-improvements">
        
      </a>
    </div>
    <p>In the future, we would like to tweak the <i>serve-expired-client-timeout</i> value, which is currently set to 500ms. It was chosen conservatively, at a time when we were cautiously experimenting with a feature that had been problematic in the past, and our gut feeling is that it can be at least halved without causing issues to further improve the client’s experience. There is also the possibility of using different values per data center, for example picking up the P90 resolve latency value which differs per datacenter, and this is something that we want to explore further.</p>
    <div>
      <h2>Summary</h2>
      <a href="#summary">
        
      </a>
    </div>
    <p>We are enabling Unbound to return expired entries from the cache whenever an upstream takes longer than 500ms to respond, while refreshing the cache in the background, unless 3600 seconds have elapsed from the record's original TTL.</p> ]]></content:encoded>
            <category><![CDATA[DNS]]></category>
            <category><![CDATA[Cache]]></category>
            <guid isPermaLink="false">64s0ckd7FF53Lw8Q48zi1h</guid>
            <dc:creator>André Cruz</dc:creator>
        </item>
    </channel>
</rss>