Files
dns-things/assets/js/ip-tools.js
dwindown e5195ba1f1 Initial commit: DNS Things - Comprehensive DNS utility website
Features implemented:
- Modular JavaScript architecture (theme.js, dns-tools.js, whois.js, punycode.js, ip-tools.js, main.js)
- Responsive design with dark/light theme toggle
- DNS Lookup and Reverse DNS Lookup tools
- Whois Lookup functionality
- IDN Punycode Converter with full Unicode support
- Comprehensive IP Address Tools (validation, IPv4-to-IPv6 mapping, IPv6 compression/expansion)
- Dynamic tab descriptions that change based on active tool
- Mobile-responsive horizontal scrollable tabs
- Copy-to-clipboard functionality for all results
- Clean footer with dynamic year
- IPv4-mapped IPv6 address explanation with clear warnings

Technical improvements:
- Separated concerns with modular JS files
- Fixed browser compatibility issues with punycode library
- Implemented proper error handling and user feedback
- Added comprehensive input validation
- Optimized for mobile devices with touch-friendly UI
2025-08-01 23:13:52 +07:00

438 lines
21 KiB
JavaScript

// DNS Things - IP Tools Module
// IP Tools utility functions
function isValidIPv4(ip) {
const ipv4Regex = /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
return ipv4Regex.test(ip);
}
function isValidIPv6(ip) {
const ipv6Regex = /^(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$|^::1$|^::$|^(?:[0-9a-fA-F]{1,4}:)*::(?:[0-9a-fA-F]{1,4}:)*[0-9a-fA-F]{1,4}$/;
return ipv6Regex.test(ip) || /^(?:[0-9a-fA-F]{0,4}:){0,7}[0-9a-fA-F]{0,4}$/.test(ip);
}
function ipv4ToIPv6(ipv4) {
const parts = ipv4.split('.');
const hex1 = (parseInt(parts[0]) * 256 + parseInt(parts[1])).toString(16).padStart(4, '0');
const hex2 = (parseInt(parts[2]) * 256 + parseInt(parts[3])).toString(16).padStart(4, '0');
return `::ffff:${hex1}:${hex2}`;
}
function compressIPv6(ipv6) {
// Expand first if compressed
let expanded = expandIPv6(ipv6);
// Then compress by removing leading zeros and consecutive zero groups
let compressed = expanded.replace(/\b0+([0-9a-fA-F]+)/g, '$1');
// Replace longest sequence of consecutive zero groups with ::
let parts = compressed.split(':');
let maxZeroStart = -1, maxZeroLength = 0;
let currentZeroStart = -1, currentZeroLength = 0;
for (let i = 0; i < parts.length; i++) {
if (parts[i] === '0' || parts[i] === '') {
if (currentZeroStart === -1) currentZeroStart = i;
currentZeroLength++;
} else {
if (currentZeroLength > maxZeroLength) {
maxZeroStart = currentZeroStart;
maxZeroLength = currentZeroLength;
}
currentZeroStart = -1;
currentZeroLength = 0;
}
}
if (currentZeroLength > maxZeroLength) {
maxZeroStart = currentZeroStart;
maxZeroLength = currentZeroLength;
}
if (maxZeroLength > 1) {
let before = parts.slice(0, maxZeroStart).join(':');
let after = parts.slice(maxZeroStart + maxZeroLength).join(':');
compressed = before + '::' + after;
compressed = compressed.replace(/^:/, '').replace(/:$/, '');
if (compressed === '') compressed = '::';
}
return compressed;
}
function expandIPv6(ipv6) {
if (ipv6.includes('::')) {
let parts = ipv6.split('::');
let left = parts[0] ? parts[0].split(':') : [];
let right = parts[1] ? parts[1].split(':') : [];
let middle = new Array(8 - left.length - right.length).fill('0000');
let expanded = left.concat(middle).concat(right);
return expanded.map(part => part.padStart(4, '0')).join(':');
}
return ipv6.split(':').map(part => part.padStart(4, '0')).join(':');
}
function ipv4ToBinary(ipv4) {
return ipv4.split('.').map(octet =>
parseInt(octet).toString(2).padStart(8, '0')
).join('.');
}
function ipv4ToDecimal(ipv4) {
const parts = ipv4.split('.');
return (parseInt(parts[0]) << 24) + (parseInt(parts[1]) << 16) + (parseInt(parts[2]) << 8) + parseInt(parts[3]);
}
function calculateSubnet(cidr) {
const [ip, prefixLength] = cidr.split('/');
const prefix = parseInt(prefixLength);
if (!isValidIPv4(ip) || prefix < 0 || prefix > 32) {
throw new Error('Invalid CIDR notation');
}
const ipParts = ip.split('.').map(x => parseInt(x));
const ipInt = (ipParts[0] << 24) + (ipParts[1] << 16) + (ipParts[2] << 8) + ipParts[3];
const subnetMask = (0xFFFFFFFF << (32 - prefix)) >>> 0;
const networkInt = (ipInt & subnetMask) >>> 0;
const broadcastInt = (networkInt | (0xFFFFFFFF >>> prefix)) >>> 0;
const networkIP = [
(networkInt >>> 24) & 0xFF,
(networkInt >>> 16) & 0xFF,
(networkInt >>> 8) & 0xFF,
networkInt & 0xFF
].join('.');
const broadcastIP = [
(broadcastInt >>> 24) & 0xFF,
(broadcastInt >>> 16) & 0xFF,
(broadcastInt >>> 8) & 0xFF,
broadcastInt & 0xFF
].join('.');
const subnetMaskIP = [
(subnetMask >>> 24) & 0xFF,
(subnetMask >>> 16) & 0xFF,
(subnetMask >>> 8) & 0xFF,
subnetMask & 0xFF
].join('.');
const totalHosts = Math.pow(2, 32 - prefix);
const usableHosts = totalHosts > 2 ? totalHosts - 2 : totalHosts;
return {
network: networkIP,
broadcast: broadcastIP,
subnetMask: subnetMaskIP,
totalHosts,
usableHosts,
prefix
};
}
// Initialize IP Tools
function initIPTools() {
// IP Validation
const ipValidateInput = document.getElementById('ip-validate-input');
const ipValidateBtn = document.getElementById('ip-validate-btn');
const ipValidateResult = document.getElementById('ip-validate-result');
const validateIP = () => {
const input = ipValidateInput.value.trim();
if (!input) {
ipValidateResult.innerHTML = '';
return;
}
const isIPv4 = isValidIPv4(input);
const isIPv6 = isValidIPv6(input);
if (isIPv4) {
ipValidateResult.innerHTML = `
<div class="p-4 bg-green-50 dark:bg-green-900/30 rounded-lg">
<div class="flex justify-between items-start">
<div class="flex-1">
<h5 class="font-medium text-green-800 dark:text-green-200 mb-2">✅ Valid IPv4 Address</h5>
<div class="space-y-1 text-sm">
<div><strong>Type:</strong> IPv4</div>
<div><strong>Address:</strong> ${input}</div>
<div><strong>Binary:</strong> ${ipv4ToBinary(input)}</div>
<div><strong>Decimal:</strong> ${ipv4ToDecimal(input)}</div>
</div>
</div>
<button onclick="copyToClipboard('${input}', this)" class="ml-2 p-2 text-green-600 dark:text-green-400 hover:bg-green-100 dark:hover:bg-green-800 rounded" title="Copy to clipboard">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path>
</svg>
</button>
</div>
</div>
`;
} else if (isIPv6) {
const compressed = compressIPv6(input);
const expanded = expandIPv6(input);
ipValidateResult.innerHTML = `
<div class="p-4 bg-green-50 dark:bg-green-900/30 rounded-lg">
<div class="flex justify-between items-start">
<div class="flex-1">
<h5 class="font-medium text-green-800 dark:text-green-200 mb-2">✅ Valid IPv6 Address</h5>
<div class="space-y-1 text-sm">
<div><strong>Type:</strong> IPv6</div>
<div><strong>Compressed:</strong> ${compressed}</div>
<div><strong>Expanded:</strong> ${expanded}</div>
</div>
</div>
<button onclick="copyToClipboard('${compressed}', this)" class="ml-2 p-2 text-green-600 dark:text-green-400 hover:bg-green-100 dark:hover:bg-green-800 rounded" title="Copy to clipboard">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path>
</svg>
</button>
</div>
</div>
`;
} else {
ipValidateResult.innerHTML = `
<div class="p-4 bg-red-50 dark:bg-red-900/30 text-red-700 dark:text-red-300 rounded-lg">
<h5 class="font-medium mb-1">❌ Invalid IP Address</h5>
<p class="text-sm">Please enter a valid IPv4 or IPv6 address.</p>
</div>
`;
}
};
if (ipValidateBtn && ipValidateInput) {
ipValidateBtn.addEventListener('click', validateIP);
ipValidateInput.addEventListener('input', () => {
clearTimeout(ipValidateInput.timeout);
ipValidateInput.timeout = setTimeout(validateIP, 500);
});
}
// IPv4 to IPv6 Mapping
const ipv4ToIPv6Input = document.getElementById('ipv4-to-ipv6-input');
const ipv4ToIPv6Btn = document.getElementById('ipv4-to-ipv6-btn');
const ipv4ToIPv6Result = document.getElementById('ipv4-to-ipv6-result');
const convertIPv4ToIPv6 = () => {
const input = ipv4ToIPv6Input.value.trim();
if (!input) {
ipv4ToIPv6Result.innerHTML = '';
return;
}
if (isValidIPv4(input)) {
const ipv6 = ipv4ToIPv6(input);
// Escape the IPv6 address for safe HTML insertion
const escapedIPv6 = ipv6.replace(/'/g, '&#39;');
ipv4ToIPv6Result.innerHTML = `
<div class="p-4 bg-green-50 dark:bg-green-900/30 rounded-lg">
<div class="flex justify-between items-start">
<div class="flex-1">
<h5 class="font-medium text-green-800 dark:text-green-200 mb-2">IPv6 Mapped Address:</h5>
<div class="font-mono text-sm break-all p-2 bg-white dark:bg-gray-800 rounded border">${escapedIPv6}</div>
<p class="text-xs text-green-600 dark:text-green-400 mt-2">✅ IPv4-mapped IPv6 address for ${input}</p>
<div class="text-xs text-gray-500 dark:text-gray-400 mt-2 p-2 bg-red-50 dark:bg-red-900/20 rounded border-l-2 border-red-400">
<p class="font-medium text-red-700 dark:text-red-300 mb-1">⚠️ Important:</p>
<p>This is NOT your actual IPv6 address! This is a special technical format that represents your IPv4 address (${input}) in IPv6 notation.</p>
<p class="mt-1">Your real IPv6 address is shown in the "Your IP Address" section above and is completely different.</p>
<p class="mt-1"><strong>Use case:</strong> IPv4-mapped IPv6 addresses are used for network programming and IPv4/IPv6 compatibility.</p>
</div>
</div>
<button onclick="copyToClipboard('${escapedIPv6}', this)" class="ml-2 p-2 text-green-600 dark:text-green-400 hover:bg-green-100 dark:hover:bg-green-800 rounded" title="Copy to clipboard">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path>
</svg>
</button>
</div>
</div>
`;
} else {
ipv4ToIPv6Result.innerHTML = `
<div class="p-4 bg-red-50 dark:bg-red-900/30 text-red-700 dark:text-red-300 rounded-lg">
<h5 class="font-medium mb-1">Error:</h5>
<p class="text-sm">Please enter a valid IPv4 address.</p>
</div>
`;
}
};
if (ipv4ToIPv6Btn && ipv4ToIPv6Input) {
ipv4ToIPv6Btn.addEventListener('click', convertIPv4ToIPv6);
ipv4ToIPv6Input.addEventListener('input', () => {
clearTimeout(ipv4ToIPv6Input.timeout);
ipv4ToIPv6Input.timeout = setTimeout(convertIPv4ToIPv6, 500);
});
}
// IPv6 Compression/Expansion
const ipv6CompressInput = document.getElementById('ipv6-compress-input');
const ipv6CompressBtn = document.getElementById('ipv6-compress-btn');
const ipv6CompressResult = document.getElementById('ipv6-compress-result');
const processIPv6 = () => {
const input = ipv6CompressInput.value.trim();
if (!input) {
ipv6CompressResult.innerHTML = '';
return;
}
if (isValidIPv6(input)) {
const compressed = compressIPv6(input);
const expanded = expandIPv6(input);
ipv6CompressResult.innerHTML = `
<div class="p-4 bg-green-50 dark:bg-green-900/30 rounded-lg">
<div class="space-y-3">
<div>
<h5 class="font-medium text-green-800 dark:text-green-200 mb-2">Compressed Form:</h5>
<div class="flex justify-between items-center">
<div class="font-mono text-sm break-all p-2 bg-white dark:bg-gray-800 rounded border flex-1 mr-2">${compressed}</div>
<button onclick="copyToClipboard('${compressed}', this)" class="p-2 text-green-600 dark:text-green-400 hover:bg-green-100 dark:hover:bg-green-800 rounded" title="Copy compressed">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path>
</svg>
</button>
</div>
</div>
<div>
<h5 class="font-medium text-green-800 dark:text-green-200 mb-2">Expanded Form:</h5>
<div class="flex justify-between items-center">
<div class="font-mono text-sm break-all p-2 bg-white dark:bg-gray-800 rounded border flex-1 mr-2">${expanded}</div>
<button onclick="copyToClipboard('${expanded}', this)" class="p-2 text-green-600 dark:text-green-400 hover:bg-green-100 dark:hover:bg-green-800 rounded" title="Copy expanded">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path>
</svg>
</button>
</div>
</div>
</div>
</div>
`;
} else {
ipv6CompressResult.innerHTML = `
<div class="p-4 bg-red-50 dark:bg-red-900/30 text-red-700 dark:text-red-300 rounded-lg">
<h5 class="font-medium mb-1">Error:</h5>
<p class="text-sm">Please enter a valid IPv6 address.</p>
</div>
`;
}
};
if (ipv6CompressBtn && ipv6CompressInput) {
ipv6CompressBtn.addEventListener('click', processIPv6);
ipv6CompressInput.addEventListener('input', () => {
clearTimeout(ipv6CompressInput.timeout);
ipv6CompressInput.timeout = setTimeout(processIPv6, 500);
});
}
// IP to Binary/Decimal
const ipBinaryInput = document.getElementById('ip-binary-input');
const ipBinaryBtn = document.getElementById('ip-binary-btn');
const ipBinaryResult = document.getElementById('ip-binary-result');
const convertIPToBinary = () => {
const input = ipBinaryInput.value.trim();
if (!input) {
ipBinaryResult.innerHTML = '';
return;
}
if (isValidIPv4(input)) {
const binary = ipv4ToBinary(input);
const decimal = ipv4ToDecimal(input);
ipBinaryResult.innerHTML = `
<div class="p-4 bg-green-50 dark:bg-green-900/30 rounded-lg">
<div class="space-y-3">
<div>
<h5 class="font-medium text-green-800 dark:text-green-200 mb-2">Binary Representation:</h5>
<div class="flex justify-between items-center">
<div class="font-mono text-sm break-all p-2 bg-white dark:bg-gray-800 rounded border flex-1 mr-2">${binary}</div>
<button onclick="copyToClipboard('${binary}', this)" class="p-2 text-green-600 dark:text-green-400 hover:bg-green-100 dark:hover:bg-green-800 rounded" title="Copy binary">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path>
</svg>
</button>
</div>
</div>
<div>
<h5 class="font-medium text-green-800 dark:text-green-200 mb-2">Decimal Representation:</h5>
<div class="flex justify-between items-center">
<div class="font-mono text-sm break-all p-2 bg-white dark:bg-gray-800 rounded border flex-1 mr-2">${decimal}</div>
<button onclick="copyToClipboard('${decimal}', this)" class="p-2 text-green-600 dark:text-green-400 hover:bg-green-100 dark:hover:bg-green-800 rounded" title="Copy decimal">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path>
</svg>
</button>
</div>
</div>
</div>
</div>
`;
} else {
ipBinaryResult.innerHTML = `
<div class="p-4 bg-red-50 dark:bg-red-900/30 text-red-700 dark:text-red-300 rounded-lg">
<h5 class="font-medium mb-1">Error:</h5>
<p class="text-sm">Please enter a valid IPv4 address.</p>
</div>
`;
}
};
if (ipBinaryBtn && ipBinaryInput) {
ipBinaryBtn.addEventListener('click', convertIPToBinary);
ipBinaryInput.addEventListener('input', () => {
clearTimeout(ipBinaryInput.timeout);
ipBinaryInput.timeout = setTimeout(convertIPToBinary, 500);
});
}
// Subnet Calculator
const subnetInput = document.getElementById('subnet-input');
const subnetBtn = document.getElementById('subnet-btn');
const subnetResult = document.getElementById('subnet-result');
const calculateSubnetInfo = () => {
const input = subnetInput.value.trim();
if (!input) {
subnetResult.innerHTML = '';
return;
}
try {
const subnet = calculateSubnet(input);
subnetResult.innerHTML = `
<div class="p-4 bg-green-50 dark:bg-green-900/30 rounded-lg">
<h5 class="font-medium text-green-800 dark:text-green-200 mb-3">Subnet Information:</h5>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 text-sm">
<div class="space-y-2">
<div><strong>Network Address:</strong> ${subnet.network}</div>
<div><strong>Broadcast Address:</strong> ${subnet.broadcast}</div>
<div><strong>Subnet Mask:</strong> ${subnet.subnetMask}</div>
</div>
<div class="space-y-2">
<div><strong>Prefix Length:</strong> /${subnet.prefix}</div>
<div><strong>Total Hosts:</strong> ${subnet.totalHosts.toLocaleString()}</div>
<div><strong>Usable Hosts:</strong> ${subnet.usableHosts.toLocaleString()}</div>
</div>
</div>
</div>
`;
} catch (error) {
subnetResult.innerHTML = `
<div class="p-4 bg-red-50 dark:bg-red-900/30 text-red-700 dark:text-red-300 rounded-lg">
<h5 class="font-medium mb-1">Error:</h5>
<p class="text-sm">${error.message}</p>
</div>
`;
}
};
if (subnetBtn && subnetInput) {
subnetBtn.addEventListener('click', calculateSubnetInfo);
subnetInput.addEventListener('input', () => {
clearTimeout(subnetInput.timeout);
subnetInput.timeout = setTimeout(calculateSubnetInfo, 500);
});
}
}