<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
  <title>Brody Franks</title>
  <link rel="self" href="https://franks.id.au/atom.xml"/>
  <link href="https://franks.id.au/"/>
  <id>https://franks.id.au/atom.xml</id>
  <updated>2026-10-05T07:00:00.000Z</updated>
  <author><name>Brody Franks</name></author>
  
  <entry>
    <title>A deep dive into Tailscale&#39;s DERP protocol</title>
    <link href="https://franks.id.au/tailscale-derp/"/>
    <id>https://franks.id.au/tailscale-derp/</id>
    <published>2026-10-05T07:00:00.000Z</published>
    <updated>2026-10-05T07:00:00.000Z</updated>
    <content type="html">&lt;p&gt;A key component of Tailscale is its fleet of relay servers. These servers run a custom protocol called DERP (Designated Encrypted Relay for Packets), which lets nodes exchange endpoint information and fall back to a relayed connection when a direct connection is not possible. The servers route packets by WireGuard public key. A client connects, proves it owns the matching private key, and keeps the connection open. Other clients can then send packets to that client through the relay by addressing them to its public key.&lt;/p&gt;
&lt;p&gt;Knowing the WireGuard public key of the recipient and the DERP region they are connected to is enough to address traffic to them through the relay. Public DERP servers are rate-limited in practice, and a node drops WireGuard packets from unknown peers. Tailscale’s control plane distributes nodes’ public keys to peers. These public keys are intended to remain encrypted on the wire.&lt;/p&gt;
&lt;p&gt;Below, I’ll show how to use DERP to set up bidirectional communication between two clients. We’ll do this in about 40 lines of Python, using PyNaCl as the only third-party dependency.&lt;/p&gt;
&lt;h2 id=&quot;framing&quot; tabindex=&quot;-1&quot;&gt;Framing&lt;/h2&gt;
&lt;p&gt;DERP messages consist of a type, length, and message body.&lt;/p&gt;
&lt;pre class=&quot;language-python&quot;&gt;&lt;code class=&quot;language-python&quot;&gt;MAGIC &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;DERP🔑&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;encode&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
SERVER_KEY&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; CLIENT_INFO&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; SEND_PACKET&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; RECV_PACKET &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;


&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;read_frame&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;f&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    typ&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; n &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; struct&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;unpack&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&gt;BI&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; f&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;read&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; typ&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; f&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;read&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;n&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;


&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;write_frame&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;sock&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; typ&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; body&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    sock&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;sendall&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;struct&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;pack&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&gt;BI&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; typ&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;body&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; body&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;connecting&quot; tabindex=&quot;-1&quot;&gt;Connecting&lt;/h2&gt;
&lt;p&gt;The connection starts with an ordinary HTTPS request and is upgraded to DERP.&lt;/p&gt;
&lt;pre class=&quot;language-python&quot;&gt;&lt;code class=&quot;language-python&quot;&gt;url &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; urlsplit&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;sys&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;argv&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
sock &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; socket&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;create_connection&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;hostname&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; url&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;port &lt;span class=&quot;token keyword&quot;&gt;or&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;443&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
sock &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; ssl&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;create_default_context&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;wrap_socket&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;sock&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; server_hostname&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;hostname&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
sock&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;sendall&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token string-interpolation&quot;&gt;&lt;span class=&quot;token string&quot;&gt;f&quot;GET &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;path&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt; HTTP/1.1&#92;r&#92;nHost: &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;hostname&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#92;r&#92;n&quot;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token string&quot;&gt;&quot;Connection: Upgrade&#92;r&#92;nUpgrade: DERP&#92;r&#92;n&#92;r&#92;n&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;encode&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
f &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; sock&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;makefile&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;rb&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;assert&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;b&quot; 101 &quot;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; f&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;readline&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;server refused the DERP upgrade&quot;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;while&lt;/span&gt; f&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;readline&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;b&quot;&#92;r&#92;n&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;b&quot;&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;pass&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;handshake&quot; tabindex=&quot;-1&quot;&gt;Handshake&lt;/h2&gt;
&lt;p&gt;The client and server exchange public keys. The server announces its key, and the client answers with its own key and a NaCl box, sealed to the server, containing the client’s protocol version.&lt;/p&gt;
&lt;pre class=&quot;language-python&quot;&gt;&lt;code class=&quot;language-python&quot;&gt;typ&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; body &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; read_frame&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;f&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;assert&lt;/span&gt; typ &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; SERVER_KEY &lt;span class=&quot;token keyword&quot;&gt;and&lt;/span&gt; body&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;startswith&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;MAGIC&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
server &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; PublicKey&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;body&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;MAGIC&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;MAGIC&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
me &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; PrivateKey&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;generate&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
info &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Box&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;me&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; server&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;encrypt&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;b&#39;{&quot;version&quot;:2}&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
write_frame&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;sock&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; CLIENT_INFO&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;bytes&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;me&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;public_key&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; info&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;my key:&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;bytes&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;me&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;public_key&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;hex&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; flush&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;sending-and-receiving&quot; tabindex=&quot;-1&quot;&gt;Sending and receiving&lt;/h2&gt;
&lt;p&gt;Packets are addressed to the peer’s public key and relayed by the server.&lt;/p&gt;
&lt;pre class=&quot;language-python&quot;&gt;&lt;code class=&quot;language-python&quot;&gt;peer &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;bytes&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;fromhex&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;sys&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;argv&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;sys&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;argv&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;None&lt;/span&gt;


&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;receive&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;global&lt;/span&gt; peer
    &lt;span class=&quot;token keyword&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        typ&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; body &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; read_frame&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;f&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; typ &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; RECV_PACKET&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
            peer &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; body&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-interpolation&quot;&gt;&lt;span class=&quot;token string&quot;&gt;f&quot;&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;peer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;hex&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token format-spec&quot;&gt;8]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;: &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;body&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;decode&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;errors&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;replace&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; flush&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;


threading&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Thread&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;target&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;receive&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; daemon&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;start&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; line &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; sys&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;stdin&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    write_frame&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;sock&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; SEND_PACKET&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; peer &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; line&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;rstrip&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&#92;n&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;encode&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;result&quot; tabindex=&quot;-1&quot;&gt;Result&lt;/h2&gt;
&lt;p&gt;Two clients, &lt;code&gt;A&lt;/code&gt; and &lt;code&gt;B&lt;/code&gt;, can connect to the same DERP server (the one below is from &lt;a href=&quot;https://github.com/tailscale/tailcat&quot; target=&quot;_blank&quot; rel=&quot;noreferrer&quot;&gt;Tailcat&lt;/a&gt;). &lt;code&gt;A&lt;/code&gt; prints its key and waits:&lt;/p&gt;
&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;$ python3 derp.py https://tc301a.ipn.dev/derp
my key: dea9abc4abeb62c7098758edbbd06f380f8796c50c2847cd4afc0012432e1d34&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;B&lt;/code&gt; connects with &lt;code&gt;A&lt;/code&gt;’s key and sends a line:&lt;/p&gt;
&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;$ python3 derp.py https://tc301a.ipn.dev/derp dea9abc4abeb62c7098758edbbd06f380f8796c50c2847cd4afc0012432e1d34
my key: 97d5ab10d70c3b6bb230546b4d78d1f18d9fe016eae3bb32992ccfd212fd9567
hello&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;A&lt;/code&gt; receives it and can reply:&lt;/p&gt;
&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;97d5ab10: hello
hi back&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;B&lt;/code&gt; receives the reply:&lt;/p&gt;
&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;dea9abc4: hi back&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The connections to the DERP server are encrypted with TLS, but the server still sees both public keys and the payload. The payload is not encrypted end to end unless we encrypt it separately. Normal Tailscale traffic already has that additional encryption through WireGuard.&lt;/p&gt;
&lt;p&gt;Self-hosted DERP servers can require additional client verification, while Tailscale’s public relays do not appear to require registration with its control plane. For example, &lt;a href=&quot;https://headscale.net/stable/ref/derp/&quot; target=&quot;_blank&quot; rel=&quot;noreferrer&quot;&gt;Headscale&lt;/a&gt; uses these public relays by default. Tailscale employee Brad Fitzpatrick wrote on &lt;a href=&quot;https://news.ycombinator.com/item?id=49455652&quot; target=&quot;_blank&quot; rel=&quot;noreferrer&quot;&gt;Hacker News&lt;/a&gt; that “it’s been our CEO Avery’s position for ~6.5 years now that we should run DERP servers on the internet for the public good.”&lt;/p&gt;
&lt;p&gt;Tailscale’s DERP servers are meshed within each region, allowing them to forward packets between clients connected to different servers. This lets clients reconnect to another server in the region during maintenance or an outage. Clients try servers within a region in a supplied priority order for failover, rather than choosing them based on current load.&lt;/p&gt;
&lt;h2 id=&quot;conclusion&quot; tabindex=&quot;-1&quot;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;DERP relays, which can connect peers behind NATs, can also be used to bootstrap a direct WireGuard connection without a separate control plane. This is the idea behind &lt;a href=&quot;https://github.com/brofranks/headwire&quot; target=&quot;_blank&quot; rel=&quot;noreferrer&quot;&gt;Headwire&lt;/a&gt;, a small side project I built and have been running for a little while. Headwire translates a static WireGuard-style configuration into the peer and relay maps expected by Tailscale’s &lt;code&gt;magicsock&lt;/code&gt; library, which probes UDP paths and selects between direct and relayed connections.&lt;/p&gt;
&lt;p&gt;Headwire supports Linux and &lt;a href=&quot;https://github.com/brofranks/headwire-apple&quot; target=&quot;_blank&quot; rel=&quot;noreferrer&quot;&gt;macOS&lt;/a&gt;. There is also an iOS app, but it is unlikely to be distributed on the App Store anytime soon because Apple requires VPN apps to be published through an organization developer account. Headwire is not designed to be a full replacement for Tailscale. It goes beyond basic WireGuard with features such as inbound ACLs and IP masquerading, inherited from Tailscale’s data plane library. It also benefits from the &lt;a href=&quot;https://tailscale.com/blog/making-tailscale-faster&quot; target=&quot;_blank&quot; rel=&quot;noreferrer&quot;&gt;performance optimizations&lt;/a&gt; Tailscale has made to its fork of wireguard-go.&lt;/p&gt;
</content>
  </entry>
  
</feed>
