Skip to content

feat(runtime): node:dns hosts-file reverse lookups (c-ares parity)#5040

Merged
proggeramlug merged 1 commit into
mainfrom
node-dns-local-parity
Jun 12, 2026
Merged

feat(runtime): node:dns hosts-file reverse lookups (c-ares parity)#5040
proggeramlug merged 1 commit into
mainfrom
node-dns-local-parity

Conversation

@proggeramlug

Copy link
Copy Markdown
Contributor

What now works

dns.reverse() / dnsPromises.reverse() now mirror Node's c-ares "files first" lookup order: /etc/hosts (or %SystemRoot%\System32\drivers\etc\hosts on Windows) is consulted before issuing a PTR query. Entries are merged the way c-ares merges them — lines sharing an address or a (case-insensitive) hostname collapse into one entry (127.0.0.1 localhost + ::1 localhost become one entry holding both addresses) — and the result is the merged entry's aliases (every name except the canonical first one), matching Node's HostentToNames, which reports only h_aliases. Verified empirically against node v26.3.0 on three shapes: 127.0.0.1/::1 (merged multi-alias entry), an IP with two hosts lines (canonical excluded), and the DNS PTR fallback path (all PTR names returned).

dns.lookupService() for non-loopback addresses now also prefers the hosts-file canonical name before falling back to a PTR query (loopback → localhost unchanged).

PERRY_DETERMINISTIC_NET=1 still bypasses everything (verified: deterministic reverse stays ["localhost"]).

Crash fixed

On machines where the upstream resolver has no PTR record for 127.0.0.1 (e.g. a home router returning NXDOMAIN), dnsPromises.reverse("127.0.0.1") rejected with ENOTFOUND → unhandled rejection → the dns/resolve/localhost parity binary exited 1, while node returned the hosts-file aliases. That was the lone dns suite failure.

Test made environment-robust

test-parity/node-suite/dns/resolve/localhost.ts assumed the local resolver answers A/AAAA for localhost. On resolvers that return ENODATA for the AAAA query, node itself crashed (callback6.value.includes on undefined), making the test unpassable for any Perry behavior. It now prints has <addr> / err:<code> / the reverse alias array, so node and Perry must agree in every resolver environment while still asserting real resolution (incl. the hosts-file reverse path byte-for-byte).

node-suite before → after (local macOS, node v26.3.0)

  • dns: 5/6 → 6/6 (fixed resolve/localhost.ts; final output byte-identical to node)
  • Regression sweep over 10 untouched modules (dgram, os, punycode, querystring, string_decoder, url, events, constants, sys, domain — 291 tests): 275 pass, 16 fail — the identical 16 fail on a pre-change baseline build (10 are DEP0040/DEP0169 stderr artifacts of the raw side-by-side runner, which the official harness normalizes away; the events/on + remaining ones are pre-existing gaps unrelated to dns).
  • cargo test -p perry-runtime dns_resolver: 3 new unit tests for the hosts parser (merge-by-address, merge-by-name, comments, case-insensitivity) all green.
  • cargo fmt --all clean; file-size gate OK.

Files

  • crates/perry-runtime/src/dns_resolver.rsparse_hosts_file (c-ares merge semantics), hosts_file_names(ip), unit tests
  • crates/perry-runtime/src/dns.rsreverse consults hosts file first (aliases); lookupService canonical-name preference
  • test-parity/node-suite/dns/resolve/localhost.ts — environment-robust output

No version/changelog edits (maintainer folds metadata at merge per CLAUDE.md).

dns.reverse() went straight to a PTR query, but Node's c-ares uses
"files first" lookup order: /etc/hosts entries that share an address
or hostname are merged into one entry, and HostentToNames reports the
merged entry's aliases (every name except the canonical first one).
On machines where the upstream resolver has no PTR for 127.0.0.1 this
made dns.reverse('127.0.0.1') reject with ENOTFOUND (an unhandled
rejection in the dns/resolve parity test) while node returned the
hosts-file aliases.

- dns_resolver.rs: parse_hosts_file with c-ares merge semantics
  (merge by shared address or case-insensitive hostname), plus
  hosts_file_names(ip) used before falling through to PTR; unit
  tests for merge/comment/case behavior.
- dns.rs: reverse consults the hosts file first and returns aliases;
  lookupService uses the canonical hosts-file name for non-loopback
  addresses. PERRY_DETERMINISTIC_NET=1 still bypasses both.
- test-parity/node-suite/dns/resolve/localhost.ts: print
  record-or-error-code summaries instead of assuming the local
  resolver answers A/AAAA for localhost — node itself crashed with a
  TypeError on resolvers that return ENODATA, making the test
  unpassable regardless of Perry's behavior. The rewrite asserts the
  same resolution semantics with environment-independent output.

node-suite dns: 5/6 -> 6/6 locally.
@proggeramlug proggeramlug merged commit dfa3e57 into main Jun 12, 2026
13 checks passed
@proggeramlug proggeramlug deleted the node-dns-local-parity branch June 12, 2026 16:20
proggeramlug added a commit that referenced this pull request Jun 13, 2026
… + hostname) (#5077)

node:dns node-suite: 5/6 -> 6/6 locally (zero regressions in
assert/net/dgram + dns_resolver unit tests + error-shape probes).

Root causes:

1. resolve*/reverse rejections were missing Node's `.syscall` and
   `.hostname` own properties. A caught `dns.resolve4` error exposed only
   `.code` ("ESERVFAIL"); Node's c-ares errors also carry
   `e.syscall === "queryA"` and `e.hostname === "localhost"` (with
   `errno` left undefined). `dns.reverse` errors likewise carry
   `syscall === "getHostByAddr"`.
   - dns.rs: new `dns_query_error_value` builds the error and registers
     code + syscall + hostname on the message StringHeader (mirrors the
     existing `.code` side-table path); resolve_error_value /
     reverse_error_value route through it.
   - node_submodules/diagnostics.rs: new ERROR_MESSAGE_HOSTNAMES side
     table + register_error_hostname / error_hostname_for_message,
     paralleling the syscall/path/dest tables.
   - object/field_get_set.rs: new `.hostname` Error getter arm (mirrors
     `.path`/`.syscall`). The user-assigned-prop path is consulted first,
     so a user-set `err.hostname` still wins — verified against Node.

2. test imports/default-export.ts asserted that
   `resolver.resolve4("localhost")` returns an array. resolve4 queries the
   configured nameserver directly (it does NOT consult /etc/hosts), so
   "localhost" yields an A record on some resolvers and
   ESERVFAIL/ENOTFOUND on others — on a SERVFAIL machine Node itself
   aborts on the unhandled rejection, making the assertion unportable.
   Rewrote it to print array-or-error-code, the same environment-robust
   record-or-error approach #5040 already applied to the sibling
   dns/resolve/localhost.ts test. (No product behavior was papered over:
   Perry already threw the correct ESERVFAIL there — the fix above just
   completes that error's shape.)

Co-authored-by: Ralph Küpper <ralph@skelpo.com>
proggeramlug added a commit that referenced this pull request Jun 14, 2026
…#5148)

Audit and update of the documentation after the recent Node.js parity sprint
(PRs ~#4900#5040+). Brings the public docs in line with current behavior:

- README: new "Node.js compatibility" section (~97% of Node's own test suite,
  ~95% overall) and a note that Perry compiles .js/.cjs/.mjs/.jsx source
  directly; fix the stale "Decorators: not supported" row (legacy TS decorators
  + emitDecoratorMetadata are supported).
- introduction / supported-features: add Node.js compatibility + JavaScript
  input sections.
- limitations: correct the stale "no SharedArrayBuffer or Atomics" claim
  (real cross-thread Atomics + SAB landed); note .js compilation.
- threading/overview: document the SharedArrayBuffer/Atomics shared-state path.
- runtime-parity-gaps: add a behavioral-status header.
- typescript-parity-gaps (v0.4.56) / typescript-compatibility-tests (v0.4.50):
  add prominent 'historical/outdated' banners pointing at the maintained
  source of truth.

Co-authored-by: Ralph Küpper <ralph2@skelpo.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant