Linux Network Namespace & veth Visualizer
function parseNetns(text) {
const ns = ['root'];
text.split('\n').forEach(line => {
const m = line.match(/^(\S+)/);
if (m && m[1]) ns.push(m[1]);
});
return [...new Set(ns)];
}
function parseLinks(text) {
const links = [];
text.split('\n').forEach(line => {
// Match: N: name@peer: or N: name:
const m = line.match(/^\d+:\s+([a-zA-Z0-9_.-]+)(?:@([a-zA-Z0-9_.-]+))?:/);
if (m) {
const [, name, peer] = m;
const type = name.startsWith('br') ? 'bridge'
: peer ? 'veth'
: name === 'lo' ? 'lo'
: 'eth';
links.push({ name, peer: peer || null, type });
}
});
return links;
}
function parseAddrs(text) {
const addrs = {};
let current = null;
text.split('\n').forEach(line => {
const ifMatch = line.match(/^\d+:\s+([a-zA-Z0-9_.-]+)(?:@\S+)?:/);
if (ifMatch) { current = ifMatch[1]; addrs[current] = addrs[current] || []; }
const ipMatch = line.match(/inet\s+(\d+\.\d+\.\d+\.\d+\/\d+)/);
if (ipMatch && current) addrs[current].push(ipMatch[1]);
});
return addrs;
}
function render() {
const nsText = document.getElementById('nsInput').value;
const linkText = document.getElementById('linkInput').value;
const addrText = document.getElementById('addrInput').value;
const namespaces = parseNetns(nsText);
const links = parseLinks(linkText);
const addrs = parseAddrs(addrText);
const elements = [];
// Namespace nodes
namespaces.forEach(ns => {
elements.push({ data: { id: `ns_${ns}`, label: ns === 'root' ? 'root\nnamespace' : ns, type: 'ns', ns } });
});
// Interface nodes and veth pair edges
const seenPairs = new Set();
links.filter(l => l.type !== 'lo').forEach(l => {
const iface = l.name;
const ip = (addrs[iface] || []).join('\n') || '';
const type = l.type;
elements.push({ data: { id: `if_${iface}`, label: iface + (ip ? '\n' + ip : ''), type, ip } });
// Connect interface to root namespace
elements.push({ data: { id: `ns_root-${iface}`, source: 'ns_root', target: `if_${iface}` } });
// veth pair edge
if (l.peer && !seenPairs.has(`${l.peer}-${iface}`)) {
seenPairs.add(`${iface}-${l.peer}`);
elements.push({ data: { id: `veth_${iface}_${l.peer}`, source: `if_${iface}`, target: `if_${l.peer}`, type: 'veth-pair' } });
}
});
document.getElementById('cySection').style.display = 'block';
if (cyInst) { cyInst.destroy(); cyInst = null; }
cyInst = cytoscape({
container: document.getElementById('cy'),
elements,
style: [
{ selector: 'node[type="ns"]', style: {
'label': 'data(label)', 'text-wrap': 'wrap',
'shape': 'roundrectangle', 'width': 120, 'height': 50,
'background-color': ele => ele.data('ns') === 'root' ? '#28a745' : '#0a3d62',
'color': '#fff', 'font-size': 12, 'font-weight': 'bold',
'text-valign': 'center', 'text-halign': 'center',
}},
{ selector: 'node[type="veth"]', style: {
'label': 'data(label)', 'text-wrap': 'wrap',
'shape': 'ellipse', 'width': 100, 'height': 50,
'background-color': '#4facfe', 'color': '#fff',
'font-size': 10, 'text-valign': 'center', 'text-halign': 'center',
}},
{ selector: 'node[type="bridge"]', style: {
'label': 'data(label)', 'shape': 'diamond',
'width': 90, 'height': 50,
'background-color': '#f7971e', 'color': '#fff',
'font-size': 11, 'text-valign': 'center', 'text-halign': 'center',
}},
{ selector: 'node[type="eth"]', style: {
'label': 'data(label)', 'text-wrap': 'wrap',
'shape': 'roundrectangle', 'width': 100, 'height': 40,
'background-color': '#6c757d', 'color': '#fff',
'font-size': 10, 'text-valign': 'center', 'text-halign': 'center',
}},
{ selector: 'edge', style: {
'curve-style': 'bezier', 'line-color': '#555', 'width': 2,
'target-arrow-shape': 'none',
}},
{ selector: 'edge[type="veth-pair"]', style: {
'line-color': '#4facfe', 'width': 3, 'line-style': 'dashed',
}},
{ selector: 'node:selected', style: { 'border-width': 3, 'border-color': '#ffd200' }},
],
layout: { name: 'cose', padding: 30 },
userZoomingEnabled: true,
});
cyInst.on('tap', 'node', evt => {
const d = evt.target.data();
let html = `${d.label.replace(/\n/g, ' / ')}
Type: ${d.type}`; if (d.ip) html += `
IPs: ${d.ip}`; document.getElementById('detailPanel').innerHTML = html; }); } function loadSample() { document.getElementById('nsInput').value = 'red (id: 0)\nblue (id: 1)'; document.getElementById('linkInput').value = `1: lo: mtu 65536
2: eth0: mtu 1500
3: veth-red@veth-red-peer: mtu 1500
4: veth-red-peer@veth-red: mtu 1500
5: veth-blue@veth-blue-peer: mtu 1500
6: veth-blue-peer@veth-blue: mtu 1500
7: br0: mtu 1500`;
document.getElementById('addrInput').value =
`3: veth-red@veth-red-peer:
inet 10.0.1.1/24 scope global veth-red
4: veth-red-peer@veth-red:
inet 10.0.1.2/24 scope global veth-red-peer
5: veth-blue@veth-blue-peer:
inet 10.0.2.1/24 scope global veth-blue
6: veth-blue-peer@veth-blue:
inet 10.0.2.2/24 scope global veth-blue-peer`;
render();
}
function clearAll() {
document.getElementById('nsInput').value = '';
document.getElementById('linkInput').value = '';
document.getElementById('addrInput').value = '';
document.getElementById('cySection').style.display = 'none';
if (cyInst) { cyInst.destroy(); cyInst = null; }
}
Type: ${d.type}`; if (d.ip) html += `
IPs: ${d.ip}`; document.getElementById('detailPanel').innerHTML = html; }); } function loadSample() { document.getElementById('nsInput').value = 'red (id: 0)\nblue (id: 1)'; document.getElementById('linkInput').value = `1: lo: