Skip to content

Instantly share code, notes, and snippets.

@Wh1terat
Created July 31, 2023 15:55
Show Gist options
  • Save Wh1terat/4e716b78278dd4dc3709a8ee949250e6 to your computer and use it in GitHub Desktop.
Save Wh1terat/4e716b78278dd4dc3709a8ee949250e6 to your computer and use it in GitHub Desktop.
IPv4 IPAM Visualiser
<!DOCTYPE html>
<html>
<head>
<title>IPAM</title>
</head>
<body>
<canvas id="ipam"></canvas>
<div id="tooltip" style="position: absolute; display: none; background-color: #ffffff; border: 1px solid #000000; padding: 5px;"></div>
<script>
class DrawIPAM {
constructor(supernet, canvas) {
this.CONFIG = {
"colors": {
"background": '#202020',
"supernet": '#404040',
"border": '#000000',
"header": '#c0c0c0',
"legend": '#c0c0c0',
"xaxis": '#c0c0c0',
"yaxis": '#c0c0c0',
"desc": '#000000',
"cidr": [
{cidr:"32",color:"#0000FF"},
{cidr:"31",color:"#7F7F00"},
{cidr:"30",color:"#FF963C"},
{cidr:"29",color:"#B4FFFF"},
{cidr:"28",color:"#00FF00"},
{cidr:"27",color:"#FFFF7F"},
{cidr:"26",color:"#FF00FF"},
{cidr:"25",color:"#00C8B4"},
{cidr:"24",color:"#FF007F"},
{cidr:"23",color:"#FF7FFF"},
{cidr:"22",color:"#00FF7F"},
{cidr:"21",color:"#007F00"},
{cidr:"20",color:"#FF7F7F"},
]
},
"fonts": {
"header": "bold 18pt Courier New",
"legend": "13pt Courier New",
"xaxis": "13pt Courier New",
"yaxis": "13pt Courier New",
"desc": "bold 12pt Courier New"
},
"cellHeight": 30,
"cellWidth": 5,
"borderTop": 125,
"borderBottom": 25,
"borderLeft": 160,
"borderRight": 25,
"headerX": 15,
"headerY": 30,
"legendX": 45,
"legendY": 50,
"legendRectSize": 15,
};
this.supernet = supernet;
this.prefixes = [];
this.canvas = canvas;
this.ctx = this.canvas.getContext("2d");
this.prefixRects = [];
this.subnetBase = supernet.split("/")[0];
this.prefixLength = supernet.split("/")[1];
this.numSubnets = 2 ** (24 - parseInt(this.prefixLength));
this.canvas.width = (256 * this.CONFIG.cellWidth) + this.CONFIG.borderLeft + this.CONFIG.borderRight;
this.canvas.height = (this.numSubnets * this.CONFIG.cellHeight) + this.CONFIG.borderTop + this.CONFIG.borderBottom;
this.ctx.fillStyle = this.CONFIG.colors.background;
this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
this.canvas.onclick = (event) => {
let x = event.pageX - this.canvas.offsetLeft;
let y = event.pageY - this.canvas.offsetTop;
let prefix = this.getPrefix(x, y);
if(prefix !== null) {
console.log(`${prefix.network} - ${prefix.description}`);
}
};
this.canvas.onmousemove = function(event) {
let x = event.pageX - this.canvas.offsetLeft;
let y = event.pageY - this.canvas.offsetTop;
let prefix = this.getPrefix(x, y);
if(prefix !== null) {
document.getElementById('tooltip').style.display = 'block';
document.getElementById('tooltip').style.left = event.pageX + 'px';
document.getElementById('tooltip').style.top = (event.pageY + 20) + 'px';
document.getElementById('tooltip').innerText = prefix.description;
} else {
document.getElementById('tooltip').style.display = 'none';
}
}.bind(this);
this.canvas.onmouseleave = function(event) {
document.getElementById('tooltip').style.display = 'none';
}.bind(this);
}
addPrefix(prefix) {
this.addPrefixes([prefix]);
}
addPrefixes(prefixes) {
this.prefixes = this.prefixes.concat(prefixes);
this.drawPrefixes();
}
removePrefix(prefix) {
this.removePrefixes([prefix]);
}
removePrefixes(prefixes) {
this.prefixes = this.prefixes.filter(existingPrefix => {
for(let prefix of prefixes) {
if(typeof prefix === 'string') {
if(existingPrefix.network === prefix) {
return false;
}
}
else if(typeof prefix === 'object') {
if(existingPrefix.network === prefix.network && existingPrefix.description === prefix.description) {
return false;
}
}
}
return true;
});
this.drawPrefixes();
}
drawPrefixes() {
this.drawHeader();
this.drawCidrChart();
this.drawSupernetRect();
this.drawColumnsAndLabels();
this.drawRows();
this.drawPrefixesRect();
}
drawHeader() {
this.ctx.fillStyle = this.CONFIG.colors.header;
this.ctx.font = this.CONFIG.fonts.header;
this.ctx.fillText(`Netblock ${this.supernet}`, this.CONFIG.headerX, this.CONFIG.headerY);
}
drawCidrChart() {
let cidrLegendXPos = this.CONFIG.legendX;
this.CONFIG.colors.cidr.forEach((item) => {
this.ctx.fillStyle = item.color;
this.ctx.fillRect(cidrLegendXPos, this.CONFIG.legendY, this.CONFIG.legendRectSize, this.CONFIG.legendRectSize);
this.ctx.font = this.CONFIG.fonts.legend;
this.ctx.fillStyle = this.CONFIG.colors.legend;
const text = "/" + item.cidr;
const textHeight = parseInt(this.ctx.font.match(/\d+/), 10);
const textWidth = this.ctx.measureText(text).width;
const textX = cidrLegendXPos + this.CONFIG.legendRectSize + (textWidth/4);
const textY = this.CONFIG.legendY + ((this.CONFIG.legendRectSize/2) + (textHeight/4));
this.ctx.fillText(text , textX, textY);
cidrLegendXPos += this.CONFIG.legendRectSize * 5;
});
}
drawSupernetRect() {
const rectWidth = canvas.width - this.CONFIG.borderLeft - this.CONFIG.borderRight;
const rectHeight = this.numSubnets * this.CONFIG.cellHeight;
// Draw Supernet rect
this.ctx.rect(this.CONFIG.borderLeft, this.CONFIG.borderTop, rectWidth, rectHeight);
this.ctx.fillStyle = this.CONFIG.colors.supernet;
this.ctx.fill();
// Draw Supernet border
this.ctx.lineWidth = 2;
this.ctx.strokeStyle = this.CONFIG.colors.border;
this.ctx.stroke();
}
drawColumnsAndLabels() {
this.ctx.beginPath();
this.ctx.save();
this.ctx.setLineDash([5, 1]);
for (let i = 0; i < 16; i++) {
const lineX = ((i * 16) * this.CONFIG.cellWidth) + this.CONFIG.borderLeft;
const lineY = canvas.height - this.CONFIG.borderBottom;
this.ctx.moveTo(lineX, this.CONFIG.borderTop);
this.ctx.lineTo(lineX, lineY);
this.ctx.save();
this.ctx.translate(lineX, this.CONFIG.borderTop - 5);
this.ctx.rotate(-0.5 * Math.PI);
this.ctx.fillStyle = this.CONFIG.colors.yaxis;
this.ctx.font = this.CONFIG.fonts.yaxis;
this.ctx.fillText((i*16).toString(), 0, 3);
this.ctx.restore();
}
this.ctx.stroke();
this.ctx.restore();
}
drawRows() {
const [baseA, baseB] = this.subnetBase.split(".");
this.ctx.beginPath();
for (let i = 0; i < this.numSubnets; i++) {
const lineX = (256 * this.CONFIG.cellWidth) + this.CONFIG.borderLeft;
const lineY = (i * this.CONFIG.cellHeight) + this.CONFIG.borderTop;
this.ctx.moveTo(this.CONFIG.borderLeft, lineY);
this.ctx.lineTo(lineX, lineY);
this.ctx.font = this.CONFIG.fonts.xaxis;
this.ctx.fillStyle = this.CONFIG.colors.xaxis;
const subnetAddress = `${baseA}.${baseB}.${i}.0`;
const textHeight = parseInt(this.ctx.font.match(/\d+/), 10);
const textWidth = this.ctx.measureText(subnetAddress).width;
const textX = 10; //((this.CONFIG.borderLeft/2) - (textWidth/2));
const textY = lineY + ((this.CONFIG.cellHeight/2) + (textHeight/2));
this.ctx.fillText(subnetAddress,textX,textY);
}
this.ctx.stroke();
}
drawPrefixesRect() {
this.prefixes.sort((a, b) => a.network.split('/')[1] - b.network.split('/')[1]);
this.prefixes.forEach((prefix) => {
var [prefixBase, prefixLength] = prefix.network.split("/");
var color = this.CONFIG.colors.cidr.find(c => c.cidr == prefixLength).color;
var [baseA, baseB, baseC, baseD] = prefixBase.split(".");
var startRow = Math.floor((parseInt(baseC) * 256 + parseInt(baseD)) / 256);
var numRows = Math.pow(2, (24 - parseInt(prefixLength)));
var numCols = Math.min(Math.pow(2, (32 - parseInt(prefixLength))), 256);
var rectX = this.CONFIG.borderLeft + (parseInt(baseD) * this.CONFIG.cellWidth);
var rectY = startRow * this.CONFIG.cellHeight + this.CONFIG.borderTop;
var rectWidth = numCols * this.CONFIG.cellWidth;
var rectHeight = this.CONFIG.cellHeight * numRows;
// Correcting the height for this.prefixes smaller than /24
if (parseInt(prefixLength) > 24) {
rectHeight = this.CONFIG.cellHeight;
}
// Draw the rectangle
this.ctx.beginPath();
this.ctx.fillStyle = color;
this.ctx.strokeStyle = 'black';
this.ctx.rect(rectX, rectY, rectWidth, rectHeight);
this.ctx.fill();
this.ctx.stroke();
this.ctx.closePath();
// Store drawn rectangle
this.prefixRects.push({
x: rectX,
y: rectY,
width: rectWidth,
height: rectHeight,
prefix: prefix
});
// Drawing the text on the rectangle if possible.
this.ctx.font = this.CONFIG.fonts.desc;
var textHeight = parseInt(this.ctx.font.match(/\d+/), 10);
var textWidth = this.ctx.measureText(prefix.description).width;
if (textHeight <= rectHeight && textWidth < rectWidth) {
var textX = rectX + (rectWidth - textWidth) / 2;
var textY = rectY + (rectHeight + textHeight) / 2 - 4;
this.ctx.fillStyle = this.CONFIG.colors.desc;
this.ctx.fillText(prefix.description, textX, textY);
}
});
}
getPrefix(x, y) {
for(let i = this.prefixRects.length - 1; i >= 0; i--) {
let rect = this.prefixRects[i];
if(y > rect.y && y < rect.y + rect.height && x > rect.x && x < rect.x + rect.width) {
return rect.prefix;
}
}
return null;
}
}
let prefixes = [
{ network: "192.168.0.128/25", description: "foo1" },
{ network: "192.168.0.0/24", description: "foo2" },
{ network: "192.168.4.0/23", description: "foo3"}
];
let prefixes2 = [
{ network: "192.168.0.64/26", description: "foo4" },
{ network: "192.168.3.0/24", description: "foo5" },
{ network: "192.168.10.0/23", description: "foo3"}
];
let canvas = document.getElementById('ipam');
let ipam = new DrawIPAM("192.168.0.0/20", canvas);
ipam.addPrefixes(prefixes);
ipam.addPrefixes(prefixes2);
ipam.removePrefix("192.168.0.128/25");
ipam.removePrefixes([{ network: "192.168.3.0/24", description: "foo5" }]);
ipam.addPrefix({ network: "192.168.14.64/26", description: "foo6" });
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment