mhost 0.10.0

Fast, async DNS lookup library and CLI -- modern dig/host replacement with parallel multi-server queries, DoH, DoT, subdomain discovery, and zone verification
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
<div align="center">
  <h1><img src="docs/images/logo.png" alt="mhost" /> mhost</h1>
  <p><strong>More than host</strong> -- a modern, high-performance DNS Swiss Army knife and Rust library.</p>
  <p>
    <a href="https://github.com/lukaspustina/mhost/actions/workflows/ci.yml"><img src="https://github.com/lukaspustina/mhost/actions/workflows/ci.yml/badge.svg" alt="CI build" /></a>
    <a href="https://crates.io/crates/mhost"><img src="https://img.shields.io/crates/v/mhost.svg" alt="mhost on crates.io" /></a>
    <a href="https://docs.rs/mhost"><img src="https://docs.rs/mhost/badge.svg" alt="Documentation on docs.rs" /></a>
    <a href="https://github.com/lukaspustina/mhost/releases"><img src="https://img.shields.io/github/release/lukaspustina/mhost.svg" alt="GitHub release" /></a>
    <img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="License: MIT" />
    <img src="https://img.shields.io/badge/license-Apache_2.0-blue.svg" alt="License: Apache 2.0" />
  </p>
  <p>
    <a href="#why-mhost">Features</a> |
    <a href="#quick-start">Quick Start</a> |
    <a href="#mdive--interactive-tui">mdive TUI</a> |
    <a href="#installation">Installation</a> |
    <a href="#using-mhost-as-a-rust-library">Library API</a>
  </p>
</div>

![Multi lookup for all available records of github.com.](docs/images/multi-lookup-all-records-github.png)

mhost queries many DNS servers in parallel and aggregates their answers. It supports UDP, TCP, DNS-over-TLS, and DNS-over-HTTPS, understands 20+ record types, and ships with 80+ pre-configured public resolvers. Beyond simple lookups it can profile an entire domain, discover subdomains, trace the delegation chain, validate your DNS configuration, check propagation, diff records across nameservers, and verify live DNS against a zone file -- all from a single binary.

**Two binaries, one toolkit:** `mhost` is a powerful CLI for scripts, pipelines, and quick one-liners. [`mdive`](#mdive--interactive-tui) is an interactive TUI that lets you explore DNS like a file manager -- drill into subdomains, discover hidden records, and chase references across domains, all without leaving your terminal.

## Why mhost?

Most DNS tools do one thing. `dig` does lookups. `subfinder` discovers subdomains. `dnschecker.org` checks propagation. `dog` gives you pretty output. **mhost does all of them** -- and they compose, because they share the same resolver engine, output formats, and server configurations.

| | dig | dog | doggo | q | subfinder | **mhost** |
|---|:---:|:---:|:---:|:---:|:---:|:---:|
| Multi-server parallel queries | | | | | | **80+ built-in** |
| UDP, TCP, DoT, DoH | UDP, TCP | DoT, DoH | DoT, DoH, DoQ | DoT, DoH, DoQ | | **all four** |
| Subdomain discovery | | | | | passive | **10+ strategies** |
| DNS configuration linting | | | | | | **13 checks** |
| Delegation trace (all servers per hop) | `+trace` (1/hop) | | | | | **parallel, all servers** |
| Propagation checking | | | | | | **across 6 providers** |
| Diff between nameservers / snapshots | | | | | | **live, file, or mixed** |
| Zone file verification (CI-ready) | | | | | | **BIND zone file support** |
| Interactive TUI | | | | | | **mdive** |
| JSON output | | | | | | **every command** |
| Reusable Rust library | | | | | | **async, builder API** |
| WHOIS / geolocation | | | | | | **IP-level** |

**Pro tip:** `alias host="mhost l"` and never look back.

## Quick Start

```sh
# Install (pick one)
brew install lukaspustina/mhost/mhost          # macOS
cargo install --features app mhost             # Rust toolchain (CLI only)
cargo install --features tui mhost             # Rust toolchain (CLI + TUI)
docker run lukaspustina/mhost:latest mhost l github.com   # Try without installing

# Look up github.com using your system nameservers
mhost l github.com

# Add 80+ public resolvers from 6 providers for broader results
mhost -p l github.com

# Get ALL record types + WHOIS info in one shot
mhost -p l --all -w github.com

# Validate your domain's DNS configuration
mhost -p check example.com

# Discover subdomains (CT logs, wordlists, NSEC walking, ...)
mhost -p discover example.com

# Verify live DNS matches your zone file (CI-friendly, non-zero on mismatch)
mhost verify example.com.zone

# Pipe to jq for scripting
mhost -q -p --output json l --all github.com \
  | jq '.lookups[] | .result.Response.records[]? | select(.type == "A") | .data.A'

# Or just dive in interactively
mdive github.com
```

## Who Is This For?

**DevOps / SRE** -- Verify DNS changes landed with `mhost verify`, check propagation across public resolvers with `mhost propagation`, diff before/after with JSON snapshots. Non-zero exit codes for CI/CD pipelines.

**Security professionals** -- Discover subdomains via 10+ strategies (CT logs, NSEC walking, AXFR attempts, wordlists, permutation). Validate DNSSEC chains, detect zone transfer exposure, check for open resolvers.

**DNS administrators** -- Lint your configuration against 13 best-practice checks. Trace the full delegation chain querying all servers at each hop. Compare records across nameserver sets to catch inconsistencies.

**Developers** -- Use mhost as a Rust library with an ergonomic async builder API. JSON output on every command for scripting. 80+ built-in resolvers so you never have to hardcode IPs.

## What Can mhost Do?

| Command | Alias | What it does |
|---------|-------|--------------|
| [`lookup`](#lookup) | `l` | Look up DNS records for a domain, IP address, or CIDR block |
| [`domain-lookup`](#domain-lookup) | `domain` | Profile a domain -- apex + 68 well-known subdomains in one operation |
| [`discover`](#discover) | `d` | Find subdomains using 10+ strategies (wordlists, CT logs, AXFR, NSEC walking, ...) |
| [`check`](#check) | `c` | Validate DNS configuration against 13 lints (SOA, NS, SPF, DMARC, DNSSEC, ...) |
| [`trace`](#trace) | `t` | Trace the delegation path from root servers, querying all servers at each hop |
| [`dnssec`](#dnssec) | -- | Visualize the DNSSEC trust chain from root to target domain |
| [`propagation`](#propagation) | `prop` | Check whether a DNS change has propagated across public resolvers |
| [`verify`](#verify) | `v` | Verify live DNS matches a BIND zone file -- catch drift before it bites |
| [`diff`](#diff) | -- | Compare DNS records between nameservers or JSON snapshots |
| [`info`](#info) | -- | Built-in reference for record types, TXT sub-types, and well-known subdomains |
| `server-lists` | -- | Download public nameserver lists for large-scale queries |
| `completions` | -- | Generate shell completions (bash, zsh, fish) |

---

## mdive -- Interactive TUI

While `mhost` is built for scripts and one-liners, `mdive` is built for humans. It's an interactive terminal UI that turns DNS exploration into something that actually feels good -- think "file manager for DNS." Type a domain, watch records stream in from multiple servers in real time, then drill into anything interesting.

```sh
mdive example.com                        # Dive right in
mdive -p example.com                     # Use 80+ public resolvers for broader coverage
mdive -s 8.8.8.8 -s 1.1.1.1 example.com # Pick your own nameservers
```

![mdive main view github.com.](docs/images/mdive-main-view-github.png)

**A live, sortable record table.** All DNS records for a domain -- apex plus dozens of well-known subdomains across 10 categories (email auth, TLS/DANE, SRV services, infrastructure, and more). Results stream in progressively as servers respond, with a real-time progress bar in the status line. Toggle between raw DNS wire format and human-readable values with a single keypress.

**Drill-down navigation.** See a CNAME pointing somewhere interesting? Press `l` to follow it. Found a subdomain in the results? Hit Enter to dive in. Every query is pushed onto a history stack, so Backspace takes you right back. It's like `cd` and `cd ..` but for DNS.

![mdive discovery empty github.com.](docs/images/mdive-discovery-view-empty-github.png)

**Five discovery strategies, one keypress away.** Press `d` to open the discovery panel, then launch any combination:

| Key | Strategy | What it does |
|-----|----------|--------------|
| `c` | CT Logs | Search Certificate Transparency logs via crt.sh |
| `w` | Wordlist | Brute-force 424 common subdomain names (with automatic wildcard filtering) |
| `s` | SRV Probing | Probe 22 well-known SRV service records |
| `t` | TXT Mining | Extract referenced domains from SPF includes and DMARC URIs |
| `p` | Permutation | Generate variations of already-discovered labels (dev-, staging-, -prod, ...) |
| `a` | All | Run everything at once |

![mdive discovery full github.com.](docs/images/mdive-discovery-view-full-github.png)

Discovered subdomains appear in the main table as they're found. Wildcard detection runs automatically to filter false positives.

**Built-in DNS health checks.** Press `c` to run best-practice lints against the current domain -- CNAME-at-apex detection, NS redundancy, SPF/DMARC validation, DNSSEC chain verification, HTTPS/SVCB mode checks, CAA coverage, TTL sanity, and more. Each result shows pass/warning/fail with a clear explanation.

**WHOIS and geolocation.** Press `w` and mdive fetches WHOIS data for every IP in your results -- AS numbers, network prefixes, organizations, countries, and geolocations. Handy for understanding where a domain's infrastructure actually lives.

**Server response dashboard.** Press `s` to see every nameserver that responded, sorted by latency -- protocol, response counts, error counts, and min/avg/max timing. The stats panel (`S`) shows a compact summary right in the status bar: record type distribution, query health, DNSSEC status, and response time ranges.

**Regex filtering.** Press `/` and type a pattern. Matches against record names, types, and values in real time. Quickly zero in on that one TXT record in a sea of results.

<details>
<summary><strong>Keybindings</strong></summary>

mdive uses vi-style navigation with a few extras:

| Key | Action | Key | Action |
|-----|--------|-----|--------|
| `j`/`k` | Move down/up | `i` | Enter domain query |
| `gg`/`G` | First/last row | `/` | Filter (regex) |
| `22gg` | Jump to line 22 | `C` | Clear filter |
| PgUp/PgDn | Scroll by 10 | `r` | Re-run query |
| Enter | Drill into subdomain | `h` | Toggle human view |
| `l`/Right | Follow value target | `S` | Toggle stats |
| Left/BS | Go back in history | Tab | Cycle grouping |
| `1`-`0` | Toggle categories | `a`/`n` | All/none categories |
| `o` | Record detail popup | `?` | Help |

</details>

<details>
<summary><strong>Category Toggles</strong></summary>

Records are organized into 10 categories. Toggle any with number keys, or press `a` for all / `n` for none:

| Key | Category | Key | Category |
|-----|----------|-----|----------|
| `1` | Email Auth (DMARC, SPF, ...) | `6` | Infrastructure (LDAP, Kerberos) |
| `2` | Email Services (IMAP, SMTP) | `7` | Modern Protocols (STUN, TURN) |
| `3` | TLS / DANE | `8` | Verification & Metadata |
| `4` | Communication (SIP, XMPP, Matrix) | `9` | Legacy |
| `5` | Calendar & Contacts (CalDAV) | `0` | Gaming |

Cycle the grouping mode with Tab: **Category** (default) -> **Record Type** -> **Name** -> **Server**.

</details>

<details>
<summary><strong>mdive CLI Options</strong></summary>

```
mdive [OPTIONS] [DOMAIN]

Options:
  -s, --nameserver <SPEC>          Add a nameserver (repeatable)
  -p, --predefined                 Add predefined public nameservers
      --predefined-filter <PROTO>  Filter predefined by protocol [udp, tcp, tls, https]
  -S, --no-system-lookups          Skip system nameservers
  -t, --timeout <SECS>             Query timeout [default: 5] (1-30)
  -4, --ipv4-only                  IPv4 only
  -6, --ipv6-only                  IPv6 only
  -h, --help                       Print help
```

</details>

<details>
<summary><strong>Building mdive</strong></summary>

mdive lives behind the `tui` feature flag to keep the default build lean:

```sh
cargo build --features tui         # Build both mhost and mdive
cargo run --bin mdive --features tui -- example.com
```

</details>

---

## CLI Use Cases

### Simple Lookup

```sh
mhost l github.com
```

![Default lookup for github.com.](docs/images/default-lookup-github.png)

This uses your system nameservers and queries the default record types (A, AAAA, CNAME, MX).

### More Nameservers, More Answers

```sh
mhost -p l github.com
```

![Default lookup with predefined servers for github.com.](docs/images/default-lookup-predefined-servers-github.png)

`-p` adds mhost's predefined public nameservers from Cloudflare, Google, Quad9, Mullvad, Wikimedia, and DNS4EU. More servers means more confidence that you're seeing the full picture.

### Go Big -- Thousands of Nameservers

```sh
mhost server-lists public-dns -o servers.txt
mhost --limit 6000 --max-concurrent-servers 1000 --timeout 1 -f servers.txt l www.github.com
```

![Default lookup with servers list for github.com.](docs/images/default-lookup-servers-list-github.png)

Download a community-maintained list of public resolvers, then fire queries at all of them. These settings are intentionally aggressive -- mhost defaults are much more cautious.

### All Four Protocols at Once

```sh
mhost \
  -s 1.1.1.1 \
  -s tcp:1.1.1.1 \
  -s tls:1.1.1.1:853,tls_auth_name=cloudflare-dns.com \
  -s https:1.1.1.1:443,tls_auth_name=cloudflare-dns.com,name=Cloudflare \
  l github.com
```

![Default lookup with all protocols for github.com.](docs/images/default-lookup-all-protocols-github.png)

Nameserver spec format: `protocol:host:port,tls_auth_name=hostname,name=label`

### Profile an Entire Domain

```sh
mhost -p domain-lookup example.com         # ~42 well-known entries
mhost -p domain-lookup --all example.com   # ~68 entries (extended set)
```

One command queries the apex plus dozens of well-known subdomains: email auth (DMARC, MTA-STS, BIMI, TLS-RPT), SRV services (IMAP, SMTP, CalDAV, XMPP, Matrix, ...), DANE/TLSA records, and more.

### Discover Subdomains

```sh
mhost -p d github.com
```

![Discover github.com.](docs/images/discover-github.png)

mhost chains 10+ discovery strategies automatically:

1. Standard DNS record lookups
2. Certificate Transparency logs (crt.sh)
3. TXT record mining for referenced domains
4. SRV service probing
5. Wildcard detection via random subdomain probes
6. Zone transfer (AXFR) attempts
7. NSEC walking
8. Wordlist brute force (424 built-in entries, or supply your own with `-w`)
9. Subdomain permutation on discovered names
10. Recursive discovery on found subdomains (`--depth 1..3`)
11. Reverse DNS lookups on discovered IPs

You can also explore a domain's autonomous systems:

```sh
mhost -p l --all -w github.com
mhost -p l --all 140.82.121.0/24
```

![Discover AS of github.com.](docs/images/discover-as-github.png)

### Validate DNS Configuration

```sh
mhost -p c github.com
```

![Check github.com.](docs/images/check-github.png)

The `check` command runs 13 lints against a domain's DNS records:

| Lint | What it checks |
|------|---------------|
| SOA | Start of Authority record validity |
| NS | NS delegation, lame delegation, network diversity |
| CNAME | CNAME usage rules |
| MX | Null MX, duplicate preferences, target resolution |
| SPF | SPF record syntax and policy |
| DMARC | DMARC policy validation |
| CAA | Certificate Authority Authorization tags |
| TTL | TTL consistency across records |
| DNSSEC | DNSSEC presence and configuration |
| HTTPS/SVCB | Service binding record well-formedness |
| AXFR | Zone transfer exposure |
| Open Resolver | Open resolver detection |
| Delegation | Delegation consistency |

Disable any lint individually: `--no-soa`, `--no-spf`, `--no-dnssec`, etc.

### Trace the Delegation Chain

```sh
mhost trace example.com
mhost trace -t AAAA --show-all-servers example.com
```

![trace-github.com.](docs/images/trace-github.png)

Unlike `dig +trace` which queries one server per hop, mhost's `trace` command queries **all nameservers at each delegation level in parallel**. It detects referral divergence (where different root/TLD servers disagree), reports per-server latency, and resolves missing glue records automatically.

### Check DNS Propagation

```sh
mhost -p propagation example.com
mhost -p prop --all example.com
```

![propagation-github.com.-1](docs/images/propagation-github_1.png)
![propagation-github.com.-2](docs/images/propagation-github_2.png)

After making a DNS change, check whether it has reached all the major public resolvers. Uses the predefined nameserver set (Cloudflare, Google, Quad9, Mullvad, Wikimedia, DNS4EU).

### Diff Records Between Nameservers

```sh
mhost diff --left 8.8.8.8 --right 1.1.1.1 example.com
mhost diff --left 8.8.8.8 --right 1.1.1.1 --all example.com
```

Compare what two different nameserver sets return for the same domain. Useful for debugging inconsistencies or verifying migrations.

You can also diff against saved JSON snapshots for migration validation and change tracking:

```sh
# Save a snapshot
mhost lookup -s 8.8.8.8 -q -t A,AAAA,MX example.com --output json > before.json

# Later, diff snapshot against live DNS
mhost diff --left-from-file before.json --right 1.1.1.1 example.com

# Or compare two snapshots offline
mhost diff --left-from-file before.json --right-from-file after.json example.com
```

### Verify DNS Against a Zone File

```sh
mhost verify example.com.zone
```

Pushed a DNS change and wondering if it actually landed? `verify` reads a BIND zone file -- the most widely used format for DNS zone specification -- compares every record against live DNS, and tells you exactly what matches, what's missing, and what showed up unexpectedly. Non-zero exit code on mismatch, so it drops straight into CI pipelines.

```sh
# Verify against your authoritative nameserver
mhost -s ns1.example.com verify example.com.zone

# Check propagation to public resolvers
mhost -p verify example.com.zone

# Strict mode: also flag TTL differences
mhost verify --strict example.com.zone

# Only check mail-related records
mhost verify --only-type MX,TXT example.com.zone

# CI one-liner
mhost verify zones/example.com.zone || notify_team "DNS drift detected"
```

**Don't have a zone file?** BIND zone format is the universal lingua franca of DNS -- almost every DNS provider can export to it, and tools like `dig`, `nsd`, and BIND itself all speak it natively. If your DNS lives in Terraform, Pulumi, CloudFormation, or any other IaC tool, just ask an LLM to convert the state to a BIND zone file. For example, feed `terraform show -json` output to your favorite LLM and ask for a zone file -- it takes seconds and gives you a portable, version-controllable source of truth you can verify against at any time.

### Look Up Record Type Info

```sh
mhost info            # List all supported types
mhost info MX         # Details about MX records
mhost info SPF        # Details about SPF TXT sub-type
mhost info _dmarc     # Details about the _dmarc well-known subdomain
```

Built-in reference with summaries, details, and RFC references for every supported record type, TXT sub-type, and well-known subdomain.

---

## Installation

### Homebrew (macOS)

```sh
brew install lukaspustina/mhost/mhost
```

### Docker

```sh
docker run lukaspustina/mhost:latest mhost l example.com
```

### Debian / Ubuntu

Download the `.deb` from the [latest GitHub Release](https://github.com/lukaspustina/mhost/releases):

```sh
dpkg -i mhost.deb
```

### Redhat / Fedora

Download the `.rpm` from the [latest GitHub Release](https://github.com/lukaspustina/mhost/releases):

```sh
rpm -i mhost.rpm
```

### Cargo (Rust developers)

```sh
cargo install --features app mhost       # CLI only
cargo install --features tui mhost       # CLI + interactive TUI (mdive)
```

### From Source

```sh
git clone https://github.com/lukaspustina/mhost
cd mhost
make install                             # CLI only
cargo install --features tui --path .    # CLI + TUI
```

---

## Using mhost as a Rust Library

mhost is also a reusable library. Build without the CLI:

```sh
cargo build --lib   # no CLI dependencies
```

### Builder API (recommended)

```rust
use mhost::resolver::{ResolverGroupBuilder, MultiQuery};
use mhost::resolver::lookup::Uniquify;
use mhost::nameserver::predefined::PredefinedProvider;
use mhost::RecordType;
use std::time::Duration;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let resolvers = ResolverGroupBuilder::new()
        .system()
        .predefined(PredefinedProvider::Google)
        .timeout(Duration::from_secs(3))
        .build()
        .await?;

    let query = MultiQuery::multi_record(
        "example.com",
        vec![RecordType::A, RecordType::AAAA],
    )?;
    let lookups = resolvers.lookup(query).await?;
    let a_records = lookups.a().unique().to_owned();
    println!("A records: {:?}", a_records);
    Ok(())
}
```

### Manual Construction

```rust
use mhost::nameserver::NameServerConfig;
use mhost::resolver::{MultiQuery, Resolver, ResolverConfig, ResolverGroup};
use mhost::resolver::lookup::Uniquify;
use mhost::RecordType;
use std::net::SocketAddr;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mut resolvers = ResolverGroup::from_system_config(Default::default()).await?;

    let sock_addr: SocketAddr = "8.8.8.8:53".parse()?;
    let config = ResolverConfig::new(NameServerConfig::udp(sock_addr));
    let google = Resolver::new(config, Default::default()).await?;
    resolvers.add(google);

    let query = MultiQuery::multi_record(
        "example.com",
        vec![RecordType::A, RecordType::AAAA, RecordType::TXT],
    )?;
    let lookups = resolvers.lookup(query).await?;
    let a_records = lookups.a().unique().to_owned();
    println!("A records: {:?}", a_records);
    Ok(())
}
```

### Running DNS Lints

The core library includes 9 pure lint checks (CAA, CNAME, DMARC, DNSSEC, HTTPS/SVCB, MX, NS, SPF, TTL) that analyse `Lookups` results for common misconfigurations — no `app-*` features required:

```rust
use mhost::resolver::{ResolverGroupBuilder, MultiQuery};
use mhost::lints::{check_spf, check_caa, CheckResult};
use mhost::RecordType;
use std::time::Duration;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let resolvers = ResolverGroupBuilder::new()
        .system()
        .timeout(Duration::from_secs(3))
        .build()
        .await?;

    let query = MultiQuery::multi_record(
        "example.com",
        vec![RecordType::TXT, RecordType::CAA],
    )?;
    let lookups = resolvers.lookup(query).await?;

    for result in check_spf(&lookups).iter().chain(check_caa(&lookups).iter()) {
        match result {
            CheckResult::Ok(msg) => println!("  OK: {}", msg),
            CheckResult::Warning(msg) => println!("  WARN: {}", msg),
            CheckResult::Failed(msg) => println!("  FAIL: {}", msg),
            CheckResult::NotFound() => println!("  (not found)"),
        }
    }
    Ok(())
}
```

See [docs.rs/mhost](https://docs.rs/mhost) for the full API documentation.

---

## JSON Output

Every command supports `--output json` for machine-readable output. Combine with `-q` (quiet) to suppress status messages:

```sh
mhost -q --output json l --all example.com | jq .
mhost -q --output json trace example.com | jq '.hops[] | .zone_name'
mhost -q --output json c example.com | jq '.results[] | select(.status != "Ok")'
```

---

<details>
<summary><strong>Global Options</strong></summary>

mhost has a rich set of options that apply to all commands:

```
Nameserver selection:
  -s, --nameserver <SPEC>           Add a nameserver (IP, or protocol:host:port,...)
  -p, --predefined                  Add predefined public nameservers
      --predefined-filter <PROTO>   Filter predefined by protocol [udp, tcp, tls, https]
      --list-predefined             Show all predefined nameservers
  -f, --nameservers-from-file <F>   Load nameservers from file
      --no-system-nameservers       Skip /etc/resolv.conf nameservers
  -S, --no-system-lookups           Skip system nameservers for lookups

IP version filtering:
  -4, --ipv4-only                   Only use IPv4 nameservers and return IPv4 results
  -6, --ipv6-only                   Only use IPv6 nameservers and return IPv6 results

Concurrency & resilience:
      --limit <N>                   Max nameservers to query [default: 100] (1-10000)
      --max-concurrent-servers <N>  Max concurrent nameservers [default: 10] (1-100)
      --max-concurrent-requests <N> Max concurrent requests per server [default: 5] (1-50)
      --retries <N>                 Retries per server [default: 0] (0-10)
      --timeout <SECS>              Response timeout [default: 5] (1-300)
      --continue-on-error           Continue on server errors
      --continue-on-timeout         Continue on server timeouts
      --wait-multiple-responses     Wait for additional responses until timeout

Output:
  -o, --output <FORMAT>             Output format: summary or json [default: summary]
  -q, --quiet                       Only print results (no status messages)
      --no-color                    Disable colored output
      --ascii                       ASCII-only output (no Unicode symbols)
      --show-errors                 Show error counts
  -v                                Increase verbosity (repeat for more)
```

</details>

<details>
<summary><strong>Command Reference</strong></summary>

### Lookup

```sh
mhost l [OPTIONS] <DOMAIN | IP | CIDR | SERVICE SPEC>
```

```
  -t, --record-type <TYPE>   Record types [default: A,AAAA,CNAME,MX]
      --all                  Query all record types
  -s, --service              Parse argument as SRV service spec
  -w, --whois                Include WHOIS information for A/AAAA/PTR results
```

Accepts domain names, IPv4/IPv6 addresses, CIDR blocks (reverse lookup of all IPs in range), and SRV service specs (`smtp:tcp:example.com` or `dns:udp:example.com`).

### Domain Lookup

```sh
mhost domain-lookup [OPTIONS] <DOMAIN>
```

```
      --all                     Include extended well-known subdomains (~68 total)
  -p, --show-partial-results    Show results incrementally
```

Queries the apex plus well-known subdomains covering:
- **Apex**: A, AAAA, MX, NS, SOA, CAA, HTTPS, TXT, CNAME, SVCB, NAPTR, SSHFP
- **Email auth**: DMARC, MTA-STS, TLS-RPT, BIMI
- **Email services**: submission, IMAP, POP3, autodiscover (SRV)
- **TLS/DANE**: TLSA for ports 443, 25, 587, 993, etc.
- **Communication**: SIP, XMPP, Matrix (SRV)
- **Calendar/Contacts**: CalDAV, CardDAV (SRV)
- **Infrastructure**: LDAP, Kerberos (SRV)
- **Modern protocols**: STUN, TURN (SRV)
- **Verification**: ACME challenge, AT Protocol, DNSLink, domain verification TXT records

### Discover

```sh
mhost d [OPTIONS] <DOMAIN>
```

```
  -s, --subdomains-only            Show only subdomains
  -w, --wordlist-from-file <F>     Custom wordlist file
      --no-ct-logs                 Skip Certificate Transparency queries
      --depth <N>                  Recursive discovery depth [default: 0] (0-3)
      --rnd-names-number <N>       Random names for wildcard check [default: 3] (1-20)
      --rnd-names-len <N>          Random name length [default: 32] (8-128)
  -p, --show-partial-results       Show results incrementally
```

### Check

```sh
mhost c [OPTIONS] <DOMAIN>
```

```
  -p, --show-partial-results         Show results after each lint
  -i, --show-intermediate-lookups    Show all DNS lookups made during checks
      --no-soa                       Disable SOA check
      --no-ns                        Disable NS delegation check
      --no-cnames                    Disable CNAME lint
      --no-mx                        Disable MX check
      --no-spf                       Disable SPF check
      --no-dmarc                     Disable DMARC check
      --no-caa                       Disable CAA check
      --no-ttl                       Disable TTL check
      --no-dnssec                    Disable DNSSEC check
      --no-https-svcb                Disable HTTPS/SVCB check
      --no-axfr                      Disable AXFR check
      --no-open-resolver             Disable open resolver check
      --no-delegation                Disable delegation check
```

### Trace

```sh
mhost trace [OPTIONS] <DOMAIN>
```

```
  -t, --record-type <TYPE>       Record type to query [default: A]
      --max-hops <N>             Maximum delegation hops [default: 10] (1-20)
      --show-all-servers         Show per-server details (IP, latency, outcome)
  -p, --show-partial-results     Show each hop as it completes
```

### Propagation

```sh
mhost -p propagation [OPTIONS] <DOMAIN>
```

```
  -t, --record-type <TYPE>       Record types [default: A,AAAA,CNAME,MX]
      --all                      Check all record types
  -p, --show-partial-results     Show results incrementally
```

### Diff

```sh
mhost diff [OPTIONS] <DOMAIN>
```

```
      --left <SERVER>            Left nameserver(s) (repeatable)
      --right <SERVER>           Right nameserver(s) (repeatable)
      --left-from-file <FILE>    Load left side from a JSON snapshot file (from lookup --output json)
      --right-from-file <FILE>   Load right side from a JSON snapshot file (from lookup --output json)
  -t, --record-type <TYPE>       Record types [default: A,AAAA,CNAME,MX,NS,SOA,TXT]
      --all                      Compare all record types
```

Each side requires either `--left`/`--right` (live query) or `--left-from-file`/`--right-from-file` (snapshot). You can mix: one side live, the other from file.

### Verify

```sh
mhost verify [OPTIONS] <ZONE_FILE>
```

```
  <ZONE_FILE>                      Path to BIND zone file (required)
      --origin <NAME>              Override zone origin ($ORIGIN)
      --strict                     Report TTL differences as mismatches
      --only-type <TYPE>           Only verify these record types (repeatable, comma-delimited)
      --ignore-type <TYPE>         Skip these record types (repeatable, comma-delimited)
      --ignore-extra               Suppress extra-record reporting (live records not in zone file)
      --ignore-soa                 Skip SOA serial comparison
```

By default, SOA, DNSSEC records (RRSIG, DNSKEY, DS, NSEC, NSEC3, NSEC3PARAM), and apex NS records are skipped. Wildcard records are reported as skipped since they can't be verified via simple lookups. Exit code `0` means all records verified; non-zero means mismatches or missing records.

### DNSSEC

```sh
mhost dnssec [OPTIONS] <DOMAIN>
```

```
      --max-hops <N>             Maximum delegation hops [default: 10] (1-20)
  -p, --show-partial-results     Show each delegation level as it completes
```

Walks the DNS delegation chain from root servers to the target domain, querying DNSKEY, DS, and RRSIG records at each level. Renders a color-coded trust chain tree showing key roles (KSK/ZSK), algorithm strength, signature expiry, and DS-to-DNSKEY linkage.

</details>

<details>
<summary><strong>Predefined Nameservers</strong></summary>

mhost ships with 84 configurations across 6 providers. All use **unfiltered endpoints** (no content filtering or blocking). Each provider is available over UDP, TCP, DoT, and DoH.

| Provider | Primary IPv4 | Secondary IPv4 | IPv6 | TLS/HTTPS Hostname |
|----------|-------------|---------------|------|-------------------|
| Cloudflare | 1.1.1.1 | 1.0.0.1 | 2606:4700:4700::1111 / ::1001 | cloudflare-dns.com |
| Google | 8.8.8.8 | 8.8.4.4 | 2001:4860:4860::8888 / ::8844 | dns.google |
| Quad9 | 9.9.9.10 | 149.112.112.10 | 2620:fe::10 / ::fe:10 | dns10.quad9.net |
| Mullvad | 194.242.2.2 | 193.19.108.2 | 2a07:e340::2 | dns.mullvad.net |
| Wikimedia | 185.71.138.138 | 185.71.139.139 | 2001:67c:930::1 / ::2 | wikimedia-dns.org |
| DNS4EU | 185.134.197.54 | 185.134.196.54 | -- | unfiltered.joindns4.eu |

Use `mhost --list-predefined` to see every configuration.

</details>

<details>
<summary><strong>Supported Record Types</strong></summary>

| Type | Description | Type | Description |
|------|-------------|------|-------------|
| A | IPv4 address | NS | Name server |
| AAAA | IPv6 address | OPENPGPKEY | OpenPGP public key |
| ANAME | ANAME / ALIAS | PTR | Pointer (reverse DNS) |
| ANY | Query all types | SOA | Start of Authority |
| CAA | CA Authorization | SRV | Service locator |
| CNAME | Canonical name | SSHFP | SSH fingerprint |
| HINFO | Host information | SVCB | Service binding |
| HTTPS | HTTPS service binding | TLSA | TLS/DANE certificate |
| MX | Mail exchange | TXT | Text record |
| NAPTR | Naming Authority Pointer | DNSSEC | DNSKEY, DS, RRSIG, NSEC, ... |

</details>

---

## Changelog

See the [CHANGELOG](CHANGELOG.md) for a full release history.

## Limitations

- Only DNS class `IN` is supported.

## Architecture Design Records

The [docs/adr/](docs/adr/) directory contains Architecture Decision Records for the project.

## Thanks

Thanks to [Benjamin Fry](https://github.com/bluejekyll) for [Hickory DNS](https://github.com/hickory-dns/hickory-dns) (formerly Trust-DNS), which does all the heavy DNS lifting.

## License

MIT or Apache-2.0, at your option.

## Postcardware

You're free to use `mhost`. If you find it useful, I would highly appreciate you sending me a postcard from your hometown mentioning how you use `mhost`. My work address is

```
Lukas Pustina
CenterDevice GmbH
Rheinwerkallee 3
53227 Bonn
Germany
```