UNPKG

dnsweeper

Version:

Advanced CLI tool for DNS record risk analysis and cleanup. Features CSV import for Cloudflare/Route53, automated risk assessment, and parallel DNS validation.

215 lines 7.26 kB
/** * IPv6アドレス正規化ユーティリティ */ /** * IPv6アドレスを正規化する * - ゼロ圧縮を適用 * - 小文字に統一 * - 不要な先頭ゼロを削除 */ export function normalizeIPv6(address) { // 基本的な検証 if (!isValidIPv6(address)) { throw new Error(`Invalid IPv6 address: ${address}`); } // IPv4射影アドレスの処理 const ipv4Match = address.match(/::ffff:(\d+\.\d+\.\d+\.\d+)/i); if (ipv4Match) { return `::ffff:${ipv4Match[1]}`; } // 省略形を展開 const expanded = expandIPv6(address); // 正規化(最長のゼロ連続を圧縮) const normalized = compressIPv6(expanded); return normalized.toLowerCase(); } /** * IPv6アドレスを展開形式に変換 */ export function expandIPv6(address) { let addr = address.toLowerCase(); // :: を処理 if (addr.includes('::')) { const parts = addr.split('::'); const left = parts[0] ? parts[0].split(':') : []; const right = parts[1] ? parts[1].split(':') : []; const missing = 8 - left.length - right.length; const middle = Array(missing).fill('0000'); const allParts = [...left, ...middle, ...right]; addr = allParts.join(':'); } // 各セグメントを4桁に補完 return addr .split(':') .map((part) => part.padStart(4, '0')) .join(':'); } /** * IPv6アドレスを圧縮形式に変換 */ export function compressIPv6(address) { const addr = address.toLowerCase(); const parts = addr.split(':'); // 各パートから先頭のゼロを削除 const trimmed = parts.map((part) => part.replace(/^0+/, '') || '0'); // 最長のゼロ連続を見つける let maxZeroStart = -1; let maxZeroLength = 0; let currentZeroStart = -1; let currentZeroLength = 0; for (let i = 0; i < trimmed.length; i++) { if (trimmed[i] === '0') { if (currentZeroStart === -1) { currentZeroStart = i; currentZeroLength = 1; } else { currentZeroLength++; } if (currentZeroLength > maxZeroLength) { maxZeroStart = currentZeroStart; maxZeroLength = currentZeroLength; } } else { currentZeroStart = -1; currentZeroLength = 0; } } // 2つ以上の連続するゼロがある場合のみ圧縮 if (maxZeroLength >= 2) { const before = trimmed.slice(0, maxZeroStart); const after = trimmed.slice(maxZeroStart + maxZeroLength); if (before.length === 0 && after.length === 0) { return '::'; } else if (before.length === 0) { return '::' + after.join(':'); } else if (after.length === 0) { return before.join(':') + '::'; } else { return before.join(':') + '::' + after.join(':'); } } return trimmed.join(':'); } /** * IPv6アドレスの妥当性を検証 */ export function isValidIPv6(address) { // 基本的なパターンチェック const ipv6Pattern = /^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$/; if (!ipv6Pattern.test(address)) { return false; } // :: が複数回出現しないかチェック const doubleColonCount = (address.match(/::/g) || []).length; if (doubleColonCount > 1) { return false; } // 各セグメントが4桁以下かチェック const segments = address.split(/::?/); for (const segment of segments) { if (segment.length > 4) { return false; } } return true; } /** * 2つのIPv6アドレスが同じかどうかを比較 */ export function compareIPv6(addr1, addr2) { try { const normalized1 = normalizeIPv6(addr1); const normalized2 = normalizeIPv6(addr2); return normalized1 === normalized2; } catch { return false; } } /** * IPv6アドレスがリンクローカルアドレスかどうかを判定 */ export function isLinkLocalIPv6(address) { const expanded = expandIPv6(address); return expanded.startsWith('fe80:'); } /** * IPv6アドレスがユニークローカルアドレスかどうかを判定 */ export function isUniqueLocalIPv6(address) { const expanded = expandIPv6(address); return expanded.startsWith('fc') || expanded.startsWith('fd'); } /** * IPv6アドレスがマルチキャストアドレスかどうかを判定 */ export function isMulticastIPv6(address) { const expanded = expandIPv6(address); return expanded.startsWith('ff'); } /** * IPv6アドレスからプレフィックスを抽出 */ export function extractIPv6Prefix(address, prefixLength) { if (prefixLength < 0 || prefixLength > 128) { throw new Error(`Invalid prefix length: ${prefixLength}`); } const expanded = expandIPv6(address); const parts = expanded.split(':'); const bitsPerPart = 16; const fullParts = Math.floor(prefixLength / bitsPerPart); const remainingBits = prefixLength % bitsPerPart; const prefixParts = []; // 完全なパートをコピー for (let i = 0; i < fullParts; i++) { const part = parts[i]; if (part !== undefined) { prefixParts.push(part); } } // 部分的なパートを処理 if (remainingBits > 0 && fullParts < 8) { const partStr = parts[fullParts]; if (!partStr) { return address; // パートが存在しない場合は元のアドレスを返す } const partValue = parseInt(partStr, 16); const mask = (0xffff << (16 - remainingBits)) & 0xffff; const maskedValue = partValue & mask; prefixParts.push(maskedValue.toString(16).padStart(4, '0')); } // 残りをゼロで埋める while (prefixParts.length < 8) { prefixParts.push('0000'); } const prefixAddress = prefixParts.join(':'); return compressIPv6(prefixAddress); } /** * IPv6アドレスのスコープを取得 */ export function getIPv6Scope(address) { const normalized = normalizeIPv6(address); if (normalized === '::1') { return 'loopback'; } if (normalized === '::') { return 'unspecified'; } if (isLinkLocalIPv6(address)) { return 'link-local'; } if (isUniqueLocalIPv6(address)) { return 'unique-local'; } if (isMulticastIPv6(address)) { return 'multicast'; } return 'global'; } //# sourceMappingURL=ipv6.js.map