2025-09-01 17:40:27 -07:00
|
|
|
import {revive, persist, labelInput, labelSelect} from '/js/util.js';
|
|
|
|
|
|
|
|
export default {
|
|
|
|
id:'subnet', name:'IP Subnet Calculator', about:'Calculate IPv4 and IPv6 subnet information, CIDR notation, and network ranges.',
|
|
|
|
render(root){
|
|
|
|
const key='calc_subnet_v1';
|
|
|
|
const s = revive(key,{
|
|
|
|
ipVersion: 'ipv4',
|
|
|
|
ipAddress: '192.168.1.0',
|
|
|
|
subnetMask: '255.255.255.0',
|
|
|
|
cidr: 24,
|
|
|
|
customCidr: 24
|
|
|
|
});
|
|
|
|
|
|
|
|
const ui = document.createElement('div');
|
|
|
|
|
|
|
|
// IP version selector
|
|
|
|
const versionSection = document.createElement('div');
|
|
|
|
versionSection.innerHTML = `
|
|
|
|
<h3 style="color: var(--accent); margin-bottom: 15px;">IP Version</h3>
|
|
|
|
<div style="margin-bottom: 20px;">
|
|
|
|
<select name="ipVersion" data-ui="lite" style="width: 200px; padding: 12px; border: 1px solid var(--border); border-radius: 8px; font-size: 16px;">
|
|
|
|
<option value="ipv4" selected>IPv4</option>
|
|
|
|
<option value="ipv6">IPv6</option>
|
|
|
|
</select>
|
|
|
|
</div>
|
|
|
|
`;
|
|
|
|
|
|
|
|
// IPv4 input section
|
|
|
|
const ipv4Section = document.createElement('div');
|
|
|
|
ipv4Section.innerHTML = `
|
|
|
|
<div id="ipv4-inputs" style="display: block;">
|
|
|
|
<h3 style="color: var(--accent); margin-bottom: 15px;">IPv4 Configuration</h3>
|
|
|
|
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 15px; margin-bottom: 20px;">
|
|
|
|
<div>
|
|
|
|
<label style="display: block; margin-bottom: 8px; font-weight: 500; color: var(--text);">
|
|
|
|
IP Address
|
|
|
|
</label>
|
|
|
|
<input type="text" name="ipAddress" value="${s.ipAddress}"
|
|
|
|
placeholder="192.168.1.0"
|
|
|
|
style="width: 100%; padding: 12px; border: 1px solid var(--border); border-radius: 8px; font-size: 16px;">
|
|
|
|
</div>
|
|
|
|
<div>
|
|
|
|
<label style="display: block; margin-bottom: 8px; font-weight: 500; color: var(--text);">
|
|
|
|
Subnet Mask
|
|
|
|
</label>
|
|
|
|
<input type="text" name="subnetMask" value="${s.subnetMask}"
|
|
|
|
placeholder="255.255.255.0"
|
|
|
|
style="width: 100%; padding: 12px; border: 1px solid var(--border); border-radius: 8px; font-size: 16px;">
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div style="margin-bottom: 20px;">
|
|
|
|
<label style="display: block; margin-bottom: 8px; font-weight: 500; color: var(--text);">
|
|
|
|
CIDR Notation (Alternative)
|
|
|
|
</label>
|
|
|
|
<input type="number" name="cidr" value="${s.cidr}" min="0" max="32"
|
|
|
|
style="width: 200px; padding: 12px; border: 1px solid var(--border); border-radius: 8px; font-size: 16px;">
|
|
|
|
<span style="color: var(--muted); margin-left: 10px;">/</span>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
`;
|
|
|
|
|
|
|
|
// IPv6 input section
|
|
|
|
const ipv6Section = document.createElement('div');
|
|
|
|
ipv6Section.id = 'ipv6-inputs';
|
|
|
|
ipv6Section.style.display = 'none';
|
|
|
|
|
|
|
|
// IPv6 title
|
|
|
|
const ipv6Title = document.createElement('h3');
|
|
|
|
ipv6Title.style.cssText = 'color: var(--accent); margin-bottom: 15px;';
|
|
|
|
ipv6Title.textContent = 'IPv6 Configuration';
|
|
|
|
ipv6Section.appendChild(ipv6Title);
|
|
|
|
|
|
|
|
// IPv6 Address input container
|
|
|
|
const ipv6AddressContainer = document.createElement('div');
|
|
|
|
ipv6AddressContainer.style.cssText = 'margin-bottom: 20px;';
|
|
|
|
|
|
|
|
const ipv6AddressLabel = document.createElement('label');
|
|
|
|
ipv6AddressLabel.style.cssText = 'display: block; margin-bottom: 8px; font-weight: 500; color: var(--text);';
|
|
|
|
ipv6AddressLabel.textContent = 'IPv6 Address';
|
|
|
|
|
|
|
|
const ipv6AddressInput = document.createElement('input');
|
|
|
|
ipv6AddressInput.type = 'text';
|
|
|
|
ipv6AddressInput.name = 'ipv6Address';
|
|
|
|
ipv6AddressInput.value = '2001:db8::';
|
|
|
|
ipv6AddressInput.placeholder = '2001:db8::';
|
|
|
|
ipv6AddressInput.style.cssText = 'width: 100%; padding: 12px; border: 1px solid var(--border); border-radius: 8px; font-size: 16px;';
|
|
|
|
|
|
|
|
ipv6AddressContainer.appendChild(ipv6AddressLabel);
|
|
|
|
ipv6AddressContainer.appendChild(ipv6AddressInput);
|
|
|
|
|
|
|
|
// IPv6 CIDR input container
|
|
|
|
const ipv6CidrContainer = document.createElement('div');
|
|
|
|
ipv6CidrContainer.style.cssText = 'margin-bottom: 20px;';
|
|
|
|
|
|
|
|
const ipv6CidrLabel = document.createElement('label');
|
|
|
|
ipv6CidrLabel.style.cssText = 'display: block; margin-bottom: 8px; font-weight: 500; color: var(--text);';
|
|
|
|
ipv6CidrLabel.textContent = 'CIDR Prefix Length';
|
|
|
|
|
|
|
|
const ipv6CidrInput = document.createElement('input');
|
|
|
|
ipv6CidrInput.type = 'number';
|
|
|
|
ipv6CidrInput.name = 'ipv6Cidr';
|
|
|
|
ipv6CidrInput.value = '64';
|
|
|
|
ipv6CidrInput.min = '0';
|
|
|
|
ipv6CidrInput.max = '128';
|
|
|
|
ipv6CidrInput.style.cssText = 'width: 200px; padding: 12px; border: 1px solid var(--border); border-radius: 8px; font-size: 16px;';
|
|
|
|
|
|
|
|
const ipv6CidrSpan = document.createElement('span');
|
|
|
|
ipv6CidrSpan.style.cssText = 'color: var(--muted); margin-left: 10px;';
|
|
|
|
ipv6CidrSpan.textContent = '/';
|
|
|
|
|
|
|
|
ipv6CidrContainer.appendChild(ipv6CidrLabel);
|
|
|
|
ipv6CidrContainer.appendChild(ipv6CidrInput);
|
|
|
|
ipv6CidrContainer.appendChild(ipv6CidrSpan);
|
|
|
|
|
|
|
|
// Add all elements to IPv6 section
|
|
|
|
ipv6Section.appendChild(ipv6AddressContainer);
|
|
|
|
ipv6Section.appendChild(ipv6CidrContainer);
|
|
|
|
|
|
|
|
ui.append(versionSection, ipv4Section, ipv6Section);
|
|
|
|
|
|
|
|
// Results section
|
|
|
|
const out = document.createElement('div');
|
|
|
|
out.className = 'result';
|
|
|
|
out.style.cssText = `
|
|
|
|
margin: 20px 0;
|
|
|
|
padding: 15px;
|
|
|
|
background: var(--k-bg);
|
|
|
|
border-radius: 8px;
|
|
|
|
border-left: 4px solid var(--accent);
|
|
|
|
`;
|
|
|
|
ui.append(out);
|
|
|
|
|
|
|
|
// Utility functions
|
|
|
|
function getNetworkClass(ip) {
|
|
|
|
const firstOctet = parseInt(ip.split('.')[0]);
|
|
|
|
if (firstOctet >= 0 && firstOctet <= 127) return 'A';
|
|
|
|
if (firstOctet >= 128 && firstOctet <= 191) return 'B';
|
|
|
|
if (firstOctet >= 192 && firstOctet <= 223) return 'C';
|
|
|
|
if (firstOctet >= 224 && firstOctet <= 239) return 'D';
|
|
|
|
if (firstOctet >= 240 && firstOctet <= 255) return 'E';
|
|
|
|
return 'Unknown';
|
|
|
|
}
|
|
|
|
|
2025-09-01 22:04:35 -07:00
|
|
|
function getRFCNetworkInfo(ip, cidr) {
|
|
|
|
const ipLong = ipToLong(ip);
|
|
|
|
const parts = ip.split('.');
|
|
|
|
const firstOctet = parseInt(parts[0]);
|
|
|
|
const secondOctet = parseInt(parts[1]);
|
|
|
|
|
|
|
|
// Check for specific RFC-defined ranges
|
|
|
|
if (firstOctet === 0) {
|
|
|
|
return {
|
|
|
|
type: 'Current Network',
|
|
|
|
description: 'RFC 1122: "This host on this network" - used only as source address',
|
|
|
|
rfc: 'RFC 1122',
|
|
|
|
cidr: '0.0.0.0/8'
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
if (firstOctet === 10) {
|
|
|
|
return {
|
|
|
|
type: 'Private Network',
|
|
|
|
description: 'RFC 1918: Private IP address range for Class A networks',
|
|
|
|
rfc: 'RFC 1918',
|
|
|
|
cidr: '10.0.0.0/8'
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
if (firstOctet === 127) {
|
|
|
|
return {
|
|
|
|
type: 'Loopback',
|
|
|
|
description: 'RFC 1122: Loopback addresses - packets sent to this address are processed locally',
|
|
|
|
rfc: 'RFC 1122',
|
|
|
|
cidr: '127.0.0.0/8'
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
if (firstOctet === 169 && secondOctet === 254) {
|
|
|
|
return {
|
|
|
|
type: 'Link-Local',
|
|
|
|
description: 'RFC 3927: Automatic Private IP Addressing (APIPA) - used when DHCP fails',
|
|
|
|
rfc: 'RFC 3927',
|
|
|
|
cidr: '169.254.0.0/16'
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
if (firstOctet === 172 && secondOctet >= 16 && secondOctet <= 31) {
|
|
|
|
return {
|
|
|
|
type: 'Private Network',
|
|
|
|
description: 'RFC 1918: Private IP address range for Class B networks',
|
|
|
|
rfc: 'RFC 1918',
|
|
|
|
cidr: '172.16.0.0/12'
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
if (firstOctet === 192 && secondOctet === 168) {
|
|
|
|
return {
|
|
|
|
type: 'Private Network',
|
|
|
|
description: 'RFC 1918: Private IP address range for Class C networks',
|
|
|
|
rfc: 'RFC 1918',
|
|
|
|
cidr: '192.168.0.0/16'
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
if (firstOctet === 192 && secondOctet === 0 && parseInt(parts[2]) === 0) {
|
|
|
|
return {
|
|
|
|
type: 'IETF Protocol Assignments',
|
|
|
|
description: 'RFC 5736: Reserved for IETF protocol assignments',
|
|
|
|
rfc: 'RFC 5736',
|
|
|
|
cidr: '192.0.0.0/24'
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
if (firstOctet === 192 && secondOctet === 0 && parseInt(parts[2]) === 2) {
|
|
|
|
return {
|
|
|
|
type: 'Test-Net',
|
|
|
|
description: 'RFC 5737: Documentation and example code (TEST-NET-1)',
|
|
|
|
rfc: 'RFC 5737',
|
|
|
|
cidr: '192.0.2.0/24'
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
if (firstOctet === 192 && secondOctet === 88 && parseInt(parts[2]) === 99) {
|
|
|
|
return {
|
|
|
|
type: '6to4 Relay',
|
|
|
|
description: 'RFC 3068: IPv6 to IPv4 relay anycast addresses',
|
|
|
|
rfc: 'RFC 3068',
|
|
|
|
cidr: '192.88.99.0/24'
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
if (firstOctet === 198 && secondOctet === 18) {
|
|
|
|
return {
|
|
|
|
type: 'Benchmark Testing',
|
|
|
|
description: 'RFC 2544: Network interconnect device benchmark testing',
|
|
|
|
rfc: 'RFC 2544',
|
|
|
|
cidr: '198.18.0.0/15'
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
if (firstOctet === 198 && secondOctet === 51 && parseInt(parts[2]) === 100) {
|
|
|
|
return {
|
|
|
|
type: 'Test-Net',
|
|
|
|
description: 'RFC 5737: Documentation and example code (TEST-NET-2)',
|
|
|
|
rfc: 'RFC 5737',
|
|
|
|
cidr: '198.51.100.0/24'
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
if (firstOctet === 203 && secondOctet === 0 && parseInt(parts[2]) === 113) {
|
|
|
|
return {
|
|
|
|
type: 'Test-Net',
|
|
|
|
description: 'RFC 5737: Documentation and example code (TEST-NET-3)',
|
|
|
|
rfc: 'RFC 5737',
|
|
|
|
cidr: '203.0.113.0/24'
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
if (firstOctet === 100 && secondOctet >= 64 && secondOctet <= 127) {
|
|
|
|
return {
|
|
|
|
type: 'CGNAT',
|
|
|
|
description: 'RFC 6598: Carrier-Grade NAT (CGN) shared address space',
|
|
|
|
rfc: 'RFC 6598',
|
|
|
|
cidr: '100.64.0.0/10'
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
if (firstOctet >= 224 && firstOctet <= 239) {
|
|
|
|
return {
|
|
|
|
type: 'Multicast',
|
|
|
|
description: 'RFC 1112: Multicast addresses - used for one-to-many communication',
|
|
|
|
rfc: 'RFC 1112',
|
|
|
|
cidr: '224.0.0.0/4'
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
if (firstOctet >= 240 && firstOctet <= 255) {
|
|
|
|
return {
|
|
|
|
type: 'Reserved',
|
|
|
|
description: 'RFC 1112: Reserved for future use (formerly Class E)',
|
|
|
|
rfc: 'RFC 1112',
|
|
|
|
cidr: '240.0.0.0/4'
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ipLong === 0xFFFFFFFF) {
|
|
|
|
return {
|
|
|
|
type: 'Broadcast',
|
|
|
|
description: 'RFC 919: Limited broadcast address - reaches all hosts on the local network',
|
|
|
|
rfc: 'RFC 919'
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check for other broadcast addresses based on CIDR
|
|
|
|
if (cidr < 32) {
|
|
|
|
const networkLong = (ipLong & ((0xFFFFFFFF << (32 - cidr)) >>> 0)) >>> 0;
|
|
|
|
const broadcastLong = (networkLong | (~((0xFFFFFFFF << (32 - cidr)) >>> 0)) >>> 0) >>> 0;
|
|
|
|
if (ipLong === broadcastLong) {
|
|
|
|
return {
|
|
|
|
type: 'Network Broadcast',
|
|
|
|
description: `Broadcast address for /${cidr} network - reaches all hosts in this subnet`,
|
|
|
|
rfc: 'RFC 919'
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Default case - public IP
|
|
|
|
return {
|
|
|
|
type: 'Public IP',
|
|
|
|
description: 'Globally routable IP address on the Internet',
|
|
|
|
rfc: null
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
function getIPv6NetworkInfo(ipv6, cidr) {
|
|
|
|
const expanded = expandIPv6(ipv6);
|
|
|
|
const parts = expanded.split(':');
|
|
|
|
|
|
|
|
// Convert to BigInt for comparison
|
|
|
|
let ipv6Long = 0n;
|
|
|
|
for (let i = 0; i < 8; i++) {
|
|
|
|
const part = parseInt(parts[i], 16);
|
|
|
|
ipv6Long = (ipv6Long << 16n) + BigInt(part);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check for specific IPv6 reserved ranges
|
|
|
|
|
|
|
|
// ::/128 - Unspecified address
|
|
|
|
if (ipv6Long === 0n) {
|
|
|
|
return {
|
|
|
|
type: 'Unspecified',
|
|
|
|
description: 'RFC 4291: Unspecified address - used only as source address',
|
|
|
|
rfc: 'RFC 4291',
|
|
|
|
cidr: '::/128'
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
// ::1/128 - Loopback
|
|
|
|
if (ipv6Long === 1n) {
|
|
|
|
return {
|
|
|
|
type: 'Loopback',
|
|
|
|
description: 'RFC 4291: Loopback address - equivalent to IPv4 127.0.0.1',
|
|
|
|
rfc: 'RFC 4291',
|
|
|
|
cidr: '::1/128'
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
// 2000::/3 - Global Unicast (public IPv6)
|
|
|
|
if ((ipv6Long >> 125n) === 4n) { // First 3 bits are 001
|
|
|
|
return {
|
|
|
|
type: 'Global Unicast',
|
|
|
|
description: 'RFC 4291: Globally routable IPv6 addresses (public Internet)',
|
|
|
|
rfc: 'RFC 4291',
|
|
|
|
cidr: '2000::/3'
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
// fc00::/7 - Unique Local Address (ULA)
|
|
|
|
if ((ipv6Long >> 121n) === 0x7Dn) { // First 7 bits are 1111110
|
|
|
|
return {
|
|
|
|
type: 'Unique Local Address',
|
|
|
|
description: 'RFC 4193: Private IPv6 addresses (equivalent to IPv4 private ranges)',
|
|
|
|
rfc: 'RFC 4193',
|
|
|
|
cidr: 'fc00::/7'
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
// fe80::/10 - Link-Local
|
|
|
|
if ((ipv6Long >> 118n) === 0xFE80n >> 6n) { // First 10 bits are 1111111010
|
|
|
|
return {
|
|
|
|
type: 'Link-Local',
|
|
|
|
description: 'RFC 4291: Link-local addresses - valid only on the local network segment',
|
|
|
|
rfc: 'RFC 4291',
|
|
|
|
cidr: 'fe80::/10'
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
// ff00::/8 - Multicast
|
|
|
|
if ((ipv6Long >> 120n) === 0xFFn) { // First 8 bits are 11111111
|
|
|
|
return {
|
|
|
|
type: 'Multicast',
|
|
|
|
description: 'RFC 4291: IPv6 multicast addresses - used for one-to-many communication',
|
|
|
|
rfc: 'RFC 4291',
|
|
|
|
cidr: 'ff00::/8'
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
// 2001:db8::/32 - Documentation
|
|
|
|
if ((ipv6Long >> 96n) === 0x20010DB8n) {
|
|
|
|
return {
|
|
|
|
type: 'Documentation',
|
|
|
|
description: 'RFC 3849: Reserved for documentation and example code',
|
|
|
|
rfc: 'RFC 3849',
|
|
|
|
cidr: '2001:db8::/32'
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
// 2001::/32 - Teredo
|
|
|
|
if ((ipv6Long >> 96n) === 0x20010000n) {
|
|
|
|
return {
|
|
|
|
type: 'Teredo',
|
|
|
|
description: 'RFC 4380: Teredo tunneling - IPv6 over UDP over IPv4',
|
|
|
|
rfc: 'RFC 4380',
|
|
|
|
cidr: '2001::/32'
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
// 2002::/16 - 6to4
|
|
|
|
if ((ipv6Long >> 112n) === 0x2002n) {
|
|
|
|
return {
|
|
|
|
type: '6to4',
|
|
|
|
description: 'RFC 3056: 6to4 tunneling - automatic IPv6 over IPv4 tunneling',
|
|
|
|
rfc: 'RFC 3056',
|
|
|
|
cidr: '2002::/16'
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
// 64:ff9b::/96 - IPv4-IPv6 Translation
|
|
|
|
if ((ipv6Long >> 96n) === 0x64FF9B000000000000000000n) {
|
|
|
|
return {
|
|
|
|
type: 'IPv4-IPv6 Translation',
|
|
|
|
description: 'RFC 6052: Well-known prefix for IPv4-IPv6 translation',
|
|
|
|
rfc: 'RFC 6052',
|
|
|
|
cidr: '64:ff9b::/96'
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
// 100::/64 - Discard-Only
|
|
|
|
if ((ipv6Long >> 64n) === 0x100000000000000n) {
|
|
|
|
return {
|
|
|
|
type: 'Discard-Only',
|
|
|
|
description: 'RFC 6666: Discard-only address block - packets are discarded',
|
|
|
|
rfc: 'RFC 6666',
|
|
|
|
cidr: '100::/64'
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
// ::ffff:0:0/96 - IPv4-mapped IPv6
|
|
|
|
if ((ipv6Long >> 96n) === 0xFFFF00000000n) {
|
|
|
|
return {
|
|
|
|
type: 'IPv4-mapped IPv6',
|
|
|
|
description: 'RFC 4291: IPv4 addresses mapped to IPv6 format',
|
|
|
|
rfc: 'RFC 4291',
|
|
|
|
cidr: '::ffff:0:0/96'
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
// ::/96 - IPv4-compatible IPv6 (deprecated)
|
|
|
|
if ((ipv6Long >> 96n) === 0n && (ipv6Long & 0xFFFFFFFF00000000n) === 0n) {
|
|
|
|
return {
|
|
|
|
type: 'IPv4-compatible IPv6',
|
|
|
|
description: 'RFC 4291: IPv4-compatible IPv6 addresses (deprecated)',
|
|
|
|
rfc: 'RFC 4291',
|
|
|
|
cidr: '::/96'
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
// Default case - other reserved or unknown
|
|
|
|
return {
|
|
|
|
type: 'Reserved/Unknown',
|
|
|
|
description: 'Reserved or unassigned IPv6 address range',
|
|
|
|
rfc: null
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2025-09-01 17:40:27 -07:00
|
|
|
function ipToLong(ip) {
|
|
|
|
return ip.split('.').reduce((acc, octet) => (acc << 8) + parseInt(octet), 0) >>> 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
function longToIp(long) {
|
|
|
|
return [
|
|
|
|
(long >>> 24) & 255,
|
|
|
|
(long >>> 16) & 255,
|
|
|
|
(long >>> 8) & 255,
|
|
|
|
long & 255
|
|
|
|
].join('.');
|
|
|
|
}
|
|
|
|
|
|
|
|
function cidrToMask(cidr) {
|
|
|
|
// Handle edge cases
|
|
|
|
if (cidr === 0) return '0.0.0.0';
|
|
|
|
if (cidr === 32) return '255.255.255.255';
|
|
|
|
|
|
|
|
// For other CIDR values, use the bit manipulation approach
|
|
|
|
// But handle the 32-bit overflow issue
|
|
|
|
let mask;
|
|
|
|
if (cidr === 31) {
|
|
|
|
// Special case for /31 to avoid overflow
|
|
|
|
mask = 0xFFFFFFFE;
|
|
|
|
} else {
|
|
|
|
// Calculate mask: create a number with 'cidr' leading 1s
|
|
|
|
mask = 0;
|
|
|
|
for (let i = 31; i >= (32 - cidr); i--) {
|
|
|
|
mask |= (1 << i);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return longToIp(mask);
|
|
|
|
}
|
|
|
|
|
|
|
|
function maskToCidr(mask) {
|
|
|
|
const maskLong = ipToLong(mask);
|
|
|
|
|
|
|
|
// Handle edge cases
|
|
|
|
if (maskLong === 0) return 0; // 0.0.0.0 = /0
|
|
|
|
if (maskLong === 0xFFFFFFFF) return 32; // 255.255.255.255 = /32
|
|
|
|
|
|
|
|
// Count leading 1s in binary representation
|
|
|
|
let cidr = 0;
|
|
|
|
let temp = maskLong;
|
|
|
|
|
|
|
|
// Count consecutive 1s from left to right
|
|
|
|
for (let i = 31; i >= 0; i--) {
|
|
|
|
if ((temp & (1 << i)) !== 0) {
|
|
|
|
cidr++;
|
|
|
|
} else {
|
|
|
|
break; // Stop at first 0
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return cidr;
|
|
|
|
}
|
|
|
|
|
|
|
|
function validateIPv4(ip) {
|
|
|
|
// Handle CIDR notation by extracting just the IP part
|
|
|
|
const ipPart = ip.includes('/') ? ip.split('/')[0] : ip;
|
|
|
|
|
|
|
|
const parts = ipPart.split('.');
|
|
|
|
if (parts.length !== 4) return false;
|
|
|
|
return parts.every(part => {
|
|
|
|
const num = parseInt(part);
|
|
|
|
return num >= 0 && num <= 255 && part === num.toString();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function validateSubnetMask(mask) {
|
|
|
|
if (!validateIPv4(mask)) return false;
|
|
|
|
|
|
|
|
// Check that it's a valid subnet mask (consecutive 1s followed by 0s)
|
|
|
|
const maskLong = ipToLong(mask);
|
|
|
|
const binary = maskLong.toString(2).padStart(32, '0');
|
|
|
|
|
|
|
|
// Find the first 0
|
|
|
|
const firstZero = binary.indexOf('0');
|
|
|
|
|
|
|
|
// Special case: 255.255.255.255 (/32) is valid
|
|
|
|
if (firstZero === -1) return true; // All 1s (255.255.255.255) is valid for /32
|
|
|
|
|
|
|
|
// Check that all bits after first 0 are also 0
|
|
|
|
return binary.substring(firstZero).indexOf('1') === -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
function validateIPv6(ip) {
|
|
|
|
// Proper IPv6 validation
|
|
|
|
if (!ip || typeof ip !== 'string') return false;
|
|
|
|
|
|
|
|
// Check for basic IPv6 format (contains colons)
|
|
|
|
if (!ip.includes(':')) return false;
|
|
|
|
|
|
|
|
// Split by double colon and validate each part
|
|
|
|
const parts = ip.split('::');
|
|
|
|
if (parts.length > 2) return false; // Only one double colon allowed
|
|
|
|
|
|
|
|
// Validate each part
|
|
|
|
for (let part of parts) {
|
|
|
|
if (part === '') continue; // Empty part is allowed for :: notation
|
|
|
|
|
|
|
|
const segments = part.split(':');
|
|
|
|
for (let segment of segments) {
|
|
|
|
if (segment === '') continue; // Empty segment is allowed
|
|
|
|
|
|
|
|
// Each segment should be 1-4 hex characters
|
|
|
|
if (!/^[0-9a-fA-F]{1,4}$/.test(segment)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
function expandIPv6(ip) {
|
|
|
|
// Expand compressed IPv6 address
|
|
|
|
const parts = ip.split('::');
|
|
|
|
if (parts.length === 1) return ip;
|
|
|
|
|
|
|
|
const left = parts[0].split(':');
|
|
|
|
const right = parts[1] ? parts[1].split(':') : [];
|
|
|
|
const missing = 8 - left.length - right.length;
|
|
|
|
|
|
|
|
const expanded = [...left];
|
|
|
|
for (let i = 0; i < missing; i++) {
|
|
|
|
expanded.push('0000');
|
|
|
|
}
|
|
|
|
expanded.push(...right);
|
|
|
|
|
|
|
|
return expanded.join(':');
|
|
|
|
}
|
|
|
|
|
|
|
|
function compressIPv6(ip) {
|
|
|
|
// Compress IPv6 address to shortest possible form
|
|
|
|
const parts = ip.split(':');
|
|
|
|
|
|
|
|
// Find the longest sequence of zeros
|
|
|
|
let longestZeroStart = -1;
|
|
|
|
let longestZeroLength = 0;
|
|
|
|
let currentZeroStart = -1;
|
|
|
|
let currentZeroLength = 0;
|
|
|
|
|
|
|
|
for (let i = 0; i < parts.length; i++) {
|
|
|
|
if (parts[i] === '0000' || parts[i] === '0') {
|
|
|
|
if (currentZeroStart === -1) {
|
|
|
|
currentZeroStart = i;
|
|
|
|
currentZeroLength = 1;
|
|
|
|
} else {
|
|
|
|
currentZeroLength++;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (currentZeroLength > longestZeroLength) {
|
|
|
|
longestZeroStart = currentZeroStart;
|
|
|
|
longestZeroLength = currentZeroLength;
|
|
|
|
}
|
|
|
|
currentZeroStart = -1;
|
|
|
|
currentZeroLength = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check if the last sequence is the longest
|
|
|
|
if (currentZeroLength > longestZeroLength) {
|
|
|
|
longestZeroStart = currentZeroStart;
|
|
|
|
longestZeroLength = currentZeroLength;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Only compress if we have at least 2 consecutive zeros
|
|
|
|
if (longestZeroLength >= 2) {
|
|
|
|
const left = parts.slice(0, longestZeroStart);
|
|
|
|
const right = parts.slice(longestZeroStart + longestZeroLength);
|
|
|
|
|
|
|
|
// Remove leading zeros from each part
|
|
|
|
const leftCompressed = left.map(part => {
|
|
|
|
const num = parseInt(part, 16);
|
|
|
|
return num.toString(16);
|
|
|
|
});
|
|
|
|
const rightCompressed = right.map(part => {
|
|
|
|
const num = parseInt(part, 16);
|
|
|
|
return num.toString(16);
|
|
|
|
});
|
|
|
|
|
|
|
|
// Handle edge cases for proper :: placement
|
|
|
|
if (leftCompressed.length === 0 && rightCompressed.length === 0) {
|
|
|
|
return '::';
|
|
|
|
} else if (leftCompressed.length === 0) {
|
|
|
|
return '::' + rightCompressed.join(':');
|
|
|
|
} else if (rightCompressed.length === 0) {
|
|
|
|
return leftCompressed.join(':') + '::';
|
|
|
|
} else {
|
|
|
|
return [...leftCompressed, '', ...rightCompressed].join(':');
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// No compression needed, just remove leading zeros
|
|
|
|
return parts.map(part => {
|
|
|
|
const num = parseInt(part, 16);
|
|
|
|
return num.toString(16);
|
|
|
|
}).join(':');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function ipv6ToLong(ip) {
|
|
|
|
const expanded = expandIPv6(ip);
|
|
|
|
const parts = expanded.split(':');
|
|
|
|
let result = 0n;
|
|
|
|
|
|
|
|
// IPv6 addresses are big-endian (most significant byte first)
|
|
|
|
// Each part is a 16-bit hex value
|
|
|
|
for (let i = 0; i < 8; i++) {
|
|
|
|
const part = parseInt(parts[i], 16);
|
|
|
|
result = (result << 16n) + BigInt(part);
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
function longToIPv6(long) {
|
|
|
|
const parts = [];
|
|
|
|
// Extract each 16-bit segment in big-endian order
|
|
|
|
// Start from the most significant bits (left side)
|
|
|
|
for (let i = 7; i >= 0; i--) {
|
|
|
|
const part = Number((long >> BigInt(i * 16)) & 0xFFFFn);
|
|
|
|
parts.push(part.toString(16).padStart(4, '0'));
|
|
|
|
}
|
|
|
|
return parts.join(':');
|
|
|
|
}
|
|
|
|
|
|
|
|
function generateAvailableNetworks(baseIP, cidr) {
|
|
|
|
const networks = [];
|
|
|
|
const networkSize = Math.pow(2, 32 - cidr);
|
|
|
|
const baseLong = ipToLong(baseIP);
|
|
|
|
|
2025-09-01 22:04:35 -07:00
|
|
|
// Calculate the base network for the IP based on the CIDR
|
|
|
|
// For larger subnets (CIDR < 16), we need to find the appropriate base network
|
|
|
|
let baseNetworkLong;
|
|
|
|
let maxNetworks;
|
2025-09-01 17:40:27 -07:00
|
|
|
|
2025-09-01 22:04:35 -07:00
|
|
|
if (cidr <= 8) {
|
|
|
|
// For /8 and larger, use /8 as the base (Class A)
|
|
|
|
baseNetworkLong = baseLong & ((0xFFFFFFFF << (32 - 8)) >>> 0);
|
|
|
|
maxNetworks = Math.min(64, Math.floor(16777216 / networkSize)); // 2^24 / networkSize
|
|
|
|
} else if (cidr <= 16) {
|
|
|
|
// For /9 to /16, use /16 as the base (Class B)
|
|
|
|
baseNetworkLong = baseLong & ((0xFFFFFFFF << (32 - 16)) >>> 0);
|
|
|
|
maxNetworks = Math.min(64, Math.floor(65536 / networkSize)); // 2^16 / networkSize
|
|
|
|
} else {
|
|
|
|
// For /17 and smaller, use the actual network as base
|
|
|
|
baseNetworkLong = baseLong & ((0xFFFFFFFF << (32 - cidr)) >>> 0);
|
|
|
|
maxNetworks = Math.min(64, Math.floor(65536 / networkSize));
|
|
|
|
}
|
|
|
|
|
|
|
|
// Show up to 64 networks, but limit based on what makes sense
|
|
|
|
const count = Math.min(maxNetworks, 64);
|
2025-09-01 17:40:27 -07:00
|
|
|
|
|
|
|
for (let i = 0; i < count; i++) {
|
2025-09-01 22:04:35 -07:00
|
|
|
const networkAddr = baseNetworkLong + (i * networkSize);
|
2025-09-01 17:40:27 -07:00
|
|
|
const broadcastAddr = networkAddr + networkSize - 1;
|
2025-09-01 22:04:35 -07:00
|
|
|
|
|
|
|
// Handle edge cases for host calculations
|
|
|
|
let firstHost, lastHost;
|
|
|
|
|
|
|
|
if (cidr === 32) {
|
|
|
|
// /32 - single host, no usable hosts
|
|
|
|
firstHost = networkAddr;
|
|
|
|
lastHost = networkAddr;
|
|
|
|
} else if (cidr === 31) {
|
|
|
|
// /31 - point-to-point, no usable hosts
|
|
|
|
firstHost = networkAddr;
|
|
|
|
lastHost = broadcastAddr;
|
|
|
|
} else if (cidr === 30) {
|
|
|
|
// /30 - 4 total hosts, 2 usable
|
|
|
|
firstHost = networkAddr + 1;
|
|
|
|
lastHost = broadcastAddr - 1;
|
|
|
|
} else {
|
|
|
|
// Normal case - calculate usable hosts
|
|
|
|
firstHost = networkAddr + 1;
|
|
|
|
lastHost = broadcastAddr - 1;
|
|
|
|
}
|
2025-09-01 17:40:27 -07:00
|
|
|
|
|
|
|
networks.push({
|
|
|
|
network: longToIp(networkAddr),
|
|
|
|
firstHost: longToIp(firstHost),
|
|
|
|
lastHost: longToIp(lastHost),
|
|
|
|
broadcast: longToIp(broadcastAddr)
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
return networks;
|
|
|
|
}
|
|
|
|
|
|
|
|
function calculateIPv4() {
|
|
|
|
const ipAddress = ui.querySelector('[name=ipAddress]').value;
|
|
|
|
const subnetMask = ui.querySelector('[name=subnetMask]').value;
|
|
|
|
const cidr = +ui.querySelector('[name=cidr]').value;
|
|
|
|
|
|
|
|
if (!validateIPv4(ipAddress)) {
|
|
|
|
out.innerHTML = `<div style="color: var(--error);">
|
|
|
|
<strong>Error:</strong> Invalid IPv4 address format
|
|
|
|
</div>`;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!validateSubnetMask(subnetMask)) {
|
|
|
|
out.innerHTML = `<div style="color: var(--error);">
|
|
|
|
<strong>Error:</strong> Invalid subnet mask format
|
|
|
|
</div>`;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const ipLong = ipToLong(ipAddress);
|
|
|
|
const maskLong = ipToLong(subnetMask);
|
|
|
|
const networkLong = (ipLong & maskLong) >>> 0; // Ensure unsigned 32-bit
|
|
|
|
const broadcastLong = (networkLong | (~maskLong >>> 0)) >>> 0; // Ensure unsigned 32-bit
|
|
|
|
|
|
|
|
// Calculate CIDR from subnet mask
|
|
|
|
const calculatedCidr = maskToCidr(subnetMask);
|
|
|
|
|
|
|
|
// Handle edge cases for host calculations
|
|
|
|
let totalHosts, firstHostLong, lastHostLong;
|
|
|
|
|
|
|
|
if (calculatedCidr === 32) {
|
|
|
|
// /32 - single host, no usable hosts
|
|
|
|
totalHosts = 1;
|
|
|
|
firstHostLong = networkLong; // Same as network
|
|
|
|
lastHostLong = networkLong; // Same as network
|
|
|
|
} else if (calculatedCidr === 31) {
|
|
|
|
// /31 - point-to-point, no usable hosts
|
|
|
|
totalHosts = 2;
|
|
|
|
firstHostLong = networkLong; // First address
|
|
|
|
lastHostLong = broadcastLong; // Second address
|
|
|
|
} else if (calculatedCidr === 30) {
|
|
|
|
// /30 - 4 total hosts, 2 usable
|
|
|
|
totalHosts = 4;
|
|
|
|
firstHostLong = networkLong + 1;
|
|
|
|
lastHostLong = broadcastLong - 1;
|
|
|
|
} else {
|
|
|
|
// Normal case - calculate usable hosts
|
|
|
|
totalHosts = Math.pow(2, 32 - calculatedCidr) - 2;
|
|
|
|
firstHostLong = networkLong + 1;
|
|
|
|
lastHostLong = broadcastLong - 1;
|
|
|
|
}
|
|
|
|
|
2025-09-01 22:04:35 -07:00
|
|
|
// Calculate total possible networks based on CIDR
|
2025-09-01 17:40:27 -07:00
|
|
|
const networkSize = Math.pow(2, 32 - calculatedCidr);
|
2025-09-01 22:04:35 -07:00
|
|
|
let totalPossibleNetworks;
|
|
|
|
|
|
|
|
if (calculatedCidr <= 8) {
|
|
|
|
// For /8 and larger, calculate based on Class A space
|
|
|
|
totalPossibleNetworks = Math.floor(16777216 / networkSize); // 2^24 / networkSize
|
|
|
|
} else if (calculatedCidr <= 16) {
|
|
|
|
// For /9 to /16, calculate based on Class B space
|
|
|
|
totalPossibleNetworks = Math.floor(65536 / networkSize); // 2^16 / networkSize
|
|
|
|
} else {
|
|
|
|
// For /17 and smaller, use the standard calculation
|
|
|
|
totalPossibleNetworks = Math.floor(65536 / networkSize);
|
|
|
|
}
|
2025-09-01 17:40:27 -07:00
|
|
|
|
|
|
|
// Generate available networks table
|
|
|
|
const availableNetworks = generateAvailableNetworks(ipAddress, calculatedCidr);
|
|
|
|
|
2025-09-01 22:04:35 -07:00
|
|
|
// Get RFC network information
|
|
|
|
const rfcInfo = getRFCNetworkInfo(ipAddress, calculatedCidr);
|
|
|
|
|
2025-09-01 17:40:27 -07:00
|
|
|
out.innerHTML = `
|
|
|
|
<div style="font-size: 24px; font-weight: 700; color: var(--accent); margin-bottom: 15px;">
|
|
|
|
IPv4 Subnet Information
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 20px; margin-bottom: 20px;">
|
|
|
|
<div>
|
|
|
|
<h4 style="color: var(--text); margin-bottom: 10px;">Input Information</h4>
|
|
|
|
<div style="color: var(--text); margin-bottom: 5px;">
|
|
|
|
<strong>IP Address:</strong> ${ipAddress}
|
|
|
|
</div>
|
|
|
|
<div style="color: var(--text); margin-bottom: 5px;">
|
|
|
|
<strong>Network Class:</strong> Class ${getNetworkClass(ipAddress)}
|
|
|
|
</div>
|
|
|
|
<div style="color: var(--text); margin-bottom: 5px;">
|
|
|
|
<strong>Subnet Mask:</strong> ${subnetMask}
|
|
|
|
</div>
|
|
|
|
<div style="color: var(--text); margin-bottom: 5px;">
|
|
|
|
<strong>CIDR Notation:</strong> /${calculatedCidr.toFixed(0)}
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div>
|
|
|
|
<h4 style="color: var(--text); margin-bottom: 10px;">Network Information</h4>
|
|
|
|
<div style="color: var(--text); margin-bottom: 5px;">
|
|
|
|
<strong>Network Address:</strong> ${longToIp(networkLong)}
|
|
|
|
</div>
|
|
|
|
<div style="color: var(--text); margin-bottom: 5px;">
|
|
|
|
<strong>Broadcast Address:</strong> ${longToIp(broadcastLong)}
|
|
|
|
</div>
|
|
|
|
<div style="color: var(--text); margin-bottom: 5px;">
|
|
|
|
<strong>Total Hosts:</strong> ${totalHosts.toLocaleString()}
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div style="background: var(--border); padding: 15px; border-radius: 8px; margin-top: 15px;">
|
|
|
|
<h4 style="color: var(--accent); margin-bottom: 10px;">Host Information</h4>
|
|
|
|
<div style="color: var(--text); margin-bottom: 5px;">
|
|
|
|
<strong>First Usable Host:</strong> ${longToIp(firstHostLong)}
|
|
|
|
</div>
|
|
|
|
<div style="color: var(--text); margin-bottom: 5px;">
|
|
|
|
<strong>Last Usable Host:</strong> ${longToIp(lastHostLong)}
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
2025-09-01 22:04:35 -07:00
|
|
|
<div style="background: var(--border); padding: 15px; border-radius: 8px; margin-top: 15px;">
|
|
|
|
<h4 style="color: var(--accent); margin-bottom: 10px;">Network Type Information</h4>
|
|
|
|
<div style="color: var(--text); margin-bottom: 5px;">
|
|
|
|
<strong>Network Type:</strong> ${rfcInfo.type}
|
|
|
|
</div>
|
|
|
|
<div style="color: var(--text); margin-bottom: 5px;">
|
|
|
|
<strong>Description:</strong> ${rfcInfo.description}
|
|
|
|
</div>
|
|
|
|
${rfcInfo.cidr ? `<div style="color: var(--text); margin-bottom: 5px;">
|
|
|
|
<strong>RFC Range:</strong> ${rfcInfo.cidr}
|
|
|
|
</div>` : ''}
|
|
|
|
${rfcInfo.rfc ? `<div style="color: var(--text); margin-bottom: 5px;">
|
|
|
|
<strong>RFC Reference:</strong> ${rfcInfo.rfc}
|
|
|
|
</div>` : ''}
|
|
|
|
</div>
|
|
|
|
|
2025-09-01 17:40:27 -07:00
|
|
|
<div style="background: var(--border); padding: 15px; border-radius: 8px; margin-top: 15px;">
|
|
|
|
<h4 style="color: var(--accent); margin-bottom: 10px;">Binary Representation</h4>
|
|
|
|
<div style="font-family: monospace; font-size: 14px; color: var(--text); margin-bottom: 5px;">
|
|
|
|
<strong>IP Address:</strong> ${ipLong.toString(2).padStart(32, '0').match(/.{1,8}/g).join('.')}
|
|
|
|
<span style="color: var(--muted); margin-left: 10px;">(0x${ipLong.toString(16).padStart(8, '0').toUpperCase()})</span>
|
|
|
|
</div>
|
|
|
|
<div style="font-family: monospace; font-size: 14px; color: var(--text); margin-bottom: 5px;">
|
|
|
|
<strong>Subnet Mask:</strong> ${maskLong.toString(2).padStart(32, '0').match(/.{1,8}/g).join('.')}
|
|
|
|
<span style="color: var(--muted); margin-left: 10px;">(0x${maskLong.toString(16).padStart(8, '0').toUpperCase()})</span>
|
|
|
|
</div>
|
|
|
|
<div style="font-family: monospace; font-size: 14px; color: var(--text); margin-bottom: 5px;">
|
|
|
|
<strong>Network:</strong> ${networkLong.toString(2).padStart(32, '0').match(/.{1,8}/g).join('.')}
|
|
|
|
<span style="color: var(--muted); margin-left: 10px;">(0x${networkLong.toString(16).padStart(8, '0').toUpperCase()})</span>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div style="background: var(--border); padding: 15px; border-radius: 8px; margin-top: 15px;">
|
|
|
|
<h4 style="color: var(--accent); margin-bottom: 10px;">Available Networks</h4>
|
|
|
|
<div style="overflow-x: auto;">
|
|
|
|
<table style="width: 100%; border-collapse: collapse; font-family: monospace; font-size: 14px;">
|
|
|
|
<thead>
|
|
|
|
<tr style="background: var(--accent); color: white;">
|
|
|
|
<th style="padding: 8px; text-align: left; border: 1px solid var(--border);">Network</th>
|
|
|
|
<th style="padding: 8px; text-align: left; border: 1px solid var(--border);">First Host</th>
|
|
|
|
<th style="padding: 8px; text-align: left; border: 1px solid var(--border);">Last Host</th>
|
|
|
|
<th style="padding: 8px; text-align: left; border: 1px solid var(--border);">Broadcast</th>
|
|
|
|
</tr>
|
|
|
|
</thead>
|
|
|
|
<tbody>
|
|
|
|
${availableNetworks.map(net => `
|
|
|
|
<tr style="border-bottom: 1px solid var(--border);">
|
|
|
|
<td style="padding: 8px; border: 1px solid var(--border);">${net.network}</td>
|
|
|
|
<td style="padding: 8px; border: 1px solid var(--border);">${net.firstHost}</td>
|
|
|
|
<td style="padding: 8px; border: 1px solid var(--border);">${net.lastHost}</td>
|
|
|
|
<td style="padding: 8px; border: 1px solid var(--border);">${net.broadcast}</td>
|
|
|
|
</tr>
|
|
|
|
`).join('')}
|
|
|
|
</tbody>
|
|
|
|
</table>
|
|
|
|
</div>
|
|
|
|
<div style="margin-top: 15px; text-align: center; color: var(--muted);">
|
|
|
|
Showing ${availableNetworks.length} of ${totalPossibleNetworks} possible networks
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
`;
|
|
|
|
}
|
|
|
|
|
|
|
|
function calculateIPv6() {
|
|
|
|
const ipv6Address = ui.querySelector('[name=ipv6Address]').value;
|
|
|
|
const ipv6Cidr = +ui.querySelector('[name=ipv6Cidr]').value;
|
|
|
|
|
|
|
|
if (!validateIPv6(ipv6Address)) {
|
|
|
|
out.innerHTML = `<div style="color: var(--error);">
|
|
|
|
<strong>Error:</strong> Invalid IPv6 address format
|
|
|
|
</div>`;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ipv6Cidr < 0 || ipv6Cidr > 128) {
|
|
|
|
out.innerHTML = `<div style="color: var(--error);">
|
|
|
|
<strong>Error:</strong> CIDR must be between 0 and 128
|
|
|
|
</div>`;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Expand the IPv6 address
|
|
|
|
const expandedIPv6 = expandIPv6(ipv6Address);
|
|
|
|
|
|
|
|
// Calculate network and broadcast addresses
|
|
|
|
const ipv6Long = ipv6ToLong(expandedIPv6);
|
|
|
|
|
|
|
|
// For IPv6, we need to handle the network portion correctly
|
|
|
|
const networkBits = BigInt(ipv6Cidr);
|
|
|
|
const hostBits = BigInt(128 - ipv6Cidr);
|
|
|
|
|
|
|
|
// Create network mask: 1s for network bits, 0s for host bits
|
|
|
|
// For IPv6, we need to create a mask with networkBits 1s followed by hostBits 0s
|
|
|
|
let networkMask = 0n;
|
|
|
|
for (let i = 127n; i >= hostBits; i--) {
|
|
|
|
networkMask |= (1n << i);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Calculate network address: clear host bits (keep network bits)
|
|
|
|
const networkLong = ipv6Long & networkMask;
|
|
|
|
|
|
|
|
// Calculate broadcast address: set host bits to 1 (keep network bits, set host bits)
|
|
|
|
const broadcastLong = networkLong | ((1n << hostBits) - 1n);
|
|
|
|
|
|
|
|
// Calculate number of hosts (subtract 2 for network and broadcast)
|
|
|
|
const totalHosts = (BigInt(2) ** BigInt(128 - ipv6Cidr)) - BigInt(2);
|
|
|
|
|
|
|
|
// Calculate total possible subnets in a /64 (typical IPv6 subnet size)
|
|
|
|
const totalPossibleSubnets = BigInt(2) ** BigInt(64 - ipv6Cidr);
|
|
|
|
|
|
|
|
// Generate available networks table (show up to 8 networks)
|
|
|
|
const availableNetworks = [];
|
|
|
|
const networksToShow = Math.min(8, Number(totalPossibleSubnets));
|
|
|
|
|
|
|
|
for (let i = 0; i < networksToShow; i++) {
|
|
|
|
const networkAddr = networkLong + (BigInt(i) * (BigInt(2) ** BigInt(128 - ipv6Cidr)));
|
|
|
|
const broadcastAddr = networkAddr + (BigInt(2) ** BigInt(128 - ipv6Cidr)) - BigInt(1);
|
|
|
|
|
|
|
|
const networkStr = longToIPv6(networkAddr);
|
|
|
|
const broadcastStr = longToIPv6(broadcastAddr);
|
|
|
|
availableNetworks.push({
|
|
|
|
network: networkStr,
|
|
|
|
networkCompressed: compressIPv6(networkStr),
|
|
|
|
broadcast: broadcastStr,
|
|
|
|
broadcastCompressed: compressIPv6(broadcastStr)
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2025-09-01 22:04:35 -07:00
|
|
|
// Get IPv6 network information
|
|
|
|
const ipv6NetworkInfo = getIPv6NetworkInfo(ipv6Address, ipv6Cidr);
|
|
|
|
|
2025-09-01 17:40:27 -07:00
|
|
|
// Format large numbers for display
|
|
|
|
function formatBigInt(num) {
|
|
|
|
if (num < BigInt(1e6)) {
|
|
|
|
return num.toString();
|
|
|
|
} else if (num < BigInt(1e9)) {
|
|
|
|
return (Number(num) / 1e6).toFixed(1) + 'M';
|
|
|
|
} else if (num < BigInt(1e12)) {
|
|
|
|
return (Number(num) / 1e9).toFixed(1) + 'B';
|
|
|
|
} else {
|
|
|
|
return Number(num).toExponential(2);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
out.innerHTML = `
|
|
|
|
<div style="font-size: 24px; font-weight: 700; color: var(--accent); margin-bottom: 15px;">
|
|
|
|
IPv6 Subnet Information
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 20px; margin-bottom: 20px;">
|
|
|
|
<div>
|
|
|
|
<h4 style="color: var(--text); margin-bottom: 10px;">Input Information</h4>
|
|
|
|
<div style="color: var(--text); margin-bottom: 5px;">
|
|
|
|
<strong>IPv6 Address:</strong> ${ipv6Address}
|
|
|
|
</div>
|
|
|
|
<div style="color: var(--text); margin-bottom: 5px;">
|
|
|
|
<strong>Expanded Address:</strong> ${expandedIPv6}
|
|
|
|
</div>
|
|
|
|
<div style="color: var(--text); margin-bottom: 5px;">
|
|
|
|
<strong>Compressed Address:</strong> ${compressIPv6(expandedIPv6)}
|
|
|
|
</div>
|
|
|
|
<div style="color: var(--text); margin-bottom: 5px;">
|
|
|
|
<strong>CIDR Prefix:</strong> /${ipv6Cidr}
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div>
|
|
|
|
<h4 style="color: var(--text); margin-bottom: 10px;">Network Information</h4>
|
|
|
|
<div style="color: var(--text); margin-bottom: 5px;">
|
|
|
|
<strong>Network Address:</strong> ${longToIPv6(networkLong)}
|
|
|
|
</div>
|
|
|
|
<div style="color: var(--text); margin-bottom: 5px;">
|
|
|
|
<strong>Network Address (Compressed):</strong> ${compressIPv6(longToIPv6(networkLong))}
|
|
|
|
</div>
|
|
|
|
<div style="color: var(--text); margin-bottom: 5px;">
|
|
|
|
<strong>Broadcast Address:</strong> ${longToIPv6(broadcastLong)}
|
|
|
|
</div>
|
|
|
|
<div style="color: var(--text); margin-bottom: 5px;">
|
|
|
|
<strong>Broadcast Address (Compressed):</strong> ${compressIPv6(longToIPv6(broadcastLong))}
|
|
|
|
</div>
|
|
|
|
<div style="color: var(--text); margin-bottom: 5px;">
|
|
|
|
<strong>Address Space:</strong> ${128 - ipv6Cidr} bits
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div style="background: var(--border); padding: 15px; border-radius: 8px; margin-top: 15px;">
|
|
|
|
<h4 style="color: var(--accent); margin-bottom: 10px;">Host Information</h4>
|
|
|
|
<div style="color: var(--text); margin-bottom: 5px;">
|
|
|
|
<strong>Total Hosts:</strong> ${formatBigInt(totalHosts)}
|
|
|
|
</div>
|
|
|
|
<div style="color: var(--text); margin-bottom: 5px;">
|
|
|
|
<strong>Subnets in /64:</strong> ${formatBigInt(totalPossibleSubnets)}
|
|
|
|
</div>
|
|
|
|
<div style="color: var(--text); margin-bottom: 5px;">
|
|
|
|
<strong>Host Bits:</strong> ${128 - ipv6Cidr}
|
|
|
|
</div>
|
|
|
|
<div style="color: var(--text); margin-bottom: 5px;">
|
|
|
|
<strong>Network Bits:</strong> ${ipv6Cidr}
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div style="background: var(--border); padding: 15px; border-radius: 8px; margin-top: 15px;">
|
2025-09-01 22:04:35 -07:00
|
|
|
<h4 style="color: var(--accent); margin-bottom: 10px;">Network Type Information</h4>
|
|
|
|
<div style="color: var(--text); margin-bottom: 5px;">
|
|
|
|
<strong>Network Type:</strong> ${ipv6NetworkInfo.type}
|
|
|
|
</div>
|
|
|
|
<div style="color: var(--text); margin-bottom: 5px;">
|
|
|
|
<strong>Description:</strong> ${ipv6NetworkInfo.description}
|
|
|
|
</div>
|
|
|
|
${ipv6NetworkInfo.cidr ? `<div style="color: var(--text); margin-bottom: 5px;">
|
|
|
|
<strong>RFC Range:</strong> ${ipv6NetworkInfo.cidr}
|
|
|
|
</div>` : ''}
|
|
|
|
${ipv6NetworkInfo.rfc ? `<div style="color: var(--text); margin-bottom: 5px;">
|
|
|
|
<strong>RFC Reference:</strong> ${ipv6NetworkInfo.rfc}
|
|
|
|
</div>` : ''}
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div style="background: var(--border); padding: 15px; border-radius: 8px; margin-top: 15px;">
|
2025-09-01 17:40:27 -07:00
|
|
|
<h4 style="color: var(--accent); margin-bottom: 10px;">Available Networks</h4>
|
|
|
|
<div style="overflow-x: auto;">
|
|
|
|
<table style="width: 100%; border-collapse: collapse; font-family: monospace; font-size: 14px;">
|
|
|
|
<thead>
|
|
|
|
<tr style="background: var(--accent); color: white;">
|
|
|
|
<th style="padding: 8px; text-align: left; border: 1px solid var(--border);">Network (Expanded)</th>
|
|
|
|
<th style="padding: 8px; text-align: left; border: 1px solid var(--border);">Network (Compressed)</th>
|
|
|
|
<th style="padding: 8px; text-align: left; border: 1px solid var(--border);">Broadcast (Expanded)</th>
|
|
|
|
<th style="padding: 8px; text-align: left; border: 1px solid var(--border);">Broadcast (Compressed)</th>
|
|
|
|
</tr>
|
|
|
|
</thead>
|
|
|
|
<tbody>
|
|
|
|
${availableNetworks.map(net => `
|
|
|
|
<tr style="border-bottom: 1px solid var(--border);">
|
|
|
|
<td style="padding: 8px; border: 1px solid var(--border);">${net.network}</td>
|
|
|
|
<td style="padding: 8px; border: 1px solid var(--border);">${net.networkCompressed}</td>
|
|
|
|
<td style="padding: 8px; border: 1px solid var(--border);">${net.broadcast}</td>
|
|
|
|
<td style="padding: 8px; border: 1px solid var(--border);">${net.broadcastCompressed}</td>
|
|
|
|
</tr>
|
|
|
|
`).join('')}
|
|
|
|
</tbody>
|
|
|
|
</table>
|
|
|
|
</div>
|
|
|
|
<div style="margin-top: 15px; text-align: center; color: var(--muted);">
|
|
|
|
Showing ${availableNetworks.length} of ${formatBigInt(totalPossibleSubnets)} possible networks in /64
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
`;
|
|
|
|
}
|
|
|
|
|
|
|
|
function calculate() {
|
|
|
|
const ipVersionSelect = ui.querySelector('[name=ipVersion]');
|
|
|
|
if (!ipVersionSelect) {
|
|
|
|
console.log('IP version select not found, skipping calculation');
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const ipVersion = ipVersionSelect.value;
|
|
|
|
console.log('Calculate called with IP version:', ipVersion, 'Select element:', ipVersionSelect);
|
|
|
|
|
|
|
|
if (ipVersion === 'ipv4') {
|
|
|
|
console.log('Calculating IPv4');
|
|
|
|
calculateIPv4();
|
|
|
|
} else if (ipVersion === 'ipv6') {
|
|
|
|
console.log('Calculating IPv6');
|
|
|
|
calculateIPv6();
|
|
|
|
} else {
|
|
|
|
console.log('Unknown IP version:', ipVersion, 'Defaulting to IPv4');
|
|
|
|
calculateIPv4();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Event listeners
|
|
|
|
ui.querySelector('[name=ipVersion]').addEventListener('change', (e) => {
|
|
|
|
const ipv4Inputs = ui.querySelector('#ipv4-inputs');
|
|
|
|
const ipv6Inputs = ui.querySelector('#ipv6-inputs');
|
|
|
|
|
|
|
|
console.log('IP version changed to:', e.target.value);
|
|
|
|
console.log('IPv4 inputs element:', ipv4Inputs);
|
|
|
|
console.log('IPv6 inputs element:', ipv6Inputs);
|
|
|
|
|
|
|
|
if (e.target.value === 'ipv4') {
|
|
|
|
ipv4Inputs.style.display = 'block';
|
|
|
|
ipv6Inputs.style.display = 'none';
|
|
|
|
console.log('Switched to IPv4 mode');
|
|
|
|
console.log('IPv4 display style:', ipv4Inputs.style.display);
|
|
|
|
console.log('IPv6 display style:', ipv6Inputs.style.display);
|
|
|
|
} else {
|
|
|
|
ipv4Inputs.style.display = 'none';
|
|
|
|
ipv6Inputs.style.display = 'block';
|
|
|
|
console.log('Switched to IPv6 mode');
|
|
|
|
console.log('IPv4 display style:', ipv4Inputs.style.display);
|
|
|
|
console.log('IPv6 display style:', ipv6Inputs.style.display);
|
|
|
|
|
|
|
|
// Debug IPv6 input elements
|
|
|
|
const ipv6AddressInput = ipv6Inputs.querySelector('[name=ipv6Address]');
|
|
|
|
const ipv6CidrInput = ipv6Inputs.querySelector('[name=ipv6Cidr]');
|
|
|
|
console.log('IPv6 Address input found:', ipv6AddressInput);
|
|
|
|
console.log('IPv6 CIDR input found:', ipv6CidrInput);
|
|
|
|
if (ipv6AddressInput) {
|
|
|
|
console.log('IPv6 Address input properties:', {
|
|
|
|
disabled: ipv6AddressInput.disabled,
|
|
|
|
readonly: ipv6AddressInput.readOnly,
|
|
|
|
style: ipv6AddressInput.style.cssText,
|
|
|
|
offsetWidth: ipv6AddressInput.offsetWidth,
|
|
|
|
offsetHeight: ipv6AddressInput.offsetHeight
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Force recalculation immediately after switching modes
|
|
|
|
setTimeout(() => {
|
|
|
|
console.log('Recalculating after mode switch');
|
|
|
|
calculate();
|
|
|
|
}, 100);
|
|
|
|
});
|
|
|
|
|
|
|
|
// Simple initialization
|
|
|
|
setTimeout(() => {
|
|
|
|
const ipVersionSelect = ui.querySelector('[name=ipVersion]');
|
|
|
|
if (ipVersionSelect) {
|
|
|
|
// Ensure the default value is set
|
|
|
|
if (!ipVersionSelect.value) {
|
|
|
|
ipVersionSelect.value = 'ipv4';
|
|
|
|
}
|
|
|
|
console.log('IP Version select initialized with value:', ipVersionSelect.value);
|
|
|
|
}
|
|
|
|
}, 200);
|
|
|
|
|
|
|
|
// IPv4 event listeners
|
|
|
|
ui.querySelector('[name=ipAddress]').addEventListener('input', (e) => {
|
|
|
|
const ipInput = e.target.value;
|
|
|
|
|
|
|
|
// Check if IP address contains CIDR notation (e.g., 10.0.0.1/8)
|
|
|
|
if (ipInput.includes('/')) {
|
|
|
|
const [ipPart, cidrPart] = ipInput.split('/');
|
|
|
|
const cidrValue = parseInt(cidrPart);
|
|
|
|
|
|
|
|
// Validate CIDR value and update CIDR field
|
|
|
|
if (cidrValue >= 0 && cidrValue <= 32) {
|
|
|
|
const cidrInput = ui.querySelector('[name=cidr]');
|
|
|
|
cidrInput.value = cidrValue;
|
|
|
|
|
|
|
|
// Update subnet mask based on new CIDR
|
|
|
|
const mask = cidrToMask(cidrValue);
|
|
|
|
const subnetMaskInput = ui.querySelector('[name=subnetMask]');
|
|
|
|
subnetMaskInput.value = mask;
|
|
|
|
|
|
|
|
// Trigger calculation
|
|
|
|
setTimeout(calculate, 10);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Always call calculate for normal input
|
|
|
|
calculate();
|
|
|
|
});
|
|
|
|
|
|
|
|
// Subnet mask input - sync with CIDR and recalculate
|
|
|
|
ui.querySelector('[name=subnetMask]').addEventListener('input', (e) => {
|
|
|
|
const mask = e.target.value;
|
|
|
|
if (validateIPv4(mask)) {
|
|
|
|
const calculatedCidr = maskToCidr(mask);
|
|
|
|
ui.querySelector('[name=cidr]').value = calculatedCidr;
|
|
|
|
// Force immediate calculation update
|
|
|
|
setTimeout(calculate, 10);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
// CIDR input - sync with subnet mask and recalculate
|
|
|
|
ui.querySelector('[name=cidr]').addEventListener('input', (e) => {
|
|
|
|
const cidr = +e.target.value;
|
|
|
|
console.log('CIDR input changed to:', cidr);
|
|
|
|
|
|
|
|
// Validate CIDR range
|
|
|
|
if (cidr >= 0 && cidr <= 32) {
|
|
|
|
const mask = cidrToMask(cidr);
|
|
|
|
ui.querySelector('[name=subnetMask]').value = mask;
|
|
|
|
console.log('Updated subnet mask to:', mask);
|
|
|
|
|
|
|
|
// Force immediate calculation update
|
|
|
|
setTimeout(() => {
|
|
|
|
console.log('Recalculating after CIDR change');
|
|
|
|
calculate();
|
|
|
|
}, 10);
|
|
|
|
} else {
|
|
|
|
console.log('Invalid CIDR value:', cidr);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
// IPv6 event listeners - use the already created input elements
|
|
|
|
ipv6AddressInput.addEventListener('input', () => {
|
|
|
|
console.log('IPv6 address input changed:', ipv6AddressInput.value);
|
|
|
|
calculate();
|
|
|
|
});
|
|
|
|
|
|
|
|
ipv6CidrInput.addEventListener('input', () => {
|
|
|
|
console.log('IPv6 CIDR input changed:', ipv6CidrInput.value);
|
|
|
|
calculate();
|
|
|
|
});
|
|
|
|
|
|
|
|
// Initial calculation - wait for DOM to be ready
|
|
|
|
setTimeout(() => {
|
|
|
|
console.log('Running initial calculation');
|
|
|
|
|
|
|
|
// Ensure default IPv4 value is set (Select Lite might override it)
|
|
|
|
const ipVersionSelect = ui.querySelector('[name=ipVersion]');
|
|
|
|
if (ipVersionSelect) {
|
|
|
|
console.log('Before setting default - IP version select value:', ipVersionSelect.value);
|
|
|
|
ipVersionSelect.value = 'ipv4';
|
|
|
|
console.log('After setting default - IP version select value:', ipVersionSelect.value);
|
|
|
|
|
|
|
|
// Force the change event to ensure proper display
|
|
|
|
const event = new Event('change', { bubbles: true });
|
|
|
|
ipVersionSelect.dispatchEvent(event);
|
|
|
|
}
|
|
|
|
|
|
|
|
console.log('About to call calculate()');
|
|
|
|
calculate();
|
|
|
|
console.log('calculate() called');
|
|
|
|
}, 300);
|
|
|
|
|
|
|
|
root.append(ui);
|
|
|
|
}
|
|
|
|
}
|