UNPKG

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
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