// Live lookups on slides. // //
with 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. // with 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) => `${esc(rr.name)} ${rr.TTL} IN ${esc(TYPES[rr.type] || rr.type)} ${esc(rr.data)}`; async function lookup(form) { const out = form.querySelector(".dns-result"); const name = form.elements.domain.value.trim().replace(/\.$/, ""); if (!name) return; out.innerHTML = `asking dns.google for ${esc(name)} AAAA ...`; try { const v6 = await resolve(name, "AAAA"); if (v6.Status === 3) { out.innerHTML = `NXDOMAIN there is no such name`; return; } if (v6.Status !== 0) { out.innerHTML = `the resolver answered with status ${v6.Status}`; 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(`no AAAA record: this name has no IPv6 address`); (v4.Answer || []).filter((rr) => rr.type === 1).forEach((rr) => lines.push(line(rr))); } out.innerHTML = lines.join("\n"); } catch (e) { out.innerHTML = `lookup failed: ${esc(e.message)} (is there a network?)`; } } // ---- 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 = `
Searching the RIPE database for ${esc(query)} ...
`; 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 = `
No results found for ${esc(query)}.
`; 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 ? `${esc(a.value)}` : esc(a.value); return `${esc(a.name)}:${value}${a.comment ? `# ${esc(a.comment)}` : ""}`; }); return `
${rows.join("")}
`; }) .join(""); out.scrollTop = 0; } catch (e) { out.innerHTML = `
Lookup failed: ${esc(e.message)}. Is there a network?
`; } } // 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(); }); })(); // Byte walks: hovering (or tabbing to) a field row lights its bytes in the // dump above it, and hovering bytes in the dump lights their row and scrolls // it into view, so the dump can be used as the index while presenting. (function () { const pair = (el) => { const walk = el.closest(".bytewalk"); if (!walk) return []; const i = el.dataset.i; return [walk.querySelector(`.bw-dump span[data-i="${i}"]`), walk.querySelector(`.bw-row[data-i="${i}"]`)].filter(Boolean); }; const target = (e) => e.target.closest && e.target.closest(".bw-row, .bw-dump span[data-i]"); const on = (e) => { const el = target(e); if (!el) return; const both = pair(el); both.forEach((x) => x.classList.add("on")); if (el.matches(".bw-dump span")) { // Scroll the box itself, and instantly: scrollIntoView would also // move the slide, and Chrome drops smooth scrolls inside the // transformed slide that reveal positions. const row = both.find((x) => x.classList.contains("bw-row")); const box = row && row.closest(".bw-rows"); if (box) box.scrollTop = row.offsetTop - box.clientHeight / 2 + row.offsetHeight / 2; } }; const off = (e) => { const el = target(e); if (el) pair(el).forEach((x) => x.classList.remove("on")); }; document.addEventListener("mouseover", on); document.addEventListener("focusin", on); document.addEventListener("mouseout", off); document.addEventListener("focusout", off); })();