domainlooker
Version:
A fast CLI and MCP server for inspecting domains: WHOIS/RDAP, DNS, SSL, ports, subdomains, with CSV/JSON export.
110 lines • 6.58 kB
JavaScript
import { Resolver } from 'dns/promises';
/** A spread of well-known public resolvers with representative locations. */
export const PUBLIC_RESOLVERS = [
{ id: "cloudflare-us", provider: "Cloudflare", ip: "1.1.1.1", location: "San Francisco", country: "US", lat: 37.7749, lon: -122.4194 },
{ id: "opendns-us", provider: "OpenDNS", ip: "208.67.222.222", location: "San Francisco", country: "US", lat: 37.7749, lon: -122.4194 },
{ id: "google-us", provider: "Google", ip: "8.8.8.8", location: "Mountain View", country: "US", lat: 37.422, lon: -122.0841 },
{ id: "shaw-ab-ca", provider: "Shaw", ip: "184.68.102.2", location: "Calgary", country: "CA", lat: 51.023, lon: -114.0718 },
{ id: "axtel-mx", provider: "Axtel", ip: "148.243.126.229", location: "Mexico City", country: "MX", lat: 19.4285, lon: -99.1277 },
{ id: "cira-ca", provider: "CIRA Shield", ip: "149.112.121.10", location: "Ottawa", country: "CA", lat: 45.3867, lon: -75.7405 },
{ id: "maroc-ma", provider: "Maroc Telecom", ip: "81.192.7.147", location: "Rabat", country: "MA", lat: 34.0084, lon: -6.8539 },
{ id: "mtn-ng", provider: "MTN Nigeria", ip: "102.91.8.126", location: "Lagos", country: "NG", lat: 6.4531, lon: 3.3792 },
{ id: "quad9-ch", provider: "Quad9", ip: "9.9.9.9", location: "Zurich", country: "CH", lat: 47.3769, lon: 8.5417 },
{ id: "bitco-za", provider: "BitCo", ip: "154.117.151.154", location: "Johannesburg", country: "ZA", lat: -26.2309, lon: 28.0583 },
{ id: "turktelekom-tr", provider: "Turk Telekom", ip: "88.248.51.121", location: "Istanbul", country: "TR", lat: 41.0082, lon: 28.9784 },
{ id: "eun-eg", provider: "EUN Egypt", ip: "193.227.29.241", location: "Giza", country: "EG", lat: 30.0268, lon: 31.2087 },
{ id: "liquid-ke", provider: "Liquid Telecom", ip: "41.84.143.137", location: "Nairobi", country: "KE", lat: -1.2841, lon: 36.8155 },
{ id: "yandex-ru", provider: "Yandex", ip: "77.88.8.8", location: "Moscow", country: "RU", lat: 55.7558, lon: 37.6173 },
{ id: "tic-ir", provider: "TIC Iran", ip: "2.189.44.44", location: "Tehran", country: "IR", lat: 35.6892, lon: 51.389 },
{ id: "cybernet-pk", provider: "Cybernet", ip: "61.5.134.35", location: "Karachi", country: "PK", lat: 24.8607, lon: 67.0011 },
{ id: "leapswitch-in", provider: "LeapSwitch", ip: "103.13.112.251", location: "Mumbai", country: "IN", lat: 19.12, lon: 72.98 },
{ id: "micronet-bd", provider: "Micro Network", ip: "103.168.90.81", location: "Dhaka", country: "BD", lat: 23.8103, lon: 90.4125 },
{ id: "opennic-sg", provider: "OpenNIC SG", ip: "172.104.178.107", location: "Singapore", country: "SG", lat: 1.3521, lon: 103.8198 },
{ id: "114dns-cn", provider: "114DNS", ip: "114.114.114.114", location: "Nanjing", country: "CN", lat: 32.0603, lon: 118.7969 },
{ id: "hinet-tw", provider: "HiNet", ip: "168.95.1.1", location: "Taipei", country: "TW", lat: 25.033, lon: 121.5654 },
{ id: "pldt-ph", provider: "PLDT", ip: "210.1.83.201", location: "Cebu City", country: "PH", lat: 10.3157, lon: 123.8854 },
{ id: "kt-kr", provider: "KT Korea", ip: "168.126.63.1", location: "Seoul", country: "KR", lat: 37.5665, lon: 126.978 },
{ id: "ntt-jp", provider: "NTT", ip: "118.3.227.163", location: "Tokyo", country: "JP", lat: 35.6762, lon: 139.6503 },
{ id: "opennic-au-syd", provider: "OpenNIC AU", ip: "112.213.35.91", location: "Sydney", country: "AU", lat: -33.8678, lon: 151.2073 },
];
const DEFAULT_TIMEOUT_MS = 4000;
export class PropagationService {
constructor(resolvers = PUBLIC_RESOLVERS) {
this.resolvers = resolvers;
}
async check(domain, recordType = 'A', timeoutMs = DEFAULT_TIMEOUT_MS) {
const answers = await Promise.all(this.resolvers.map(resolver => this.queryOne(resolver, domain, recordType, timeoutMs)));
return buildResult(domain, recordType, answers);
}
async queryOne(resolver, domain, recordType, timeoutMs) {
const r = new Resolver({ timeout: timeoutMs, tries: 1 });
r.setServers([resolver.ip]);
try {
const answer = await resolveByType(r, domain, recordType);
return { resolver, answer: answer.length ? normalize(answer) : null };
}
catch (error) {
return { resolver, answer: null, error: error instanceof Error ? error.message : String(error) };
}
}
}
async function resolveByType(r, domain, type) {
switch (type) {
case 'A': return r.resolve4(domain);
case 'AAAA': return r.resolve6(domain);
case 'CNAME': return r.resolveCname(domain);
case 'NS': return r.resolveNs(domain);
case 'TXT': return (await r.resolveTxt(domain)).map(chunks => chunks.join(''));
case 'MX': return (await r.resolveMx(domain)).map(mx => `${mx.priority} ${mx.exchange}`);
}
}
/** Classify per-resolver answers against the majority. Pure — no network. */
export function buildResult(domain, recordType, answers) {
const expected = majorityAnswer(answers.map(a => a.answer));
const results = answers.map(({ resolver, answer, error }) => ({
resolver,
answer,
error,
status: answer === null ? 'no-answer' : sameAnswer(answer, expected) ? 'in-sync' : 'differs',
}));
const inSync = results.filter(r => r.status === 'in-sync').length;
return {
domain,
recordType,
expected,
results,
inSync,
total: results.length,
percent: results.length ? Math.round((inSync / results.length) * 100) : 0,
};
}
/** Canonical form of an answer set: sorted, so order differences don't count as drift. */
export function normalize(answer) {
return [...answer].map(a => a.trim().toLowerCase()).sort();
}
export function sameAnswer(a, b) {
if (a === null || b === null)
return a === b;
return a.length === b.length && a.every((v, i) => v === b[i]);
}
/** The most common non-null answer across resolvers — the value we treat as "propagated". */
export function majorityAnswer(answers) {
const counts = new Map();
for (const answer of answers) {
if (!answer)
continue;
const key = JSON.stringify(answer);
const entry = counts.get(key);
if (entry)
entry.count++;
else
counts.set(key, { answer, count: 1 });
}
let best = null;
for (const entry of counts.values()) {
if (!best || entry.count > best.count)
best = entry;
}
return best ? best.answer : null;
}
//# sourceMappingURL=propagation.js.map