Low-level DNS requests using JavaScript in the Browser and Node.js.
import { query } from 'dns-query'
const { answers, rcode } = await query(
{ question: { type: 'TXT', name: 'google.com' } },
{ endpoints: ['1.1.1.1'] }
)
- You get the same API both on Node.js and the Browser (and React-Native).
- You need DNS information not exposed through high-level APIs (eg.
ttl
forTXT
entries). - You want to use DNS-over-HTTPS (DoH) servers.
- DoH implementation in Node.js and the browser.
- Low level implementation of classic DNS in Node.js.
- ESM modules, CommonJS modules and typescript definitions.
- A list of well-known DoH endpoints.
- A parser for compact definitions of DoH and DNS endpoints.
- A command line tool to run DNS requests.
import { query } from 'dns-query'
try {
const { answers } = await query({
question: {type: 'A', name: 'google.com'}
}, {
/* REQUIRED! */
endpoints: ['dns.google', 'dns.switch.ch'], // See more about endpoints below!
/* (optional) */
retries: 3, // retries if a given endpoint fails; -1 = infinite retries; 0 = no retry
timeout: 4000, // (default=30000) timeout for single requests
signal, // An AbortSignal to abort the request
})
} catch (error) {
switch (error.code) {
case 'HTTP_STATUS': // request failed, http status error
case 'RESPONSE_ERR': // request failed, invalid response
case 'ABORT_ERR': // request aborted
default: // Unexpected error
}
}
import { query, lookupTxt, wellknown } from 'dns-query'
// Use well-known endpoints to request
await query(/* ... */, { endpoints: wellknown.endpoints() })
// Shorthand for loading txt entries
await lookupTxt('domain.com', { endpoints: wellknown.endpoints() })
You can define the endpoints in a variety of ways:
let endpoints
endpoints = ['dns.google'] // Simple definition of an endpoint, protocol defaults to https:
endpoints = ['dns.google', 'dns.switch.ch'] // Defining multiple endpoints will use one at random.
endpoints = Promise.resolve(['dns.google']) // A promise will be resolved
endpoints = ['https://dns.google:443/dns-query'] // You can specify a url with detail props
endpoints = ['udp://8.8.8.8'] // You can also specify DNS endpoints
endpoints = ['udp://[::]'] // IPV6 endpoints need braces [] around the address.
endpoints = ['http://localhost'] // http endpoints are supported for tests!
endpoints = ['localhost [post]'] // some endpoints require POST requests
endpoints = ['dns.google [name=google]'] // you can give endpoints a name find them
endpoints = ['dns.google [ipv4=8.8.8.8]'] // you can specify the ipv4/ipv6 url as backup
endpoints = [{
protocol: 'https:',
host: 'dns.google'
}] // You can also specify a set of properties (see the next section)
// It will internally use toEndpoint to create endpoints
import { toEndpoint } from 'dns-query'
endpoints = [toEndpoint('dns.google')] // To speed things up, pass in preprocess endpoints.
For an endpoint to work, it needs to satisfy this interface:
type EndpointProps = {
/* https is the default for DoH endpoints and http for debug only! defaults to https: */
protocol?: 'https:' | 'http:'
/* https port, defaults to 443 for https, 80 for http */
port?: number | string | null
/* Host to look up */
host: string
/* Known IPV4 address that can be used for the lookup */
ipv4?: string
/* Known IPV6 address that can be used for the lookup */
ipv6?: string
/* Path, prefixed with /, defaults to /dns-query for the http/https protocol */
path?: string
/* Method to request in case of http/https, defaults to GET */
method?: 'POST' | 'GET'
/* name of the endpoint () */
name?: string
} | {
protocol: 'udp4:'
/* ipv4 endpoint to connect-to */
ipv4: string
/* https port, defaults to 53; 443 if pk is present */
port?: number | string | null
/* dnscrypt public key */
pk?: string | null
/* name of the endpoint () */
name?: string
} | {
protocol: 'udp6:'
/* ipv4 endpoint to connect-to */
ipv6: string
/* https port, defaults to 53; 443 if pk is present */
port?: number | string | null
/* dnscrypt public key */
pk?: string | null
/* name of the endpoint () */
name?: string
}
To make your life easier, dns-query
comes with a list of well-known public dns-over-https
servers to execute queries.
import { wellknown } from 'dns-query'
let endpoints = await wellknown.endpoints()
This list is automatically compiled using the data from the DNSCrypt project!
The npm package comes with the list that was/is current on the time of it's publication.
By default await wellknown.data()
will try to automatically download the newest list from the dns-query website
and fall back to the list at publication if downloading fails.
Using the argument for wellknown.endpoints(...)
you can specify which endpoints
you want to use:
let options
options = 'doh' // Filter to use only doh endpoints
options = 'dns' // Filter to use only dns servers (Node.js only!)
options = ['@cloudflare', '@google', '@opendns'] // Use specific named endpoints
options = ['https://cloudflare-dns.com/dns-query'] // For a convenient API, you can also define regular endpoints...
options = [{ host: 'cloudflare-dns.com' }] // ... and bypass the well-known entries.
options = (endpoint) => endpoint.protocol === 'https:' // Use a filter against the well-known endpoints
options = Promise.resolve('doh') // The endpoints can also be a promise
await wellknown.endpoints(options)
Node.js's dns support is limited, primary example being: resolveTxt
does not support ttl
results. For that reason, when using dns-query
in node you can also
specify dns
endpoints that should be helpful in the node environment.
const dnsServers = await wellknown.endpoints('dns') // all the known dns servers
const dohServers = await wellknown.endpoints('doh') // all the known doh servers
If we loaded the resolvers/endpoints for every request, both the server load and
your application's responsiveness will suffer. Because of that the wellknown
object
will cache the known resolvers.
You can customize the behavior by creating a new Wellknown
instance with a different
configuration:
import { Wellknown } from 'dns-query'
const wk = new Wellknown({
update: true, // Will load latest definitions from updateURL.
updateURL: new URL(), // URL to load the latest definitions. (default: project URL)
persist: false, // True to persist the loaded definitions (nodejs: in filesystem, browser: localStorage)
localStoragePrefix: 'dnsquery_', // Prefix for files persisted.
maxAge: 300000, // Max age of persisted data to be used in ms.
timeout: 5000 // Timeout when loading updates.
})
persist: true
is useful when you restart a node process or refresh a browser.
The persisted data will then be available making subsequent requests faster still.
If you set persist: true
in Node.js, it will try to persist the list of resolvers relative to
the node_modules
directory.
In the browser, setting persist: true
will use localStorage
to store the copy of resolvers.
By default it will use the localStoragePrefix = 'dnsquery_'
option.
You will be able to find the persisted resolvers under
localStorage.getItem('dnsquery_resolvers.json')
.
query(..., {
localStoragePrefix: 'my_custom_prefix'
})
These servers come with caveats that you should be aware of:
- A server may filter, log or limit the requests it receives!
- Filtering can be useful in case you want to avoid malware/ads/adult-content.
- Logging may be required in some countries and limiting may be part of a business model.
- Furthermore the different endpoints may or may not be distributed around the globe, making requests slower/faster depending on the client's location.
- Not all endpoints supply CORS headers which means that the list is severly reduced if you use this library in the browser.
If you are presenting this library to an end-user, you may want to allow them to decide what endpoint they want to use as it has privacy and usage implications!
By default query(...)
will return the packet as received by the endpoints, even if
that package was flagged as an error. The validateResponse
helper will give a
well readable error message.
import { validateResponse, query } from 'dns-query'
const respone = validateResponse(await query(/* ... */))
By default, query
will try load the latest definitions (update: true
) but will
persist them only in memory. Subsequent requests will only update the list if
it is old.
The default TXT
response of dns-queries is a list of Uint8Array
's. If you have a
TXT
response you can use the combineTxt
API to combine the requests.
import { combineTxt } from 'dns-query'
const response = await query(/* ... */)
if (response.question.type === 'TXT') {
const txt = response.answers.map(answer => combineTxt(answer.data))
}
More convenient still is the lookupTxt
API that allows you to simple request
the TXT entries for a domain.
import { lookupTxt } from 'dns-query'
const { entries, endpoint } = await lookupTxt('google.com')
You can install dns-query
as a command-line tool using npm i dns-query -g
or by running npx dns-query
.
$ dns-query <options> <input>
dns-query - Execute a dns query over https.
USAGE:
dns-query <Options> <Input>
EXAMPLES:
# Fetch from the google dns-over-https endpoint the A-name of google.com
$ dns-query --json -e google \
'{ "question": { "type": "A", "name": "google.com" } }'
# Fetch TXT entries for ipfs.io through regular dns
$ dns-query --json --dns \
'{ "question": { "type": "TXT", "name": "ipfs.io" } }'
# Pass the query through stdin
$ echo '{ "question": { "type": "A", "name": "google.com" } }' \
| dns-query --stdin --endpoint cloudflare
# Process binary packages as base64
$ dns-query --base64 AAAAAAABAAAAAAAABGlwZnMCaW8AABAAAQ==
# Load the txt data for a domain
$ dns-query --mode=txt ipfs.io
OPTIONS:
--mode ........... Mode consume/process data.
---mode=free ... Free query input (default)
---mode=txt .... TXT data loading shortcut
--help, -h ....... Show this help
--version, -v .... Show the version
--json ........... --type=json
--base64 ......... --type=base64
--binary ......... --type=binary
--type ........... Input type. Options: json, base64, binary; Default: json
--out ............ Output type. Defaults to the input --type.
--stdin .......... Get <input> from stdin instead of cli arguments
--dns ............ Use dns endpoints
--doh ............ Use doh endpoints
--endpoint, -e ... Use a specific endpoint. Can be either the name of a known
endpoint, a json object or an url. By default uses one of the known endpoints.
If multiple are provided, one at random will be used.
--endpoints ...... Lists all known endpoints as json.
--resolvers ...... List all known resolvers as json.
--response ....... Show the http response in the result.
--retries ........ Number of retries to do in case requests fails, default: 5
--timeout ........ Timeout for the request in milliseconds, default: 30000 (5 sec)
--max-age ........ Max age of the persisted data, default: 300000 (5 min)
--no-persist ..... Dont persist the the latest resolvers
--offline ........ Do not update the resolver list
The input of endpoints are passed to the toEndpoint
helper. See Endpoints
for the supported formats.