feat(runtime): node:dns hosts-file reverse lookups (c-ares parity)#5040
Merged
Conversation
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
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What now works
dns.reverse()/dnsPromises.reverse()now mirror Node's c-ares "files first" lookup order:/etc/hosts(or%SystemRoot%\System32\drivers\etc\hostson 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 localhostbecome one entry holding both addresses) — and the result is the merged entry's aliases (every name except the canonical first one), matching Node'sHostentToNames, which reports onlyh_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 →localhostunchanged).PERRY_DETERMINISTIC_NET=1still 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 withENOTFOUND→ unhandled rejection → thedns/resolve/localhostparity 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.tsassumed the local resolver answers A/AAAA forlocalhost. On resolvers that return ENODATA for the AAAA query, node itself crashed (callback6.value.includesonundefined), making the test unpassable for any Perry behavior. It now printshas <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)
resolve/localhost.ts; final output byte-identical to node)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 --allclean; file-size gate OK.Files
crates/perry-runtime/src/dns_resolver.rs—parse_hosts_file(c-ares merge semantics),hosts_file_names(ip), unit testscrates/perry-runtime/src/dns.rs—reverseconsults hosts file first (aliases);lookupServicecanonical-name preferencetest-parity/node-suite/dns/resolve/localhost.ts— environment-robust outputNo version/changelog edits (maintainer folds metadata at merge per CLAUDE.md).