Pick a domain name and watch the resolver walk the hierarchy from root to authoritative server. Each hop shows the question asked, the server that answers, and what it returns. After the first lookup, click Resolve again to see the cached answer skip all the intermediate steps.
<!-- DNS resolution demo — walks root -> TLD -> authoritative server hop by hop -->
<div class="controls">
<label for="domain-select">Domain:</label>
<select id="domain-select">
<option value="www.example.com">www.example.com</option>
<option value="mail.google.com">mail.google.com</option>
<option value="api.github.com">api.github.com</option>
<option value="blog.mozilla.org">blog.mozilla.org</option>
</select>
<button id="btn-resolve" type="button">Resolve</button>
<button id="btn-clear-cache" type="button" class="ghost">Clear cache</button>
</div>
<div id="cache-indicator" class="cache-indicator" aria-live="polite"></div>
<ol id="hop-list" class="hop-list" aria-live="polite"></ol>
<div id="result-box" class="result-box" aria-live="polite"></div>
/* DNS resolution demo styles */
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; }
.controls { display: flex; flex-wrap: wrap; align-items: center; gap: .5rem; margin-bottom: .8rem; }
label { font-size: .9rem; font-weight: 600; color: #444; }
select { font: 15px system-ui, sans-serif; padding: .35rem .6rem; border: 1px solid #b0b8c1;
border-radius: 6px; background: #f5f7fa; color: #222; }
button { font: 600 14px system-ui, sans-serif; padding: .4rem .85rem; border: 1px solid #1d3557;
background: #1d3557; color: #fff; border-radius: 7px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
/* Hop list — each hop gets a colored badge based on server type */
.hop-list { list-style: none; margin: .4rem 0; padding: 0; display: flex; flex-direction: column; gap: 6px; }
.hop { display: flex; align-items: flex-start; gap: .6rem; padding: .5rem .7rem;
border-radius: 8px; font-size: .85rem; line-height: 1.45; animation: fadein .25s ease; }
@keyframes fadein { from { opacity: 0; transform: translateY(-4px); } to { opacity: 1; transform: none; } }
.hop .badge { flex-shrink: 0; padding: .18rem .5rem; border-radius: 5px; font: 700 11px monospace;
color: #fff; white-space: nowrap; }
.hop.resolver .badge { background: #5a7088; }
.hop.root .badge { background: #e07b22; }
.hop.tld .badge { background: #2a7a4a; }
.hop.auth .badge { background: #9b2d8e; }
.hop.cache .badge { background: #0a7d9b; }
.hop.resolver { background: #eef1f5; }
.hop.root { background: #fff5eb; }
.hop.tld { background: #edfaf2; }
.hop.auth { background: #f9eef8; }
.hop.cache { background: #e8f7fb; }
.hop .text b { font-weight: 700; }
/* Final result box */
.result-box { margin-top: .5rem; padding: .55rem .8rem; border-radius: 8px;
font: 600 15px system-ui, sans-serif; min-height: 2em; }
.result-box.ok { background: #d4edda; color: #0a5226; }
.result-box.bad { background: #fde8e8; color: #8b1c1c; }
.cache-indicator { font-size: .82rem; color: #5a7088; min-height: 1.2em; margin-bottom: .2rem; }
const i18nFmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Simulated DNS data: tld = suffix, ns = authoritative nameserver, ip = final answer
const DNS_DB = {
'www.example.com': { tld: '.com', ns: 'ns1.example.com', ip: '93.184.216.34' },
'mail.google.com': { tld: '.com', ns: 'ns1.google.com', ip: '142.250.80.5' },
'api.github.com': { tld: '.com', ns: 'ns-421.awsdns.com', ip: '140.82.113.5' },
'blog.mozilla.org': { tld: '.org', ns: 'ns1.mozilla.org', ip: '34.120.97.18' }
};
// Human-readable server type labels (pulled from i18n so they translate)
const SERVER_LABELS = {
resolver: 'RESOLVER',
root: 'ROOT',
tld: 'TLD',
auth: 'AUTH',
cache: 'CACHE'
};
const cache = {};
function addHop(type, text) {
const li = document.createElement('li');
li.className = 'hop ' + type;
li.innerHTML = '<span class="badge">' + SERVER_LABELS[type] + '</span><span class="text">' + text + '</span>';
document.getElementById('hop-list').appendChild(li);
}
function setResult(msg, ok) {
const box = document.getElementById('result-box');
box.textContent = msg;
box.className = 'result-box ' + (ok ? 'ok' : 'bad');
}
function setCacheIndicator(msg) {
document.getElementById('cache-indicator').textContent = msg;
}
// Main resolve function — simulates a full recursive DNS lookup with caching
function resolve() {
const domain = document.getElementById('domain-select').value;
document.getElementById('hop-list').innerHTML = '';
document.getElementById('result-box').textContent = '';
document.getElementById('result-box').className = 'result-box';
const entry = DNS_DB[domain];
if (!entry) { setResult('Unknown domain — not in demo database.', false); return; }
// Check the in-memory cache first (like a real resolver's cache)
if (cache[domain]) {
setCacheIndicator(i18nFmt('Cache hit! TTL remaining: %ttl% s', { ttl: cache[domain].ttl }));
addHop('resolver', i18nFmt('Client asks resolver: what is the IP of <b>%domain%</b>?', { domain: domain }));
addHop('cache', i18nFmt('Resolver serves from cache: <b>%domain%</b> = <b>%ip%</b>.', { domain: domain, ip: cache[domain].ip }));
setResult(i18nFmt('Resolved: %domain% → %ip%', { domain: domain, ip: cache[domain].ip }), true);
return;
}
setCacheIndicator('Cache is empty — performing full recursive lookup.');
// Cold lookup — walk all three hierarchy levels
addHop('resolver', i18nFmt('Client asks resolver: what is the IP of <b>%domain%</b>?', { domain: domain }));
addHop('root', i18nFmt('Root server replies: for <b>%tld%</b> names, ask the TLD server.', { tld: entry.tld }));
addHop('tld', i18nFmt('TLD server replies: <b>%domain%</b> is served by <b>%ns%</b>.', { domain: domain, ns: entry.ns }));
addHop('auth', i18nFmt('Authoritative server replies: <b>%domain%</b> = <b>%ip%</b>.', { domain: domain, ip: entry.ip }));
addHop('resolver', i18nFmt('Resolver caches the answer and returns <b>%ip%</b> to the client.', { domain: domain, ip: entry.ip }));
// Store the answer in cache with a 300 s TTL
cache[domain] = { ip: entry.ip, ttl: 300 };
setCacheIndicator(i18nFmt('Cached %domain% → %ip% (TTL %ttl% s)', { domain: domain, ttl: 300 }));
setResult(i18nFmt('Resolved: %domain% → %ip%', { domain: domain, ip: entry.ip }), true);
}
document.getElementById('btn-resolve').addEventListener('click', resolve);
document.getElementById('btn-clear-cache').addEventListener('click', function() {
for (const k in cache) delete cache[k];
setCacheIndicator('Cache cleared — next resolve will do a full lookup.');
document.getElementById('hop-list').innerHTML = '';
document.getElementById('result-box').textContent = '';
document.getElementById('result-box').className = 'result-box';
});
// Auto-resolve on page load to show initial state
resolve();
<!-- DNS resolution demo — walks root -> TLD -> authoritative server hop by hop -->
<div class="controls">
<label for="domain-select">Domain:</label>
<select id="domain-select">
<option value="www.example.com">www.example.com</option>
<option value="mail.google.com">mail.google.com</option>
<option value="api.github.com">api.github.com</option>
<option value="blog.mozilla.org">blog.mozilla.org</option>
</select>
<button id="btn-resolve" type="button">Resolve</button>
<button id="btn-clear-cache" type="button" class="ghost">Clear cache</button>
</div>
<div id="cache-indicator" class="cache-indicator" aria-live="polite"></div>
<ol id="hop-list" class="hop-list" aria-live="polite"></ol>
<div id="result-box" class="result-box" aria-live="polite"></div>
/* DNS resolution demo styles */
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; }
.controls { display: flex; flex-wrap: wrap; align-items: center; gap: .5rem; margin-bottom: .8rem; }
label { font-size: .9rem; font-weight: 600; color: #444; }
select { font: 15px system-ui, sans-serif; padding: .35rem .6rem; border: 1px solid #b0b8c1;
border-radius: 6px; background: #f5f7fa; color: #222; }
button { font: 600 14px system-ui, sans-serif; padding: .4rem .85rem; border: 1px solid #1d3557;
background: #1d3557; color: #fff; border-radius: 7px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
/* Hop list — each hop gets a colored badge based on server type */
.hop-list { list-style: none; margin: .4rem 0; padding: 0; display: flex; flex-direction: column; gap: 6px; }
.hop { display: flex; align-items: flex-start; gap: .6rem; padding: .5rem .7rem;
border-radius: 8px; font-size: .85rem; line-height: 1.45; animation: fadein .25s ease; }
@keyframes fadein { from { opacity: 0; transform: translateY(-4px); } to { opacity: 1; transform: none; } }
.hop .badge { flex-shrink: 0; padding: .18rem .5rem; border-radius: 5px; font: 700 11px monospace;
color: #fff; white-space: nowrap; }
.hop.resolver .badge { background: #5a7088; }
.hop.root .badge { background: #e07b22; }
.hop.tld .badge { background: #2a7a4a; }
.hop.auth .badge { background: #9b2d8e; }
.hop.cache .badge { background: #0a7d9b; }
.hop.resolver { background: #eef1f5; }
.hop.root { background: #fff5eb; }
.hop.tld { background: #edfaf2; }
.hop.auth { background: #f9eef8; }
.hop.cache { background: #e8f7fb; }
.hop .text b { font-weight: 700; }
/* Final result box */
.result-box { margin-top: .5rem; padding: .55rem .8rem; border-radius: 8px;
font: 600 15px system-ui, sans-serif; min-height: 2em; }
.result-box.ok { background: #d4edda; color: #0a5226; }
.result-box.bad { background: #fde8e8; color: #8b1c1c; }
.cache-indicator { font-size: .82rem; color: #5a7088; min-height: 1.2em; margin-bottom: .2rem; }
const i18nFmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Simulated DNS data: tld = suffix, ns = authoritative nameserver, ip = final answer
const DNS_DB = {
'www.example.com': { tld: '.com', ns: 'ns1.example.com', ip: '93.184.216.34' },
'mail.google.com': { tld: '.com', ns: 'ns1.google.com', ip: '142.250.80.5' },
'api.github.com': { tld: '.com', ns: 'ns-421.awsdns.com', ip: '140.82.113.5' },
'blog.mozilla.org': { tld: '.org', ns: 'ns1.mozilla.org', ip: '34.120.97.18' }
};
// Human-readable server type labels (pulled from i18n so they translate)
const SERVER_LABELS = {
resolver: 'RESOLVER',
root: 'ROOT',
tld: 'TLD',
auth: 'AUTH',
cache: 'CACHE'
};
const cache = {};
function addHop(type, text) {
const li = document.createElement('li');
li.className = 'hop ' + type;
li.innerHTML = '<span class="badge">' + SERVER_LABELS[type] + '</span><span class="text">' + text + '</span>';
document.getElementById('hop-list').appendChild(li);
}
function setResult(msg, ok) {
const box = document.getElementById('result-box');
box.textContent = msg;
box.className = 'result-box ' + (ok ? 'ok' : 'bad');
}
function setCacheIndicator(msg) {
document.getElementById('cache-indicator').textContent = msg;
}
// Main resolve function — simulates a full recursive DNS lookup with caching
function resolve() {
const domain = document.getElementById('domain-select').value;
document.getElementById('hop-list').innerHTML = '';
document.getElementById('result-box').textContent = '';
document.getElementById('result-box').className = 'result-box';
const entry = DNS_DB[domain];
if (!entry) { setResult('Unknown domain — not in demo database.', false); return; }
// Check the in-memory cache first (like a real resolver's cache)
if (cache[domain]) {
setCacheIndicator(i18nFmt('Cache hit! TTL remaining: %ttl% s', { ttl: cache[domain].ttl }));
addHop('resolver', i18nFmt('Client asks resolver: what is the IP of <b>%domain%</b>?', { domain: domain }));
addHop('cache', i18nFmt('Resolver serves from cache: <b>%domain%</b> = <b>%ip%</b>.', { domain: domain, ip: cache[domain].ip }));
setResult(i18nFmt('Resolved: %domain% → %ip%', { domain: domain, ip: cache[domain].ip }), true);
return;
}
setCacheIndicator('Cache is empty — performing full recursive lookup.');
// Cold lookup — walk all three hierarchy levels
addHop('resolver', i18nFmt('Client asks resolver: what is the IP of <b>%domain%</b>?', { domain: domain }));
addHop('root', i18nFmt('Root server replies: for <b>%tld%</b> names, ask the TLD server.', { tld: entry.tld }));
addHop('tld', i18nFmt('TLD server replies: <b>%domain%</b> is served by <b>%ns%</b>.', { domain: domain, ns: entry.ns }));
addHop('auth', i18nFmt('Authoritative server replies: <b>%domain%</b> = <b>%ip%</b>.', { domain: domain, ip: entry.ip }));
addHop('resolver', i18nFmt('Resolver caches the answer and returns <b>%ip%</b> to the client.', { domain: domain, ip: entry.ip }));
// Store the answer in cache with a 300 s TTL
cache[domain] = { ip: entry.ip, ttl: 300 };
setCacheIndicator(i18nFmt('Cached %domain% → %ip% (TTL %ttl% s)', { domain: domain, ttl: 300 }));
setResult(i18nFmt('Resolved: %domain% → %ip%', { domain: domain, ip: entry.ip }), true);
}
document.getElementById('btn-resolve').addEventListener('click', resolve);
document.getElementById('btn-clear-cache').addEventListener('click', function() {
for (const k in cache) delete cache[k];
setCacheIndicator('Cache cleared — next resolve will do a full lookup.');
document.getElementById('hop-list').innerHTML = '';
document.getElementById('result-box').textContent = '';
document.getElementById('result-box').className = 'result-box';
});
// Auto-resolve on page load to show initial state
resolve();
Notice the asymmetry: the first lookup takes three or more round trips across the hierarchy; the cached lookup costs nothing extra. That is why TTL (time-to-live) is the key tuning knob in DNS — too short and every request re-queries the whole chain; too long and stale records persist after an IP address changes.
Comments
Loading comments...