Status page: count rejected announcements, and name AS numbers
The "refused" column counted rejected announcements since the start, not routes, which made two peers that send the same routes look different when one of them re-sends more often. It is now "rejected announcements", with the held routes, the number that compares, beside it as "held now". The page says "rejected" throughout, as the documentation and metrics do. Rows are highlighted under the pointer, and pointing at an AS number shows its holder's name, which the browser asks RIPEstat for. The daemon looks nothing up; the monitoring page says what the browser sends where. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
efb2d81a50
commit
acecc22f48
2 files changed
+78
-17
No files matched your search
@@ -9,7 +9,9 @@
|
|||||||
A read-only status page in the manner of the classic /statusz pages: plain
|
A read-only status page in the manner of the classic /statusz pages: plain
|
||||||
sans-serif HTML, default links, dense bordered tables, and one full-width
|
sans-serif HTML, default links, dense bordered tables, and one full-width
|
||||||
light blue (#eef) heading bar per section. Deliberately no dark theme, no
|
light blue (#eef) heading bar per section. Deliberately no dark theme, no
|
||||||
framework, no external resources. Everything shown comes from /api/*.
|
framework, no external resources. Everything shown comes from /api/*,
|
||||||
|
except the names of AS numbers: pointing at one asks RIPEstat for its
|
||||||
|
holder, from the browser (the daemon itself looks nothing up).
|
||||||
-->
|
-->
|
||||||
<style>
|
<style>
|
||||||
body { font-family: sans-serif; font-size: 13px; color: #000; background: #fff; margin: 8px; }
|
body { font-family: sans-serif; font-size: 13px; color: #000; background: #fff; margin: 8px; }
|
||||||
@@ -21,6 +23,10 @@ a { color: #00c; } a:visited { color: #551a8b; }
|
|||||||
table.data { border-collapse: collapse; margin: 4px 0; }
|
table.data { border-collapse: collapse; margin: 4px 0; }
|
||||||
table.data th, table.data td { border: 1px solid #99a; padding: 2px 6px; text-align: left; vertical-align: top; }
|
table.data th, table.data td { border: 1px solid #99a; padding: 2px 6px; text-align: left; vertical-align: top; }
|
||||||
table.data th { background: #dde; font-weight: bold; }
|
table.data th { background: #dde; font-weight: bold; }
|
||||||
|
table.data > tbody > tr:hover > td { background: #f3f3ff; }
|
||||||
|
.asn { cursor: help; }
|
||||||
|
.asn:hover { background: #dde; }
|
||||||
|
#asn-tip { position: fixed; z-index: 10; max-width: 36em; background: #ffffe8; border: 1px solid #99a; padding: 2px 6px; font-size: 12px; pointer-events: none; }
|
||||||
.mono, table.data td.mono { font-family: monospace; }
|
.mono, table.data td.mono { font-family: monospace; }
|
||||||
.ok { color: #080; font-weight: bold; } .bad { color: #c00; font-weight: bold; } .warn { color: #a60; font-weight: bold; }
|
.ok { color: #080; font-weight: bold; } .bad { color: #c00; font-weight: bold; } .warn { color: #a60; font-weight: bold; }
|
||||||
.dim { color: #666; }
|
.dim { color: #666; }
|
||||||
@@ -53,7 +59,7 @@ Router ID <b id="s-router-id">...</b>, local AS <b id="s-asn">...</b><br>
|
|||||||
</div>
|
</div>
|
||||||
<div class="righthand">
|
<div class="righthand">
|
||||||
Running on <span id="s-hostname">...</span><br>
|
Running on <span id="s-hostname">...</span><br>
|
||||||
View: <a href="/metricz">variables</a>, <a href="/api/status">status json</a>, <a href="/api/peers">peers json</a>, <a href="/api/routes/local">originated routes json</a>, <a href="/api/routes/received">received routes json</a>, <a href="/api/routes/rejected">refused routes json</a>, <a href="/api/rpki">rpki json</a>, <a href="/api/kernel">kernel json</a><br>
|
View: <a href="/metricz">variables</a>, <a href="/api/status">status json</a>, <a href="/api/peers">peers json</a>, <a href="/api/routes/local">originated routes json</a>, <a href="/api/routes/received">received routes json</a>, <a href="/api/routes/rejected">rejected routes json</a>, <a href="/api/rpki">rpki json</a>, <a href="/api/kernel">kernel json</a><br>
|
||||||
<span id="refresh-ctl"></span><br>
|
<span id="refresh-ctl"></span><br>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -65,7 +71,7 @@ View: <a href="/metricz">variables</a>, <a href="/api/status">status json</a>, <
|
|||||||
|
|
||||||
<h1 id="peers">Peers (<span id="peers-count">...</span>)</h1>
|
<h1 id="peers">Peers (<span id="peers-count">...</span>)</h1>
|
||||||
<table class="data">
|
<table class="data">
|
||||||
<thead><tr><th>name</th><th>peer</th><th>AS</th><th>state</th><th>opened by</th><th>local address (next hop)</th><th>established</th><th>uptime</th><th>4-byte AS</th><th>address families</th><th>routes</th><th>refused</th><th>detail</th></tr></thead>
|
<thead><tr><th>name</th><th>peer</th><th>AS</th><th>state</th><th>opened by</th><th>local address (next hop)</th><th>established</th><th>uptime</th><th>4-byte AS</th><th>address families</th><th>routes</th><th title="Announcements rejected by the import policy since the daemon started. A route that is announced again and rejected again counts again.">rejected announcements</th><th>detail</th></tr></thead>
|
||||||
<tbody id="peers-body"></tbody>
|
<tbody id="peers-body"></tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
@@ -101,8 +107,8 @@ from <select id="lookup-peer"><option value="">any peer</option></select>
|
|||||||
</table>
|
</table>
|
||||||
<div class="pager" id="received-pager-below"></div>
|
<div class="pager" id="received-pager-below"></div>
|
||||||
|
|
||||||
<h1 id="refused">Refused routes (<span id="refused-count">...</span>)</h1>
|
<h1 id="refused">Rejected routes (<span id="refused-count">...</span>)</h1>
|
||||||
<div class="dim">Routes that peers announced and their import policies did not take. The counts are since the daemon started; the latest refusals of each peer are kept.</div>
|
<div class="dim">Routes that peers announced and their import policies did not take. The counts are of announcements since the daemon started: a route that a peer announces again, and that is rejected again, counts again. The latest rejected routes of each peer are kept.</div>
|
||||||
<table class="data">
|
<table class="data">
|
||||||
<thead><tr><th>peer</th><th>prefix</th><th>AS path</th><th>reason</th><th>when</th><th>attributes</th></tr></thead>
|
<thead><tr><th>peer</th><th>prefix</th><th>AS path</th><th>reason</th><th>when</th><th>attributes</th></tr></thead>
|
||||||
<tbody id="refused-body"></tbody>
|
<tbody id="refused-body"></tbody>
|
||||||
@@ -151,7 +157,9 @@ from <select id="lookup-peer"><option value="">any peer</option></select>
|
|||||||
const fmtTime = unix => new Date(unix * 1000).toLocaleTimeString();
|
const fmtTime = unix => new Date(unix * 1000).toLocaleTimeString();
|
||||||
const commList = (list, named) => list.length ? list.map(c =>
|
const commList = (list, named) => list.length ? list.map(c =>
|
||||||
named && WELL_KNOWN[c] ? `<span title="${esc(c)}">${WELL_KNOWN[c]}</span>` : esc(c)).join(' ') : DASH;
|
named && WELL_KNOWN[c] ? `<span title="${esc(c)}">${WELL_KNOWN[c]}</span>` : esc(c)).join(' ') : DASH;
|
||||||
const asPath = p => p.length ? p.join(' ') : '<span class="dim">(empty)</span>';
|
// An AS number, whose holder's name is shown when it is pointed at.
|
||||||
|
const asn = n => `<span class="asn" data-asn="${esc(n)}">${esc(n)}</span>`;
|
||||||
|
const asPath = p => p.length ? p.map(asn).join(' ') : '<span class="dim">(empty)</span>';
|
||||||
|
|
||||||
async function getJSON(url) {
|
async function getJSON(url) {
|
||||||
const r = await fetch(url);
|
const r = await fetch(url);
|
||||||
@@ -163,6 +171,7 @@ from <select id="lookup-peer"><option value="">any peer</option></select>
|
|||||||
function renderStatus() {
|
function renderStatus() {
|
||||||
const s = state.status; if (!s) return;
|
const s = state.status; if (!s) return;
|
||||||
$('s-router-id').textContent = s.router_id; $('s-asn').textContent = s.local_asn;
|
$('s-router-id').textContent = s.router_id; $('s-asn').textContent = s.local_asn;
|
||||||
|
$('s-asn').className = 'asn'; $('s-asn').dataset.asn = s.local_asn;
|
||||||
$('s-version').textContent = s.version; $('s-hostname').textContent = s.hostname;
|
$('s-version').textContent = s.version; $('s-hostname').textContent = s.hostname;
|
||||||
$('s-started').textContent = new Date(s.started_at_unix * 1000).toString().replace(/ \(.*\)$/, '');
|
$('s-started').textContent = new Date(s.started_at_unix * 1000).toString().replace(/ \(.*\)$/, '');
|
||||||
tick();
|
tick();
|
||||||
@@ -186,13 +195,13 @@ from <select id="lookup-peer"><option value="">any peer</option></select>
|
|||||||
const up = p.state === 'Established';
|
const up = p.state === 'Established';
|
||||||
const detail = p.state === 'Backoff' ? `${esc(p.last_error || '')}; retry in ${until(p.retry_at_unix)}` : '';
|
const detail = p.state === 'Backoff' ? `${esc(p.last_error || '')}; retry in ${until(p.retry_at_unix)}` : '';
|
||||||
return `<tr>
|
return `<tr>
|
||||||
<td>${p.name ? esc(p.name) : '<span class="dim">(inbound)</span>'}</td><td class="mono">${esc(p.peer_addr)}</td><td>${p.peer_asn}${p.role ? ` <span class="dim" title="${p.role_confirmed ? 'The peer says so too (RFC 9234)' : 'By our configuration; the peer has not said what it is'}">${esc(p.role)}${p.role_confirmed ? ' ✓' : ''}</span>` : ''}</td>
|
<td>${p.name ? esc(p.name) : '<span class="dim">(inbound)</span>'}</td><td class="mono">${esc(p.peer_addr)}</td><td>${asn(p.peer_asn)}${p.role ? ` <span class="dim" title="${p.role_confirmed ? 'The peer says so too (RFC 9234)' : 'By our configuration; the peer has not said what it is'}">${esc(p.role)}${p.role_confirmed ? ' ✓' : ''}</span>` : ''}</td>
|
||||||
<td><span class="${STATE_CLASS[p.state] || ''}">${esc(p.state)}</span></td><td>${up ? (p.direction === 'outbound' ? 'us' : 'peer') : DASH}</td>
|
<td><span class="${STATE_CLASS[p.state] || ''}">${esc(p.state)}</span></td><td>${up ? (p.direction === 'outbound' ? 'us' : 'peer') : DASH}</td>
|
||||||
<td class="mono">${up ? esc(p.local_addr) : DASH}</td><td>${up ? fmtTime(p.established_at_unix) : DASH}</td>
|
<td class="mono">${up ? esc(p.local_addr) : DASH}</td><td>${up ? fmtTime(p.established_at_unix) : DASH}</td>
|
||||||
<td>${up ? age(p.established_at_unix) : DASH}</td><td>${up ? (p.four_octet_asn ? 'yes' : 'no') : DASH}</td>
|
<td>${up ? age(p.established_at_unix) : DASH}</td><td>${up ? (p.four_octet_asn ? 'yes' : 'no') : DASH}</td>
|
||||||
<td>${up ? esc((p.address_families || []).join(', ')) + addPath(p) + (p.extended_next_hop ? ' · IPv4 with IPv6 next hops' : '') : DASH}</td>
|
<td>${up ? esc((p.address_families || []).join(', ')) + addPath(p) + (p.extended_next_hop ? ' · IPv4 with IPv6 next hops' : '') : DASH}</td>
|
||||||
<td>${up ? p.routes : DASH}</td>
|
<td>${up ? p.routes : DASH}</td>
|
||||||
<td>${up ? (p.rejected_routes ? `<a class="bad" href="#refused">${p.rejected_routes}</a>` : 0) + (p.held_routes ? ` <span class="dim" title="Refused as RPKI or ASPA invalid, or for a next hop that cannot be reached, and kept aside, to be taken if that changes">(${p.held_routes} held)</span>` : '') : DASH}</td><td>${detail}</td></tr>`;
|
<td>${up ? (p.rejected_routes ? `<a class="bad" href="#refused">${p.rejected_routes}</a>` : 0) + (p.held_routes ? ` · <span title="Routes rejected as RPKI or ASPA invalid, or for a next hop that cannot be reached, and kept aside, to be taken if that changes">${p.held_routes} held now</span>` : '') : DASH}</td><td>${detail}</td></tr>`;
|
||||||
}).join(''));
|
}).join(''));
|
||||||
renderPeerChoices();
|
renderPeerChoices();
|
||||||
}
|
}
|
||||||
@@ -267,10 +276,10 @@ from <select id="lookup-peer"><option value="">any peer</option></select>
|
|||||||
function renderLocal() {
|
function renderLocal() {
|
||||||
const b = $('local-body'); $('local-count').textContent = state.local.length;
|
const b = $('local-body'); $('local-count').textContent = state.local.length;
|
||||||
if (!state.local.length) { setHTML(b, '<tr><td colspan="7" class="dim">This router originates no routes.</td></tr>'); return; }
|
if (!state.local.length) { setHTML(b, '<tr><td colspan="7" class="dim">This router originates no routes.</td></tr>'); return; }
|
||||||
const asn = state.status ? state.status.local_asn : '?';
|
const local = state.status ? state.status.local_asn : '?';
|
||||||
setHTML(b, state.local.map(r => `<tr>
|
setHTML(b, state.local.map(r => `<tr>
|
||||||
<td class="mono">${esc(r.prefix)}</td><td>${esc(r.origin)}</td>
|
<td class="mono">${esc(r.prefix)}</td><td>${esc(r.origin)}</td>
|
||||||
<td class="mono">${Array(1 + r.prepend).fill(asn).join(' ')}</td><td>${r.med ?? DASH}</td>
|
<td class="mono">${asPath(Array(1 + r.prepend).fill(local))}</td><td>${r.med ?? DASH}</td>
|
||||||
<td class="mono">${commList(r.communities, true)}</td><td class="mono">${commList(r.large_communities)}</td>
|
<td class="mono">${commList(r.communities, true)}</td><td class="mono">${commList(r.large_communities)}</td>
|
||||||
<td>${age(r.announced_at_unix)}</td></tr>`).join(''));
|
<td>${age(r.announced_at_unix)}</td></tr>`).join(''));
|
||||||
}
|
}
|
||||||
@@ -289,7 +298,7 @@ from <select id="lookup-peer"><option value="">any peer</option></select>
|
|||||||
setHTML(b, `<tr><td colspan="12" class="dim">${looking ? 'No route matches.' : 'No routes received from peers.'}</td></tr>`); return;
|
setHTML(b, `<tr><td colspan="12" class="dim">${looking ? 'No route matches.' : 'No routes received from peers.'}</td></tr>`); return;
|
||||||
}
|
}
|
||||||
setHTML(b, state.received.map(r => { const s = r.summary; return `<tr>
|
setHTML(b, state.received.map(r => { const s = r.summary; return `<tr>
|
||||||
<td class="mono">${esc(r.prefix)}</td><td class="mono">${r.peer_name ? esc(r.peer_name) + ' ' : ''}${esc(r.peer_addr)} <span class="dim">AS${r.peer_asn}${r.path_id ? ` path ${r.path_id}` : ''}</span></td><td class="mono">${asPath(s.as_path)}</td>
|
<td class="mono">${esc(r.prefix)}</td><td class="mono">${r.peer_name ? esc(r.peer_name) + ' ' : ''}${esc(r.peer_addr)} <span class="dim">AS${asn(r.peer_asn)}${r.path_id ? ` path ${r.path_id}` : ''}</span></td><td class="mono">${asPath(s.as_path)}</td>
|
||||||
<td class="mono">${s.next_hop ? esc(s.next_hop) : DASH}</td><td>${s.origin ? esc(s.origin) : DASH}</td><td>${s.med ?? DASH}</td><td>${s.local_pref ?? DASH}</td>
|
<td class="mono">${s.next_hop ? esc(s.next_hop) : DASH}</td><td>${s.origin ? esc(s.origin) : DASH}</td><td>${s.med ?? DASH}</td><td>${s.local_pref ?? DASH}</td>
|
||||||
<td class="mono">${commList(s.communities, true)}</td><td class="mono">${commList(s.large_communities)}</td>
|
<td class="mono">${commList(s.communities, true)}</td><td class="mono">${commList(s.large_communities)}</td>
|
||||||
<td>${r.rpki ? `<span class="${RPKI_CLASS[r.rpki] || ''}">${esc(r.rpki)}</span>` : DASH}</td>
|
<td>${r.rpki ? `<span class="${RPKI_CLASS[r.rpki] || ''}">${esc(r.rpki)}</span>` : DASH}</td>
|
||||||
@@ -349,13 +358,14 @@ from <select id="lookup-peer"><option value="">any peer</option></select>
|
|||||||
function renderRefused() {
|
function renderRefused() {
|
||||||
const b = $('refused-body');
|
const b = $('refused-body');
|
||||||
const total = state.refused.reduce((n, p) => n + p.total, 0);
|
const total = state.refused.reduce((n, p) => n + p.total, 0);
|
||||||
$('refused-count').textContent = total ? `${total} from ${state.refused.length} peer${state.refused.length === 1 ? '' : 's'}` : '0';
|
const announcements = n => `${n} announcement${n === 1 ? '' : 's'}`;
|
||||||
if (!state.refused.length) { setHTML(b, '<tr><td colspan="6" class="dim">Nothing a peer announced has been refused.</td></tr>'); return; }
|
$('refused-count').textContent = total ? `${announcements(total)} from ${state.refused.length} peer${state.refused.length === 1 ? '' : 's'}` : '0';
|
||||||
|
if (!state.refused.length) { setHTML(b, '<tr><td colspan="6" class="dim">Nothing a peer announced has been rejected.</td></tr>'); return; }
|
||||||
setHTML(b, state.refused.map(p => {
|
setHTML(b, state.refused.map(p => {
|
||||||
const why = Object.entries(p.by_reason).sort((a, c) => c[1] - a[1]).map(([reason, n]) => `${n} ${esc(reason)}`).join(', ');
|
const why = Object.entries(p.by_reason).sort((a, c) => c[1] - a[1]).map(([reason, n]) => `${n} ${esc(reason)}`).join(', ');
|
||||||
const more = p.recent.length > REFUSED_SHOWN
|
const more = p.recent.length > REFUSED_SHOWN
|
||||||
? ` <span class="dim">Latest ${REFUSED_SHOWN} shown; <a href="/api/routes/rejected?peer=${encodeURIComponent(p.peer)}">the latest ${p.recent.length} as json</a>.</span>` : '';
|
? ` <span class="dim">Latest ${REFUSED_SHOWN} shown; <a href="/api/routes/rejected?peer=${encodeURIComponent(p.peer)}">the latest ${p.recent.length} as json</a>.</span>` : '';
|
||||||
return `<tr><td colspan="6"><b>${esc(p.peer)}</b> <span class="dim">${esc(p.peer_addr)}</span>: <span class="bad">${p.total} refused</span> (${why}).${more}</td></tr>` +
|
return `<tr><td colspan="6"><b>${esc(p.peer)}</b> <span class="dim">${esc(p.peer_addr)}</span>: <span class="bad">${announcements(p.total)} rejected</span> (${why}).${more}</td></tr>` +
|
||||||
p.recent.slice(0, REFUSED_SHOWN).map(r => `<tr><td></td>
|
p.recent.slice(0, REFUSED_SHOWN).map(r => `<tr><td></td>
|
||||||
<td class="mono">${esc(r.prefix)}${r.path_id ? ` <span class="dim">path ${r.path_id}</span>` : ''}</td>
|
<td class="mono">${esc(r.prefix)}${r.path_id ? ` <span class="dim">path ${r.path_id}</span>` : ''}</td>
|
||||||
<td class="mono">${asPath(r.as_path)}</td><td>${esc(r.reason)}</td><td>${age(r.at_unix)} ago</td>
|
<td class="mono">${asPath(r.as_path)}</td><td>${esc(r.reason)}</td><td>${age(r.at_unix)} ago</td>
|
||||||
@@ -447,6 +457,44 @@ from <select id="lookup-peer"><option value="">any peer</option></select>
|
|||||||
$('reload-now').addEventListener('click', e => { e.preventDefault(); loadAll().catch(console.error); });
|
$('reload-now').addEventListener('click', e => { e.preventDefault(); loadAll().catch(console.error); });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---- AS names ----------------------------------------------------------
|
||||||
|
// The holder of an AS number, from RIPEstat, which has every number
|
||||||
|
// (reserved ones too) and lets browsers ask it. Asked from here, once per
|
||||||
|
// number and page, only for a number that is pointed at for a moment.
|
||||||
|
const asNames = new Map();
|
||||||
|
function asName(n) {
|
||||||
|
if (!asNames.has(n)) {
|
||||||
|
asNames.set(n, fetch(`https://stat.ripe.net/data/as-overview/data.json?resource=AS${n}&sourceapp=ferrix_bgp`)
|
||||||
|
.then(r => r.ok ? r.json() : Promise.reject(new Error(`HTTP ${r.status}`)))
|
||||||
|
.then(j => j.data.holder || (j.data.block && j.data.block.desc) || 'no holder registered')
|
||||||
|
// Asked again next time: the network may be back.
|
||||||
|
.catch(e => { asNames.delete(n); return `name not available (${e.message})`; }));
|
||||||
|
}
|
||||||
|
return asNames.get(n);
|
||||||
|
}
|
||||||
|
const tip = document.createElement('div');
|
||||||
|
tip.id = 'asn-tip'; tip.hidden = true; document.body.append(tip);
|
||||||
|
let pointedAt = null, asking = 0;
|
||||||
|
function placeTip(el) {
|
||||||
|
const box = el.getBoundingClientRect();
|
||||||
|
tip.style.left = `${Math.max(0, Math.min(box.left, innerWidth - tip.offsetWidth - 4))}px`;
|
||||||
|
const below = box.bottom + 4;
|
||||||
|
tip.style.top = `${below + tip.offsetHeight > innerHeight ? box.top - tip.offsetHeight - 4 : below}px`;
|
||||||
|
}
|
||||||
|
document.addEventListener('mouseover', e => {
|
||||||
|
const el = e.target.closest('.asn');
|
||||||
|
if (el === pointedAt) return;
|
||||||
|
pointedAt = el; clearTimeout(asking);
|
||||||
|
const n = el && el.dataset.asn;
|
||||||
|
if (!n || !/^\d+$/.test(n)) { tip.hidden = true; return; }
|
||||||
|
// Passing over a path on the way somewhere else asks for nothing.
|
||||||
|
asking = setTimeout(() => {
|
||||||
|
tip.textContent = `AS${n}: ...`; tip.hidden = false; placeTip(el);
|
||||||
|
asName(n).then(name => { if (pointedAt === el) { tip.textContent = `AS${n}: ${name}`; placeTip(el); } });
|
||||||
|
}, 150);
|
||||||
|
});
|
||||||
|
addEventListener('scroll', () => { tip.hidden = true; pointedAt = null; clearTimeout(asking); }, { passive: true });
|
||||||
|
|
||||||
// ---- boot --------------------------------------------------------------
|
// ---- boot --------------------------------------------------------------
|
||||||
getJSON('/api/config').then(c => {
|
getJSON('/api/config').then(c => {
|
||||||
if (!c.text) return;
|
if (!c.text) return;
|
||||||
|
|||||||
@@ -29,8 +29,8 @@ The status page, `/statusz`, displays the following information:
|
|||||||
|
|
||||||
- The configuration that was read at startup.
|
- The configuration that was read at startup.
|
||||||
- Each neighbor, with its session state, address families, role and role
|
- Each neighbor, with its session state, address families, role and role
|
||||||
confirmation, number of accepted, rejected, and held routes, and last
|
confirmation, number of accepted routes, rejected announcements, and held
|
||||||
error.
|
routes, and last error.
|
||||||
- The originated routes, and a lookup of received routes.
|
- The originated routes, and a lookup of received routes.
|
||||||
- RPKI validation: the cache, the RTR version, and the age of the data.
|
- RPKI validation: the cache, the RTR version, and the age of the data.
|
||||||
- Kernel route installation: the number of installed routes, the number of
|
- Kernel route installation: the number of installed routes, the number of
|
||||||
@@ -39,6 +39,11 @@ The status page, `/statusz`, displays the following information:
|
|||||||
- Rejected routes, by neighbor and by reason.
|
- Rejected routes, by neighbor and by reason.
|
||||||
- A live event log.
|
- A live event log.
|
||||||
|
|
||||||
|
To display the name of the holder of an AS number, point to the number. The
|
||||||
|
browser requests the name from [RIPEstat](https://stat.ripe.net/); Ferrix
|
||||||
|
does not send any requests itself. If the browser cannot reach RIPEstat,
|
||||||
|
the page displays "name not available".
|
||||||
|
|
||||||
## Route Lookup
|
## Route Lookup
|
||||||
|
|
||||||
The status page provides a lookup of received routes. The HTTP endpoint
|
The status page provides a lookup of received routes. The HTTP endpoint
|
||||||
@@ -71,12 +76,20 @@ curl -s '127.0.0.1:8080/api/routes/received?address=193.0.0.1&best=true' | jq
|
|||||||
Ferrix does not store rejected routes in the routing table. For each
|
Ferrix does not store rejected routes in the routing table. For each
|
||||||
neighbor, it records the number of rejected routes for each reason, and the
|
neighbor, it records the number of rejected routes for each reason, and the
|
||||||
200 most recent rejected routes with all the path attributes they were
|
200 most recent rejected routes with all the path attributes they were
|
||||||
received with. The status page displays this information under "Refused
|
received with. The status page displays this information under "Rejected
|
||||||
routes"; to display the attributes of a rejected route, select **show** in
|
routes"; to display the attributes of a rejected route, select **show** in
|
||||||
its row. The HTTP endpoint `/api/routes/rejected` returns the same
|
its row. The HTTP endpoint `/api/routes/rejected` returns the same
|
||||||
information as JSON. To display the records for one neighbor, add
|
information as JSON. To display the records for one neighbor, add
|
||||||
`?peer=NAME`.
|
`?peer=NAME`.
|
||||||
|
|
||||||
|
The numbers count rejected announcements, not prefixes. When a neighbor
|
||||||
|
announces a route again, for example because an attribute changed, and
|
||||||
|
Ferrix rejects it again, the count increases again. Two neighbors that
|
||||||
|
advertise the same routes can therefore have different counts. The number
|
||||||
|
of routes currently held, because they are RPKI or ASPA invalid or their
|
||||||
|
next hop is unreachable, is displayed in the neighbor table as **held
|
||||||
|
now**.
|
||||||
|
|
||||||
```json
|
```json
|
||||||
[{"peer":"transit-a","total":3,"by_reason":{"RPKI invalid":2,"bogon prefix":1},
|
[{"peer":"transit-a","total":3,"by_reason":{"RPKI invalid":2,"bogon prefix":1},
|
||||||
"recent":[{"prefix":"203.0.113.0/24","reason":"RPKI invalid","as_path":[64496,64511]}]}]
|
"recent":[{"prefix":"203.0.113.0/24","reason":"RPKI invalid","as_path":[64496,64511]}]}]
|
||||||
|
|||||||
Reference in new issue
Block a user