github.com/spider-gazelle/dns

extendable non blocking DNS for crystal lang

17 stars
9 dependents
License: MIT

Nothing has been indexed for 1.2.0 yet. The tag is recorded, its shard.yml has not been read, so the manifest and dependency list below are empty because they are unknown rather than because they are absent.

Installation

# Add this to your shard.yml
dependencies:
  dns:
    github: spider-gazelle/dns
    version: ~> 1.2.0

Then run:

shards install

shard.yml

No shard.yml has been indexed for 1.2.0. You can read it on the repository.

Dependencies

Unknown: the shard.yml for this version has not been read yet.

README

This README is the one indexed from the repository at its latest ref, not from the tag for this version.

DNS

CI

Non-blocking extendable DNS client for crystal lang.

With built in support for UDP, TLS, HTTPS and mDNS resolvers.

Installation

  1. Add the dependency to your shard.yml:

    dependencies:
      dns:
        github: spider-gazelle/dns
  2. Run shards install

Usage

A simple query

require "dns"

responses = DNS.query(
  "www.google.com",
  {
    DNS::RecordType::A,
    DNS::RecordType::AAAA,
  }
)

ips = responses.map(&.ip_address)

How to configure custom resolvers (secure from prying eyes)

require "dns"
require "dns/resolver/https"

DNS.default_resolver = DNS::Resolver::HTTPS.new(["https://1.1.1.1/dns-query"])

# or just for some routes
DNS.resolvers[/.+\.com\.au$/i] = DNS::Resolver::HTTPS.new(["https://1.1.1.1/dns-query"])

# there is a built in resolver to use mDNS for *.local routes

# if you'd prefer to use the system resolver for A and AAAA records
DNS.default_resolver = DNS::Resolver::System.new(
  # uses the fallback resolver for other DNS records
  fallback: Resolver::UDP.new
)

# for the TLS resolver you need to specify a domain name for the returned TLS certificate
DNS.default_resolver = DNS::Resolver::TLS.new({
  "8.8.8.8" => "dns.google",
})

# or maybe you don't want to use the system defined DNS servers
DNS.default_resolver = DNS::Resolver::UDP.new(["1.1.1.1", "8.8.8.8"])

# it's also possible to create your own resolvers implementing the abstract method in `DNS::Resolver`

By default, the library will attempt to obtain the DNS servers configured on your host. That is using:

  • /etc/resolv.conf on linux / nix* systems
  • Win32 API calls on Windows
  • SystemConfiguration framework on MacOS

NOTE:: currently it does not check if DNS over HTTPS is configured on either Mac or Windows (future work)

If DNS discovery is unsuccessful it falls back to using ["1.1.1.1", "8.8.8.8"].

# to customise the fallback servers
DNS::Servers.fallback = ["192.168.0.1"]

Resolver behaviour

The UDP resolver is modelled on the behaviour of unix getaddrinfo:

  • queries are retransmitted with an exponential backoff and failed over across the configured nameservers, rather than failing on a single dropped packet
  • a persistently unresponsive nameserver is demoted to the end of the list so later queries prefer healthy servers
  • resolv.conf options are honoured: timeout: (default 2s, clamped 1..30), attempts: (default 3, clamped 1..5) and ndots: (default 1, clamped 0..15). The base timeout can also be set globally with DNS.timeout.
  • an EDNS0 OPT record is sent advertising a 1232 byte UDP payload (DNS Flag Day 2020), and truncated (TC) responses are transparently refetched over TCP
# tune the base (initial) per-try timeout; backoff doubles it each attempt
DNS.timeout = 1.second

Shared UDP source port

All UDP queries are sent from a single, long-lived source port per address family (a process-wide DNS::Resolver::UDP::SharedSocket) rather than opening a fresh socket per query. A background fiber demultiplexes responses back to each waiting query by transaction id.

Reusing one source port trades some of the source-port entropy recommended by RFC 5452 for connection reuse, so every response is strictly validated before it is accepted: the transaction id (a CSPRNG value) must be outstanding, the QR bit must be set, the source address must match the queried server, and the echoed question must match the request. This is appropriate for a stub resolver talking to a trusted recursive resolver. If you operate over an untrusted path, prefer the TLS or HTTPS resolvers.

Other things to note:

  • IPv6 is supported and handled transparently
  • a DNS cache is maintained and used based on TTL of responses
  • DNS servers may return results of expected future queries, these results are cached
  • force all DNS lookups to occur via this library: require "dns/ext/addrinfo"

Contributing

  1. Fork it (https://github.com/spider-gazelle/dns/fork)
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Add some feature')
  4. Push to the branch (git push origin my-new-feature)
  5. Create a new Pull Request

Contributors