DNS wire codec, cache, stub resolver and forwarder
0

Configure Feed

Select the types of activity you want to include in your feed.

29 1 0

Clone this repository

https://git.vm.fail/gazagnaire.org/ocaml-dns https://git.vm.fail/did:plc:6iri5cpthynbinsha5vdgn32
ssh://git@git.recoil.org:2222/gazagnaire.org/ocaml-dns ssh://git@git.recoil.org:2222/did:plc:6iri5cpthynbinsha5vdgn32

For self-hosted knots, clone URLs may differ based on your setup.


README.md

nox-dns#

Warning. This is a work-in-progress experimental fork of ocaml-dns. Do not use it in production; use ocaml-dns instead.

A fork of ocaml-dns, imported at 10.2.5 and exposed as a single module Dns: the wire codec, the LRU cache, and the transport-agnostic stub client, vendored verbatim. It differs from ocaml-dns in two ways.

  1. The stub client's clock and RNG are injected -- see The client.
  2. A DNS forwarding resolver is added (Dns.Forward and its primitives), which ocaml-dns does not have -- see The forwarder.

Installation#

Install with opam:

$ opam install nox-dns

If opam cannot find the package, it may not yet be released in the public opam-repository. Add the overlay repository, then install it:

$ opam repo add samoht https://tangled.org/gazagnaire.org/opam-overlay.git
$ opam update
$ opam install nox-dns

The client#

Dns.Client.Make (T) takes a transport T and produces a stub resolver. In ocaml-dns the transport module type S carries two module-level values:

val rng   : int -> string
val clock : unit -> int64

so each transport binds them to fixed globals (dns-client.unix, .lwt, .mirage and .miou-unix use Mtime_clock.elapsed_ns and Mirage_crypto_rng.generate). This fork removes them from S and passes them to the constructor Make.v instead (renamed from ocaml-dns's create to the repo's v); S is then pure I/O -- connect / send_recv / close:

val v :
  ?cache_size:int -> ?edns:... -> ?nameservers:... -> ?timeout:int64 ->
  clock:(unit -> int64) -> rng:(int -> string) ->
  T.stack -> t

The query building, CNAME following, response matching, RFC 7766 TCP length-prefix framing and RFC 6761 special names are ocaml-dns's, in lib/client.ml; only the clock/RNG plumbing differs.

The forwarder#

Dns.Forward (with Dns.Forward_config, Dns.Hosts, Dns.Id_mux, Dns.Case0x20, Dns.Edns_policy, Dns.Server_select, Dns.Upstream_health) is a DNS forwarding resolver in the shape of moby/vpnkit's dns_forward: decode the query, answer /etc/hosts and cache hits locally, else race the zone-routed upstreams with 0x20 encoding, EDNS sizing, negative caching and health tracking. The edge (nox-dns-eio's Forward_eio) drives it over real transports.

It is specific to this fork -- upstream ocaml-dns has no forwarding resolver. It lives entirely in its own modules (forward*.ml, hosts.ml, id_mux.ml, case0x20.ml, edns_policy.ml, server_select.ml, upstream_health.ml) and uses only the public wire codec, so it is independent of the codec and the client: a consumer that wants the codec alone can ignore the Forward modules.

The codec#

The wire codec and cache are ocaml-dns's, imported at 10.2.5 (copyright the ocaml-dns authors; ISC) and restructured into one module per concept: lib/packet.ml, lib/rr_map.ml, lib/rr.ml (the record types), lib/name.ml (label compression), lib/edns.ml, lib/tsig.ml, the registry enums, and the LRU cache as lib/cache.ml. Constructors follow the repo's v convention (e.g. Packet.v and Packet.Question.v for ocaml-dns's create).

Usage#

nox-dns is the transport-agnostic core. Provide a transport and inject a clock + RNG:

module Client = Dns.Client.Make (My_transport)

let t = Client.v ~clock ~rng ~nameservers stack in
Client.getaddrinfo t Dns.Rr_map.A name

The Eio transport lives in this directory at lib/eio, packaged as nox-dns-eio: it injects the Eio clock and the process RNG, and its TCP mode (RFC 7766) needs only Eio.Net.connect, so it resolves over a unikernel's TCP-only stack.

nox-dns-eio: the Eio transport#

Single-shot A / AAAA / TXT queries through the client above, over TCP (RFC 7766, the default) or UDP (~proto:\Udp). A resolver picks up the nameservers from /etc/resolv.conf, falling back to Cloudflare (1.1.1.1) and Google (8.8.8.8`) if that file is missing or empty. The list can be overridden at construction:

# let resolver =
    Dns_eio.v
      ~nameservers:[ Ipaddr.of_string_exn "1.1.1.1" ]
      ~timeout_s:1.0 () in
  List.map Ipaddr.to_string (Dns_eio.nameservers resolver)
- : string list = ["1.1.1.1"]

The configuration binds to a network, a switch and the monotonic clock as a Dns_eio.client, which holds the response cache: create it once and reuse it, so repeat lookups within a record's TTL are served without touching the network:

let lookup_txt env name =
  Eio.Switch.run @@ fun sw ->
  let client =
    Dns_eio.client ~sw
      ~net:(Eio.Stdenv.net env)
      ~mono:(Eio.Stdenv.mono_clock env)
      ~random:(Eio.Stdenv.secure_random env)
      (Dns_eio.v ())
  in
  match Dns_eio.txt client name with
  | Ok records -> List.iter print_endline records
  | Error e -> Fmt.pr "DNS error: %a@." Dns_eio.pp_error e

The same pattern applies to Dns_eio.a (IPv4) and Dns_eio.aaaa (IPv6). A name with no matching records returns Ok []; a name that does not exist returns Error `Nxdomain.

License#

ISC, as ocaml-dns. See LICENSE.md; copyright is the ocaml-dns authors' plus this fork's changes.