A DNS lookup utility for the command line — an Elixir reimplementation of the classic Unix dig with a familiar output format and two distribution modes (escript for Elixir developers, Bakeware standalone binary for everyone else).
tdig is part of the smkwlab DNS/Elixir ecosystem and is built on top of the tenbin_dns packet library.
$ tdig example.com A @8.8.8.8
;; ->>HEADER<<- opcode: QUERY, status: No Error, id: 49671
;; flags:qr rd ra; QUERY: 1, ANSWER 2, AUTHORITY: 0, ADDITIONAL: 0
;; QUESTION SECTION:
;example.com. IN A
;; ANSWER SECTION:
example.com. 37 IN A 104.20.23.154
example.com. 37 IN A 172.66.147.243
;; Query time: 28 ms
;; SERVER: 8.8.8.8#53(8.8.8.8)
;; WHEN: 2026-05-29 10:33:23
;; MSG SIZE rcvd: 61- dig-compatible output — drop-in for scripts and humans used to
dig - UDP and TCP transports —
--tcpfor forced TCP, with optional truncation handling - IPv4 and IPv6 — auto-detected from the server address, or forced with
--v4/--v6 - EDNS0 support — bufsize control and EDNS Client Subnet (ECS)
- Reverse lookups —
-x 1.2.3.4expands to the properin-addr.arpa.query - Read/write packet files — round-trip raw DNS packets for offline analysis
- Two build modes — escript (small, needs Elixir runtime) or Bakeware (self-contained binary)
Pre-built binaries published as GitHub release assets are self-contained and don't require Elixir or Erlang to be installed. Currently supported targets:
| Platform | Asset |
|---|---|
| Linux x86_64 | tdig-linux-x86_64 |
| macOS arm64 (Apple Silicon) | tdig-macos-arm64 |
Pick the binary matching your platform:
# Linux x86_64
curl -L -o tdig https://github.com/smkwlab/tdig/releases/latest/download/tdig-linux-x86_64
chmod +x tdig
./tdig example.com A
# macOS arm64
curl -L -o tdig https://github.com/smkwlab/tdig/releases/latest/download/tdig-macos-arm64
chmod +x tdig
./tdig example.com AOr auto-detect via uname:
OS=$(uname -s | tr '[:upper:]' '[:lower:]' | sed 's/darwin/macos/')
ARCH=$(uname -m)
curl -L -o tdig "https://github.com/smkwlab/tdig/releases/latest/download/tdig-${OS}-${ARCH}"
chmod +x tdig
./tdig example.com AEach release also publishes a SHA256SUMS manifest. Verify the binary's
integrity before running it. sha256sum matches entries by filename, so keep
the binary under its release asset name for the check (e.g. download with
-o tdig-linux-x86_64, verify, then rename/chmod). If no manifest entry
matches any local file, sha256sum exits non-zero with no file was verified
rather than silently passing.
# Download the binary under its asset name and the manifest alongside it
curl -L -o tdig-linux-x86_64 https://github.com/smkwlab/tdig/releases/latest/download/tdig-linux-x86_64
curl -L -o SHA256SUMS https://github.com/smkwlab/tdig/releases/latest/download/SHA256SUMS
# Check the binary you downloaded (ignores the other platform's entry)
sha256sum --ignore-missing -c SHA256SUMS
# tdig-linux-x86_64: OKOn macOS, shasum ships with the system but its Perl build has no
--ignore-missing, so verify just the relevant line (portable — works on
Linux too):
grep tdig-macos-arm64 SHA256SUMS | shasum -a 256 -c -
# tdig-macos-arm64: OKThe checksum above proves integrity. To also prove authenticity — that the
manifest really came from tdig's release workflow and not someone who tampered
with the release — each release's SHA256SUMS is signed with
cosign keyless signing (no
maintainer-held key; the signature is bound to the release workflow's identity
via GitHub OIDC and logged to the public Sigstore transparency log).
This step is optional. If you have cosign v3
or newer installed and want the stronger guarantee, download the bundle
alongside the manifest and verify (releases from 0.4.2 on are signed with
cosign v3, whose Sigstore-bundle format needs cosign v3+ to verify):
curl -L -o SHA256SUMS https://github.com/smkwlab/tdig/releases/latest/download/SHA256SUMS
curl -L -o SHA256SUMS.cosign.bundle https://github.com/smkwlab/tdig/releases/latest/download/SHA256SUMS.cosign.bundle
cosign verify-blob \
--bundle SHA256SUMS.cosign.bundle \
--certificate-identity-regexp '^https://github.com/smkwlab/tdig/\.github/workflows/release\.yml@refs/tags/[^/]+$' \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
SHA256SUMS
# Verified OKOnce the manifest is verified, the sha256sum check above ties the binaries to
it. (--certificate-identity-regexp accepts any release tag; pin it to a
specific tag like …/release\.yml@refs/tags/0.4.1$ if you want to require one.)
If you already have Elixir installed and want a fast iterative build:
git clone https://github.com/smkwlab/tdig.git
cd tdig
mix deps.get
mix escript.build
./tdig example.com AThe tdig escript file produced is small and requires erl / escript on PATH at runtime.
To produce your own standalone binary:
MIX_ENV=prod mix release
./_build/prod/rel/bakeware/tdig example.com AThe command line syntax mirrors dig:
tdig [options] [@server] host [type] [class]
Positional arguments may appear in any order, as in dig: a token matching a known record type or class name is interpreted as such regardless of position (so tdig TXT example.com and tdig example.com TXT are equivalent), and the leading @ denotes the DNS server. To query a host literally named after a record type, add a trailing dot (e.g. tdig txt.).
When unspecified:
- server defaults to
8.8.8.8 - type defaults to
A - class defaults to
IN - port defaults to
53
tdig example.com A # IPv4 address
tdig example.com AAAA # IPv6 address
tdig gmail.com MX # mail exchangers
tdig example.com NS # name servers
tdig example.com SOA # start of authority
tdig example.com TXT # text records
tdig example.com CNAME # canonical nametdig -x 1.1.1.1
# equivalent to:
tdig 1.1.1.1.in-addr.arpa. PTRtdig --tcp example.com AUDP responses with the TC (truncated) flag are normally retried over TCP. Pass --ignore to suppress that retry.
tdig example.com A @1.1.1.1 # Cloudflare DNS
tdig example.com AAAA @2606:4700:4700::1111 # IPv6 server (auto-detected)
tdig example.com A @127.0.0.1 -p 5353 # non-standard porttdig -e example.com A # advertise EDNS0
tdig -b 4096 example.com A # advertise a specific buffer size (implies -e)
tdig --subnet 192.0.2.0/24 example.com A # EDNS Client Subnet (IPv4)
tdig --subnet 2001:db8::/48 example.com AAAA # ECS over IPv6tdig can save the raw wire packets and replay them — useful for offline parsing, fuzzing, or capture comparison.
tdig --write response.bin example.com A # save the answer packet
tdig --write-request query.bin example.com A # save the outgoing query
tdig -r response.bin # parse and print a saved packetUsage: tdig [options] [@server] host [type] [class]
options
-c --class <class> specify query class
-t --type <type> specify query type
-p --port <port> specify port number
-x --ptr shortcut for reverse lookup
--v4 use IPv4 transport
--v6 use IPv6 transport
--tcp TCP mode
--ignore Don't revert to TCP for TC responses
-e --edns use EDNS0
-b --bufsize <size> set EDNS0 Max UDP packet size
--subnet <addr/len> send EDNS Client Subnet option
-s --sort sort RRs
-r --read <file> read packet from file
-f <file> same as -r
-w --write <file> write answer packet to file
--write-request <file> write request packet to file
-v --version print version and exit
-h --help print help and exit
Run tdig --help for the in-binary reference.
dig (BIND) |
tdig |
|
|---|---|---|
| Output format | dig-style sections | dig-style sections (compatible) |
| Default server | /etc/resolv.conf |
8.8.8.8 |
| Distribution | OS package | Standalone Bakeware binary or escript |
| Runtime | C, no runtime needed | Erlang/Elixir runtime (escript) or self-contained (Bakeware) |
| EDNS Client Subnet | +subnet= |
--subnet |
| TCP forcing | +tcp |
--tcp |
| Reverse lookup | -x |
-x |
| Packet I/O | external tools | Built-in --read / --write |
If you already have BIND's dig, you don't need tdig. It's useful when:
- you want a single self-contained binary you can copy to a minimal container or jump host
- you want to embed DNS lookups in Elixir tooling and share parsing/serialization with
tenbin_dns - you want raw packet read/write without piping through
tcpdumpordnsperf
- Bakeware binary: no runtime requirement
- escript / source build: Elixir 1.17 or later, Erlang/OTP 27 or later. CI exercises the matrix Elixir 1.17.3 / OTP 27.3.4.4 (LTS) and Elixir 1.19.5 / OTP 28.5 (latest) via the shared reusable workflow
smkwlab/.github/.github/workflows/elixir-ci.yml@v1. - Git is needed for
mix deps.getbecausetenbin_dnsis fetched from a git tag
tdig is a thin query pipeline: command-line arguments are parsed into a single options map, that map drives DNS resolution, and the wire response is parsed and printed in dig-style sections. The same entry point (Tdig.CLI.main/1) backs both the escript and Bakeware builds.
graph LR
argv["argv<br/>(command-line args)"] --> cli["Tdig.CLI<br/>(parse_args: options + positional + EDNS/PTR)"]
cli --> resolve["Tdig.resolve<br/>(orchestration)"]
resolve --> build["DNSpacket.create<br/>(build query via tenbin_dns)"]
build --> send["send_server<br/>(UDP / TCP send + recv)"]
send --> parse["DNSpacket.parse<br/>(decode response via tenbin_dns)"]
parse --> disp["disp_response<br/>(dig-style sections + summary)"]
disp -. "TC flag: retry over TCP" .-> resolve
read["--read file"] -. "skip network" .-> parse
Legend and notes:
- Solid arrows trace the main request/response data flow; dotted arrows are conditional side paths.
Tdig.CLI.parse_args/1turnsargvinto an options map: it parses switches and positional[@server] host [type] [class]arguments, applies defaults (server8.8.8.8, typeA, classIN, port53), auto-selects IPv4/IPv6 from the server address, and handles EDNS0 and-xreverse-lookup rewriting.process/1short-circuits--version/--helpbefore reaching resolution.Tdig.resolve/1builds the query withtenbin_dns(DNSpacket.create/1), sends it over UDP or TCP via thesocketlibrary, then parses and renders the reply (DNSpacket.parse/1→disp_response/3).- TC retry (dotted): the truncation flag is inspected only after the response is parsed —
disp_response/3callscheck_tc_flag/2, which re-entersresolve/1in TCP mode unless--ignoreis set. --read(dotted): a saved packet file is fed straight into the parse/display stage, bypassing the network entirely.--write/--write-request(not shown) persist the raw answer / query packets along the main path.- The two build modes (escript vs. Bakeware) share this exact pipeline; they differ only in packaging, not in query handling.
mix deps.get
mix test # ExUnit tests for CLI parsing
mix credo --strict # static code analysis
mix dialyzer # type analysis (first run is slow — builds the PLT)
mix format # code formatterLefthook is configured in lefthook.yml to run mix format + mix test + mix credo --strict on each commit. Activate it with one-time setup:
brew install lefthook # or your preferred install method
lefthook install # installs the .git/hooks/pre-commit shimSkip the hooks with LEFTHOOK=0 git commit ... if needed.
For repo-level context (architecture, build modes, ecosystem positioning) see CLAUDE.md.
BSD 3-Clause License. See LICENSE for the full text.
Copyright (c) 2021, Toshihiko SHIMOKAWA.