From 41fb7875d035c455900f9da8427513a0e7e585c6 Mon Sep 17 00:00:00 2001 From: John Coffey Date: Fri, 25 Sep 2026 21:14:13 -0700 Subject: [PATCH] About: find the mail node's name with resolvePtr dns.reverse came back empty inside the image while the resolver answered the PTR, so About showed only the address. Ask for the PTR record of the in-addr.arpa / ip6.arpa name directly. --- server/src/nodes.test.ts | 11 ++++++++++- server/src/nodes.ts | 19 ++++++++++++++++++- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/server/src/nodes.test.ts b/server/src/nodes.test.ts index 1542376..67c26f1 100644 --- a/server/src/nodes.test.ts +++ b/server/src/nodes.test.ts @@ -1,6 +1,6 @@ import { test, beforeEach } from "node:test"; import assert from "node:assert/strict"; -import { clearNodeCache, mailNode } from "./nodes.js"; +import { clearNodeCache, mailNode, ptrName } from "./nodes.js"; /** * About names the mail node by the PTR of the address the server's name @@ -38,3 +38,12 @@ test("the answer is cached for a minute, then looked up again", async () => { await mailNode("https://mail.example.com", count, rev, 61_000); assert.equal(calls, 2); }); + +test("the PTR name is built for IPv4 and IPv6 alike", () => { + assert.equal(ptrName("192.0.2.52"), "52.2.0.192.in-addr.arpa"); + assert.equal( + ptrName("2001:db8::25"), + "5.2.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa", + ); + assert.equal(ptrName("::1"), "1" + ".0".repeat(31) + ".ip6.arpa"); +}); diff --git a/server/src/nodes.ts b/server/src/nodes.ts index d1c2c87..95b4308 100644 --- a/server/src/nodes.ts +++ b/server/src/nodes.ts @@ -12,7 +12,8 @@ * node has one: mail/mx2/mx3). The server offers its node name only to * administrators, so asking it would leave everyone else with nothing. */ -import { lookup as dnsLookup, reverse as dnsReverse } from "node:dns/promises"; +import { lookup as dnsLookup, resolvePtr } from "node:dns/promises"; +import { isIPv4 } from "node:net"; import { hostname } from "node:os"; import { config } from "./config.js"; @@ -33,6 +34,22 @@ export interface Nodes { type Lookup = (host: string) => Promise<{ address: string }>; type Reverse = (address: string) => Promise; +/** + * The reverse-lookup name for an address: 1.2.0.192.in-addr.arpa, or the + * nibble form under ip6.arpa. Asked for directly, because `dns.reverse` came + * back empty in the image while the resolver answered the PTR (2026-09-26). + */ +export function ptrName(address: string): string { + if (isIPv4(address)) return `${address.split(".").reverse().join(".")}.in-addr.arpa`; + const [head = "", tail = ""] = address.split("::"); + const groups = (part: string) => (part ? part.split(":") : []); + const h = groups(head), t = groups(tail); + const full = [...h, ...Array(8 - h.length - t.length).fill("0"), ...t]; + return `${full.map((g) => g.padStart(4, "0")).join("").split("").reverse().join(".")}.ip6.arpa`; +} + +const dnsReverse: Reverse = (address) => resolvePtr(ptrName(address)); + const CACHE_MS = 60_000; const cache = new Map();