<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>python &amp;mdash; forbiddenlake</title>
    <link>https://blog.kiserai.net/tag:python</link>
    <description></description>
    <pubDate>Tue, 28 Apr 2026 08:06:47 +0000</pubDate>
    <item>
      <title>Crafting a DNS-over-HTTPS GET request manually</title>
      <link>https://blog.kiserai.net/crafting-a-dns-over-https-get-request-manually</link>
      <description>&lt;![CDATA[Let&#39;s say I want to create an external health check for AdGuard that exercises both the HTTP interface and the DNS resolution, because I do.  Unlike Cloudflare, AdGuard doesn&#39;t take an easy name parameter, and I found it quite difficult to find existing documentation on how to craft a DoH request manually. Here&#39;s how to do it in Python.&#xA;&#xA;!--more--&#xA;&#xA;Requirements&#xA;&#xA;Python 3&#xA;Ideally dnspython   = 2.2.0 (so, Ubuntu 22.04&#39;s python3-dnspython package will not work. Use pip or a python docker image). Earlier versions will work without setting id=0.&#xA;requests for testing&#xA;&#xA;One-liner&#xA;&#xA;Change example.com:&#xA;&#xA;DOMAIN=&#34;example.com&#34; python3 -c &#34;import os, dns.message, requests, base64; req = dns.message.makequery(os.environ[&#39;DOMAIN&#39;], &#39;A&#39;, id=0).towire(); print(base64.urlsafeb64encode(req).decode(&#39;utf-8&#39;).rstrip(&#39;=&#39;))&#34;&#xA;&#xA;Script&#xA;&#xA;Set DOMAIN environment variable before running:&#xA;&#xA;import os&#xA;import dns.message&#xA;import requests&#xA;import base64&#xA;&#xA;reqraw = dns.message.makequery(os.environ[&#39;DOMAIN&#39;], &#39;A&#39;, id=0).towire()&#xA;reqstr = base64.urlsafeb64encode(reqraw).decode(&#39;utf-8&#39;).rstrip(&#39;=&#39;)&#xA;print(&#34;Query path and encoded string to use with a GET request to a DNS-over-HTTPS server:&#34;)&#xA;print(&#34;/dns-query?dns=&#34; + reqstr)&#xA;print(&#34;\nTesting query with Cloudflare...&#34;)&#xA;resp = requests.get(&#34;https://cloudflare-dns.com/dns-query?dns=&#34; + reqstr)&#xA;resp.raiseforstatus()&#xA;msg = dns.message.from_wire(resp.content)&#xA;print(&#34;DNS response:&#34;)&#xA;print(msg)&#xA;&#xA;Example output&#xA;&#xA;$ DOMAIN=example.com python3 ./doh.py&#xA;Query path and encoded string to use with a GET request to a DNS-over-HTTPS server:&#xA;/dns-query?dns=AAABAAABAAAAAAAAB2V4YW1wbGUDY29tAAABAAE&#xA;&#xA;Testing query with Cloudflare...&#xA;DNS response:&#xA;id 0&#xA;opcode QUERY&#xA;rcode NOERROR&#xA;flags QR RD RA&#xA;;QUESTION&#xA;example.com. IN A&#xA;;ANSWER&#xA;example.com. 82324 IN A 93.184.216.34&#xA;;AUTHORITY&#xA;;ADDITIONAL&#xA;&#xA;References&#xA;&#xA;https://github.com/dohwg/draft-ietf-doh-dns-over-https/issues/30#issuecomment-345850479&#xA;&#xA;https://developers.cloudflare.com/1.1.1.1/encryption/dns-over-https/make-api-requests/dns-wireformat/&#xA;&#xA;https://datatracker.ietf.org/doc/html/rfc8484#section-4.1&#xA;&#xA;Filed in: #python #dns #adguard]]&gt;</description>
      <content:encoded><![CDATA[<p>Let&#39;s say I want to create an external health check for <a href="https://adguard.com/en/welcome.html">AdGuard</a> that exercises both the HTTP interface and the DNS resolution, because I do.  Unlike <a href="https://developers.cloudflare.com/1.1.1.1/encryption/dns-over-https/make-api-requests/">Cloudflare</a>, AdGuard doesn&#39;t take an easy <code>name</code> parameter, and I found it quite difficult to find existing documentation on how to craft a DoH request manually. Here&#39;s how to do it in Python.</p>



<h2 id="requirements">Requirements</h2>
<ul><li>Python 3</li>
<li>Ideally <code>dnspython</code> &gt;= 2.2.0 (so, Ubuntu 22.04&#39;s <code>python3-dnspython</code> package will not work. Use pip or a <a href="https://hub.docker.com/_/python">python docker image</a>). Earlier versions will work without setting <code>id=0</code>.</li>
<li><code>requests</code> for testing</li></ul>

<h2 id="one-liner">One-liner</h2>

<p>Change <code>example.com</code>:</p>

<pre><code class="language-bash">DOMAIN=&#34;example.com&#34; python3 -c &#34;import os, dns.message, requests, base64; req = dns.message.make_query(os.environ[&#39;DOMAIN&#39;], &#39;A&#39;, id=0).to_wire(); print(base64.urlsafe_b64encode(req).decode(&#39;utf-8&#39;).rstrip(&#39;=&#39;))&#34;
</code></pre>

<h2 id="script">Script</h2>

<p>Set DOMAIN environment variable before running:</p>

<pre><code class="language-python">import os
import dns.message
import requests
import base64

req_raw = dns.message.make_query(os.environ[&#39;DOMAIN&#39;], &#39;A&#39;, id=0).to_wire()
req_str = base64.urlsafe_b64encode(req_raw).decode(&#39;utf-8&#39;).rstrip(&#39;=&#39;)
print(&#34;Query path and encoded string to use with a GET request to a DNS-over-HTTPS server:&#34;)
print(&#34;/dns-query?dns=&#34; + req_str)
print(&#34;\nTesting query with Cloudflare...&#34;)
resp = requests.get(&#34;https://cloudflare-dns.com/dns-query?dns=&#34; + req_str)
resp.raise_for_status()
msg = dns.message.from_wire(resp.content)
print(&#34;DNS response:&#34;)
print(msg)
</code></pre>

<h2 id="example-output">Example output</h2>

<pre><code class="language-bash">$ DOMAIN=example.com python3 ./doh.py
Query path and encoded string to use with a GET request to a DNS-over-HTTPS server:
/dns-query?dns=AAABAAABAAAAAAAAB2V4YW1wbGUDY29tAAABAAE

Testing query with Cloudflare...
DNS response:
id 0
opcode QUERY
rcode NOERROR
flags QR RD RA
;QUESTION
example.com. IN A
;ANSWER
example.com. 82324 IN A 93.184.216.34
;AUTHORITY
;ADDITIONAL
</code></pre>

<h2 id="references">References</h2>

<p><a href="https://github.com/dohwg/draft-ietf-doh-dns-over-https/issues/30#issuecomment-345850479">https://github.com/dohwg/draft-ietf-doh-dns-over-https/issues/30#issuecomment-345850479</a></p>

<p><a href="https://developers.cloudflare.com/1.1.1.1/encryption/dns-over-https/make-api-requests/dns-wireformat/">https://developers.cloudflare.com/1.1.1.1/encryption/dns-over-https/make-api-requests/dns-wireformat/</a></p>

<p><a href="https://datatracker.ietf.org/doc/html/rfc8484#section-4.1">https://datatracker.ietf.org/doc/html/rfc8484#section-4.1</a></p>

<p>Filed in: <a href="https://blog.kiserai.net/tag:python" class="hashtag"><span>#</span><span class="p-category">python</span></a> <a href="https://blog.kiserai.net/tag:dns" class="hashtag"><span>#</span><span class="p-category">dns</span></a> <a href="https://blog.kiserai.net/tag:adguard" class="hashtag"><span>#</span><span class="p-category">adguard</span></a></p>
]]></content:encoded>
      <guid>https://blog.kiserai.net/crafting-a-dns-over-https-get-request-manually</guid>
      <pubDate>Mon, 21 Aug 2023 19:57:41 +0000</pubDate>
    </item>
  </channel>
</rss>