106 lines
5.0 KiB
JavaScript
106 lines
5.0 KiB
JavaScript
// Live lookups on slides.
|
|
//
|
|
// <form class="dns-lookup"> with <input name="domain"> and a .dns-result:
|
|
// asks Google Public DNS over HTTPS for the name's AAAA records
|
|
// (https://developers.google.com/speed/public-dns/docs/doh/json) and
|
|
// prints the answer the way dig would.
|
|
// <form class="ripe-lookup"> with <input name="query"> and a .dns-result:
|
|
// asks the RIPE database (https://apps.db.ripe.net/docs/) what it knows
|
|
// about an address, prefix or AS number, and prints the objects the way
|
|
// whois would.
|
|
(function () {
|
|
const TYPES = { 1: "A", 5: "CNAME", 28: "AAAA" };
|
|
const esc = (s) => String(s).replace(/[&<>"]/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """ })[c]);
|
|
|
|
async function resolve(name, type) {
|
|
const r = await fetch(`https://dns.google/resolve?name=${encodeURIComponent(name)}&type=${type}`, {
|
|
headers: { accept: "application/dns-json" },
|
|
});
|
|
if (!r.ok) throw new Error(`dns.google answered HTTP ${r.status}`);
|
|
return r.json();
|
|
}
|
|
|
|
const line = (rr) =>
|
|
`<span class="name">${esc(rr.name)}</span> <span class="ttl">${rr.TTL}</span> IN <span class="type">${esc(TYPES[rr.type] || rr.type)}</span> <span class="${rr.type === 28 ? "addr" : "data"}">${esc(rr.data)}</span>`;
|
|
|
|
async function lookup(form) {
|
|
const out = form.querySelector(".dns-result");
|
|
const name = form.elements.domain.value.trim().replace(/\.$/, "");
|
|
if (!name) return;
|
|
out.innerHTML = `<span class="dim">asking dns.google for ${esc(name)} AAAA ...</span>`;
|
|
try {
|
|
const v6 = await resolve(name, "AAAA");
|
|
if (v6.Status === 3) {
|
|
out.innerHTML = `<span class="bad">NXDOMAIN</span> <span class="dim">there is no such name</span>`;
|
|
return;
|
|
}
|
|
if (v6.Status !== 0) {
|
|
out.innerHTML = `<span class="bad">the resolver answered with status ${v6.Status}</span>`;
|
|
return;
|
|
}
|
|
const answers = v6.Answer || [];
|
|
const lines = answers.map(line);
|
|
if (!answers.some((rr) => rr.type === 28)) {
|
|
// No IPv6 address. Show that this is a real answer, not a failure,
|
|
// and what the name does have.
|
|
const v4 = await resolve(name, "A");
|
|
lines.push(`<span class="dim">no AAAA record: this name has no IPv6 address</span>`);
|
|
(v4.Answer || []).filter((rr) => rr.type === 1).forEach((rr) => lines.push(line(rr)));
|
|
}
|
|
out.innerHTML = lines.join("\n");
|
|
} catch (e) {
|
|
out.innerHTML = `<span class="bad">lookup failed:</span> ${esc(e.message)} <span class="dim">(is there a network?)</span>`;
|
|
}
|
|
}
|
|
|
|
// ---- RIPE database ----------------------------------------------------
|
|
|
|
// Objects as apps.db.ripe.net shows them: one card per object, one row per
|
|
// attribute, references in blue, rows the RIPE NCC manages shaded. The
|
|
// API does not say which rows those are, so they are the ones the website
|
|
// shades for an assignment: the key, org, status, source, sponsoring-org
|
|
// and the RIPE NCC's own maintainers.
|
|
const RIPE_MANAGED = new Set(["inetnum", "inet6num", "aut-num", "org", "status", "source", "sponsoring-org"]);
|
|
const managed = (a) => RIPE_MANAGED.has(a.name) || (a.name === "mnt-by" && /^RIPE-NCC-/.test(a.value));
|
|
|
|
async function ripe(form) {
|
|
const out = form.querySelector(".ripe-result");
|
|
const query = form.elements.query.value.trim();
|
|
if (!query) return;
|
|
out.innerHTML = `<div class="ripe-msg">Searching the RIPE database for ${esc(query)} ...</div>`;
|
|
try {
|
|
const url = `https://rest.db.ripe.net/search.json?query-string=${encodeURIComponent(query)}&flags=no-referenced&flags=no-irt&source=ripe`;
|
|
const r = await fetch(url, { headers: { accept: "application/json" } });
|
|
if (r.status === 404) {
|
|
out.innerHTML = `<div class="ripe-msg">No results found for ${esc(query)}.</div>`;
|
|
return;
|
|
}
|
|
if (!r.ok) throw new Error(`rest.db.ripe.net answered HTTP ${r.status}`);
|
|
const objects = ((await r.json()).objects || {}).object || [];
|
|
out.innerHTML = objects
|
|
.map((o) => {
|
|
const rows = o.attributes.attribute.map((a) => {
|
|
const value = a.link || a.name === o.type ? `<span class="ref">${esc(a.value)}</span>` : esc(a.value);
|
|
return `<tr class="${managed(a) ? "managed" : ""}"><th>${esc(a.name)}:</th><td>${value}${a.comment ? `<span class="comment"># ${esc(a.comment)}</span>` : ""}</td></tr>`;
|
|
});
|
|
return `<div class="ripe-card"><table>${rows.join("")}</table></div>`;
|
|
})
|
|
.join("");
|
|
out.scrollTop = 0;
|
|
} catch (e) {
|
|
out.innerHTML = `<div class="ripe-msg">Lookup failed: ${esc(e.message)}. Is there a network?</div>`;
|
|
}
|
|
}
|
|
|
|
// The answer changes the slide's height; have reveal centre it again.
|
|
const relayout = () => window.deck && window.deck.layout();
|
|
|
|
document.addEventListener("submit", async (e) => {
|
|
const form = e.target.closest && e.target.closest("form.dns-lookup, form.ripe-lookup");
|
|
if (!form) return;
|
|
e.preventDefault();
|
|
await (form.classList.contains("ripe-lookup") ? ripe(form) : lookup(form));
|
|
relayout();
|
|
});
|
|
})();
|