Receiving DNS directly through JavaScript

[[日本語版](https://qiita.com/martinheidegger/items/cfeccfa71c1ea30b9ebe)]

Following my [2021 blog post about IDs](https://qiita.com/martinheidegger/items/09c48c50136b629f1107), this year is a bit more low-level. 

Every single one of us uses the [global DNS](https://en.wikipedia.org/wiki/Domain_Name_System) every single day. It is so fundamentally tied to the internet and our daily life that we barely think about how it works and just hope it works well.

> Quick Refresher: DNS (domain name system) is the way how our computers find the connection between a global name (like "qiita.com" or "google.com") and associated resources such as our e-mail or web servers.

Usually, we leave it up to low-level processes of the operating system to take care of looking up domains. When you navigate in the browser to `https://qiita.com`: Do you think about, how the browser knows which server has the data?

Usually, you don't need to think about it... but did you know... that you can actually store a little bit of data with DNS entries? Its not much data, but it can be enough for a digital signature, a business card or wallet address. You can use this data for practical and impractical things :wink:.

Personally, I needed to learn about this when I experimented with [→ dnslink](https://dnslink.dev).

You should know that this today's topic is quite geeky and you will probably never need. To me, it is mostly just fun to think about and interesting to experiment with.

### Basics with Node.js

The regular way of accessing DNS information with Node.JS is relatively boring.

```javascript:test_01_nodejs_lookup.mjs
import { lookup, resolveTxt } from 'node:dns/promises'

console.log(await lookup('qiita.com'))
console.log(await resolveTxt('qiita.com'))
```

And you will get something like:

```js
{ address: '54.65.85.19', family: 4 }
[
  ['google-site-verification=OCwPip5vYMz3KMtOVXGDy8MVETsPPKp8zcR5gxcP0mk'],
  ['pardot859913=459e91b4646f79a9dc3bb58970676deab274ab5fb48dbb4a2ae1f01950a469f5'],
  ['pardot859913=4e67330c615e435955fd1e1973811b368a84de7712bebe791483228997d47650'],
  ['v=spf1 include:_spf.google.com include:amazonses.com include:servers.mcsv.net include:mail.zendesk.com include:aspmx.pardot.com a:qiita.com ~all']
]
```

These two functions: `lookup` and `resolveTxt` are very common in other programming languages as well.

The reason for this is simple. Node.js like many others, uses "[c-ares](https://c-ares.org/)", a low-level C library for accessing the DNS.

Because it is written in C, of course we can not use this API in the browser...

### DNS in the browser

_First, a bit of history:_ DNS has a privacy problem. When you use the regular DNS system, like good-old `http://` everyone on the internet can see what you are looking for. When you browse to https://jsbeginners.com it will look up `jsbeginners.com` and theoretically anyone looking at your traffic data will see that you are a noob. :sweat:

You could use a VPN to hide the domains from the public but your VPN provider will still see what you are looking up. In order to avoid this, there have been some efforts to improve privacy. The first thing was DoT (DNS over TLS) in 2018. It specifies a proxy server that serves DNS packets over a tls(secure) connection.

Actually, more interesting to us is DoH (DNS over HTTPS). It arrived at the same time as DoT and it specifies a DNS API through regular HTTPS requests. With a DoH server we can do DNS requests in the browser!

```mermaid
sequenceDiagram
    Browser-->>DoH Server: DNS-request
    DoH Server-->>DNS: DNS-request
    DNS-->>DoH Server: DNS-response
    DoH Server-->>Browser: DNS-response
```

### How can we use it?

As web developers we like `JSON` or maybe `XML` data but both the `DNS-request` and `DNS-response` are specified as binary data packets (`Uint8Array`). For that purpose [→ mafintosh](https://github.com/mafintosh) published a long while ago [→ dns-packet](https://www.npmjs.com/package/dns-packet) which supports quite a lot of the DNS packet specification. Now, we could just use that, but the problem with it is that its written in commonjs for Node.js. So、 I went ahead and ported it to `esm` without Node.js extras and published [→ @leichtgewicht/dns-packet](https://npmjs.com/@leichtgewicht/dns-packet) - a fork that can be used in browsers, Node.js and react-native, etc.:

```javascript:test_02_dns-packet.mjs
import { encode, decode } from '@leichtgewicht/dns-packet'

console.log(
  encode({
    type: 'query',
    id: 1,
    questions: [
      { type: 'A', name: 'qiita.com' } 
    ]
  }),
  decode(new Uint8Array([
      0,  1, 0,  0,   0,   1,   0,   0,  0,  0,  0,  0,   5, 113, 105, 105,
    116, 97, 3, 99, 111, 109,   0,   0,  1,  0,  1
  ]))
)
```

Now, we have neat DNS packets that we can send using https. :eyeglasses:
Using a reliable server like `dns.cloudflare.com`, we send POST requests using `fetch` API and we will get DNS responses!

```javascript:test_03_send_dns.mjs
import { encode, decode, RECURSION_DESIRED } from '@leichtgewicht/dns-packet'

const res = await fetch('https://dns.cloudflare.com/dns-query', {
  method: 'POST',
  headers: { 'content-type': 'application/dns-message' },
  body: encode({
    type: 'query',
    flags: RECURSION_DESIRED,
    questions: [
      { type: 'A', name: 'qiita.com' }
    ]
  })
})
const bytes = new Uint8Array(await res.arrayBuffer())
console.log(decode(bytes).answers)
```

Looking at the result you should notice that there are more features in the output already.

```
{
  name: 'qiita.com',
  type: 'A',
  ttl: 60,
  class: 'IN',
  flush: false,
  data: '3.115.205.169'
}
```

Specially the `ttl` can be very important as it is not well supported by Node.js.

## Reducing the headache

With this, we can now hack requests and responses as much as we like. But in practice, there are a lot of things that can give us headaches. Supporting platforms, handling errors, etc. is all pretty annoying.

To make our live easier, I prepared [→ dns-query](https://npmjs.com/package/dns-query).

```javascript:test_04_dns-query.mjs
import { query, wellknown } from 'dns-query'

console.log(
  (await query(
    { question: { type: 'A', name: 'qiita.com' } },
    { endpoints: wellknown.endpoints() }
  )).answers
)
```

The `dns-query` package takes care of a lot details that may come in handy rather sooner than later. For example: `wellknown.entpoints()` will try to download the latest compiled listed based on the https://dnscrypt.info data.

## Next steps

With this simple API, now you have the power to do DNS requests in any environment.
As an example have a look at the "Try it out" component on [→ dnslink.dev](https://dnslink.dev#try-it-out). It is a JavaScript component that allows to lookup entries in the browser instantly.

<kbd>

[![Screenshot of dnslink.dev](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/20010/697ef325-66e9-9bd6-68ef-5943044932ff.png)](https://dnslink.dev#try-it-out)

</kbd>

You can use a similar system now in your frontend/backend/etc. and builds without special configuration which¥ should is fast and painless.

In the future it would be super-awesome to implement [DNSSEC](https://en.wikipedia.org/wiki/Domain_Name_System_Security_Extensions) and for that, please :+1: (vote) for [webcrypto-secure-curves](https://github.com/WICG/webcrypto-secure-curves) to make that easier.

---

If you have an idea, comment or if you built something using `dns-query` please let me know in the comments.

As for personal updates, this year I am free-lance developer again working more with japanese companies. I am quite busy, which is why the article this year has been shorter and my OSS contributions have been less. Next year, I hope to be able to go into a bit more practical and talk about login systems.

- :bird: [Twitter](https://twitter.com/leichtgewicht)
- :speech_left: [Discord](https://discord.com): [OWDDM Osaka Community](https://owddm.com/discord)

Thank you, and I hope you have a merry christmas and a good start in the next year.
:heart: :xmas-tree: