Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions .github/FUNDING.yml

This file was deleted.

4 changes: 4 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,7 @@ jobs:
publish:
uses: NicTool/.github/.github/workflows/publish.yml@main
secrets: inherit
permissions:
contents: read
packages: write
id-token: write
7 changes: 2 additions & 5 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,11 @@ pids
*.seed
*.pid.lock

# Directory for instrumented libs generated by jscoverage/JSCover
# Test coverage reporters
lib-cov

# Coverage directory used by tools like istanbul
coverage
*.lcov

# nyc test coverage
lcov.info
.nyc_output

# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
Expand Down
2 changes: 1 addition & 1 deletion .release
Submodule .release updated 2 files
+0 −12 js/bots.txt
+10 −12 js/contributors.cjs
25 changes: 25 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,33 @@

### Unreleased

### [1.2.0] - 2026-04-19

Comment thread
msimerson marked this conversation as resolved.
- feat(index): add tokenizeQuoted, ~10x faster char/quote/comments
- feat(zone): this.recordKeys Set for O(1) duplicate detection
- feat(maradns): parse CAA records from RAW
- feat(bind): support generic record format, RFC 3597
- feat(bind): support recursive INCLUDE directive
- fix(dns-zone): in invalid arg, show usage
- fix(bind): better error message when SOA is missing fields
- fix(bind): don't limit TTL to 5 num chars
- fix(bind): add path traversal guard
- fix: two typos uncovered by testing
- change: replace some regex with native string functions
- change: lib/{bind,maradns,tinydns}.js — added ctx parameter (default: zoneOpts)
- change: class ZONE no longer extends Map
- change: iterate directly on strings
- change: serialByDate, remove first unused param (start)
- change: str.substr() -> str.slice
- deps: bump versions to latest
- doc(README): wordsmithed
- test: convert test runner & coverage to node:test
- test: add tinydns end-to-end coverage

### [1.1.8] - 2026-04-07

- style: replace str.substring (deprecated) with slice
- style: use optional chaining

### [1.1.7] - 2026-03-13

Expand Down Expand Up @@ -197,3 +221,4 @@
[1.1.6]: https://github.com/NicTool/dns-zone/releases/tag/v1.1.6
[1.1.7]: https://github.com/NicTool/dns-zone/releases/tag/v1.1.7
[1.1.8]: https://github.com/NicTool/dns-zone/releases/tag/v1.1.8
[1.2.0]: https://github.com/NicTool/dns-zone/releases/tag/v1.2.0
2 changes: 1 addition & 1 deletion CONTRIBUTORS.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

This handcrafted artisanal software is brought to you by:

| <img height="80" src="https://avatars.githubusercontent.com/u/261635?v=4"><br><a href="https://github.com/msimerson">msimerson</a> (<a href="https://github.com/NicTool/dns-zone/commits?author=msimerson">28</a>) |
| <img height="80" src="https://avatars.githubusercontent.com/u/261635?v=4"><br><a href="https://github.com/msimerson">msimerson</a> (<a href="https://github.com/NicTool/dns-zone/commits?author=msimerson">29</a>) |
| :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: |

<sub>this file is generated by [.release](https://github.com/msimerson/.release).
Expand Down
210 changes: 87 additions & 123 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,35 @@

# dns-zone

DNS zone tool
Import, export, and validate DNS zone data across common nameserver formats.

## SYNOPSIS

Import and export DNS data to and from common nameserver formats. Normalize, validate, and optionally apply transformations at the same time.
Parse and emit DNS zone data in BIND, tinydns, and maradns formats. Normalize (expand `@`, inherit TTLs, fully-qualify names), validate (RFC 1034/1035/2181/4035 coexistence rules), and convert between formats.

## INSTALLATION

```
npm install -g @nictool/dns-zone # CLI
npm install @nictool/dns-zone # library
```
➜ ./bin/dns-zone.js -h

## SUPPORTED FORMATS

| Format | Import | Export |
| ------- | :----: | :-----------: |
| BIND | yes | yes |
| tinydns | yes | yes |
| maradns | yes | yes |
| JSON | yes | yes |
| human | n/a | yes (default) |

BIND `$INCLUDE` directives are followed (paths are confined to the source file's directory).
Comment thread
msimerson marked this conversation as resolved.

## CLI

```
➜ dns-zone -h

+-+-+-+ +-+-+-+-+
|D|N|S| |Z|O|N|E|
Expand Down Expand Up @@ -39,154 +60,97 @@ Misc

-v, --verbose Show status messages during processing
-h, --help Display this usage guide

Examples

1. BIND file to human ./bin/dns-zone.js -i bind -f isi.edu
2. BIND file to tinydns ./bin/dns-zone.js -i bind -f isi.edu -e tinydns
3. tinydns file to BIND ./bin/dns-zone.js -i tinydns -f data -e bind

Project home: https://github.com/NicTool/dns-zone
```

## bin/dns-zone.js
### Examples

#### import from STDIN to human
Default human output:

```
➜ cat example.com| ./bin/dns-zone.js -i bind -f - --origin=example.com.
➜ cat example.com | dns-zone -i bind -f - --origin=example.com.
$ORIGIN example.com.
$TTL 3600
example.com. 3600 SOA ns.example.com. username.example.com. 2020091025 7200 3600 1209600 3600
example.com. 3600 NS ns.example.com.
example.com. 3600 NS ns.somewhere.example.
example.com. 3600 MX 10 mail.example.com.
example.com. 3600 MX 20 mail2.example.com.
example.com. 3600 MX 50 mail3.example.com.
example.com. 3600 A 192.0.2.1
example.com. 3600 AAAA 2001:0db8:0010:0000:0000:0000:0000:0001
ns.example.com. 3600 A 192.0.2.2
ns.example.com. 3600 AAAA 2001:0db8:0010:0000:0000:0000:0000:0002
www.example.com. 3600 CNAME example.com.
wwwtest.example.com. 3600 CNAME www.example.com.
mail.example.com. 3600 A 192.0.2.3
mail2.example.com. 3600 A 192.0.2.4
mail3.example.com. 3600 A 192.0.2.5
```

#### from bind file to bind
Convert BIND → tinydns:

```
➜ ./bin/dns-zone.js -i bind -e bind -f isi.edu --origin=isi.edu.
isi.edu. 60 IN SOA venera.isi.edu. action\.domains.isi.edu. 20 7200 600 3600000 60
isi.edu. 60 IN NS a.isi.edu.
isi.edu. 60 IN NS venera.isi.edu.
isi.edu. 60 IN NS vaxa.isi.edu.
isi.edu. 60 IN MX 10 venera.isi.edu.
isi.edu. 60 IN MX 20 vaxa.isi.edu.
a.isi.edu. 60 IN A 26.3.0.103
venera.isi.edu. 60 IN A 10.1.0.52
venera.isi.edu. 60 IN A 128.9.0.32
vaxa.isi.edu. 60 IN A 10.2.0.27
vaxa.isi.edu. 60 IN A 128.9.0.33
➜ dns-zone --origin=isi.edu. -i bind -e tinydns -f isi.edu
Zisi.edu:venera.isi.edu:action\.domains.isi.edu:20:7200:600:3600000:60:60::
&isi.edu::a.isi.edu:60::
@isi.edu::venera.isi.edu:10:60::
+a.isi.edu:26.3.0.103:60::
```

#### from bind to bind (relative)
Render BIND relative to origin (hide ttl/class/origin/same-owner):

```
➜ ./bin/dns-zone.js -i bind -e bind -f isi.edu --ttl=60 \
--origin=isi.edu. --hide-ttl --hide-class --hide-origin --hide-same-owner
@ 60 IN SOA venera action\.domains 20 7200 600 3600000 60
NS a
NS venera
NS vaxa
MX 10 venera
MX 20 vaxa
a A 26.3.0.103
venera A 10.1.0.52
A 128.9.0.32
vaxa A 10.2.0.27
A 128.9.0.33
➜ dns-zone -i bind -e bind -f isi.edu --origin=isi.edu. \
--hide-ttl --hide-class --hide-origin --hide-same-owner
@ SOA venera action\.domains 20 7200 600 3600000 60
NS a
NS venera
a A 26.3.0.103
venera A 10.1.0.52
A 128.9.0.32
```

#### from bind to tinydns
## PROGRAMMATIC API

```
➜ ./bin/dns-zone.js --origin=isi.edu. -i bind -e tinydns -f isi.edu
Zisi.edu:venera.isi.edu:action\.domains.isi.edu:20:7200:600:3600000:60:60::
&isi.edu::a.isi.edu:60::
&isi.edu::venera.isi.edu:60::
&isi.edu::vaxa.isi.edu:60::
@isi.edu::venera.isi.edu:10:60::
@isi.edu::vaxa.isi.edu:20:60::
+a.isi.edu:26.3.0.103:60::
+venera.isi.edu:10.1.0.52:60::
+venera.isi.edu:128.9.0.32:60::
+vaxa.isi.edu:10.2.0.27:60::
+vaxa.isi.edu:128.9.0.33:60::
```
```js
import { bind, json, maradns, tinydns } from '@nictool/dns-zone'

// BIND zone file → array of RR objects
const rrs = await bind.parseZoneFile(zoneText, { origin: 'example.com.', ttl: 3600 })

#### from bind to maradns
// BIND zone file with $INCLUDE directives (pass file path so includes can be resolved)
const rrs = await bind.parseZoneFile(zoneText, { file: '/path/to/zone.db' })

// JSON (NDJSON, one RR per line — same format as -e json output)
const rrs = await json.parseZoneFile(ndjsonText)

// tinydns data file
const rrs = await tinydns.parseData(dataText)

// maradns csv2
const rrs = await maradns.parseZoneFile(csv2Text, { origin: 'example.com.' })
```
./bin/dns-zone.js -i bind -e maradns -f isi.edu --origin=isi.edu.
isi.edu. SOA venera.isi.edu. action\.domains.isi.edu. 20 7200 600 3600000 60 ~
isi.edu. +60 NS a.isi.edu. ~
isi.edu. +60 NS venera.isi.edu. ~
isi.edu. +60 NS vaxa.isi.edu. ~
isi.edu. +60 MX 10 venera.isi.edu. ~
isi.edu. +60 MX 20 vaxa.isi.edu. ~
a.isi.edu. +60 A 26.3.0.103 ~
venera.isi.edu. +60 A 10.1.0.52 ~
venera.isi.edu. +60 A 128.9.0.32 ~
vaxa.isi.edu. +60 A 10.2.0.27 ~
vaxa.isi.edu. +60 A 128.9.0.33 ~

Each RR is a [`@nictool/dns-resource-record`](https://github.com/NicTool/dns-resource-record) instance; use `rr.toBind()`, `rr.toTinydns()`, `rr.toMaraDNS()` to emit in other formats.

Zone-level validation:

```js
import ZONE from '@nictool/dns-zone/lib/zone.js'

const z = new ZONE({ origin: 'example.com.', RR: rrs })
if (z.errors.length) console.error(z.errors)
```

## VALIDATION

DNS zones have numerous rules regarding the records that can exist in them. Examples:

- [ ] serial numbers must increment when changes are made
- [x] multiple identical RRs are not allowed - RFC 2181
- [x] CAA takes tag into account, SRV: port
- [x] RFC 2181: RR sets (identical label, class, type) must have identical TTL
- [x] multiple CNAMES with the same name are not allowed
- [x] CNAME label cannot coexist except for SIG,NXT,KEY,RRSIG,NSEC
- [ ] MX and NS records cannot point to CNAME

Etc, etc, etc..

This module will input a collection of [dns-resource-records](https://github.com/NicTool/dns-resource-record) and validate that all the zone records can coexist.

## TODO

- importing
- [x] write a bind zone file parser
- [x] write a tinydns data file parser
- [x] add BIND parsing for all RRs supported by dns-rr
- [x] write a maradns parser
- normalize BIND zone records
- [x] expand `@` to zone name
- [x] empty names are same as previous RR record
- [x] missing TTLs inherit zone TTL, or zone MINIMUM
- expand hostnames to FQDNs
- [x] ALL: name field
- [x] MX: exchange
- [x] CNAME: cname,
- [x] SOA: mname, rname,
- [x] NS,PTR: dname
- [x] suppress hostname when identical to previous RR
- [x] validate zone rules
- [x] make it easy to add test cases: eg, test/fixtures/zones

## GOALS

- 2040 compatibility
- the software stack should evolve gracefully with the tech industry
- loosely coupled dependencies
- modularity
- easy to add a new DNS [resource record type](https://github.com/NicTool/dns-resource-record)
- easy to add/modify/update DNS [zone rules](https://github.com/NicTool/dns-zone)
- easily coupled with many DNS servers
- distribution of DNS data should be secure, fast, and efficient
`ZONE` enforces the following rules on the records you feed it:

- single SOA per zone (RFC 1035)
- single zone class across all records
- no duplicate RRs, including tag-aware CAA and port-aware SRV (RFC 2181)
- identical TTL across an RRset (same label/class/type) (RFC 2181)
- at most one CNAME per owner (RFC 1034)
- CNAME coexists only with SIG, NXT, KEY, NSEC, RRSIG (RFC 1034, 2181, 4035)

Violations are collected on `zone.errors` and also printed by the CLI (with `-v` to include the offending record).

## RELATED PACKAGES

- [`@nictool/dns-resource-record`](https://github.com/NicTool/dns-resource-record) — record-level parsing, validation, and format conversion
- [`@nictool/dns-nameserver`](https://github.com/NicTool/dns-nameserver) — nameserver config parsers (BIND, Knot, MaraDNS, NSD, tinydns)

## LICENSE

BSD-3-Clause — see [LICENSE](LICENSE).
Loading
Loading