diff --git a/frontend/src/NetworkConfigManager.jsx b/frontend/src/NetworkConfigManager.jsx index 1036046..8a34ca1 100644 --- a/frontend/src/NetworkConfigManager.jsx +++ b/frontend/src/NetworkConfigManager.jsx @@ -83,6 +83,7 @@ const getEmptyInterface = () => ({ serverId: '', serverId2: '', // Второй сервер (опциональный) ipsecPasswordId: '', // ID IPSec пароля (опционально, только для IPSec) + ipPoolId: '', // ID IP пула для выбора IP адресов (опционально) }); const getEmptyIpPool = () => ({ @@ -143,6 +144,7 @@ function NetworkConfigManager() { const [templateName1, setTemplateName1] = useState(''); // Опциональное имя для сервера 1 const [templateName2, setTemplateName2] = useState(''); // Опциональное имя для сервера 2 const [templateIpsecPasswordId, setTemplateIpsecPasswordId] = useState(''); // Выбранный IPSec пароль + const [templateIpPoolId, setTemplateIpPoolId] = useState(''); // Выбранный IP пул // === IPSec Passwords Management === const [ipsecPasswords, setIpsecPasswords] = useState([]); @@ -529,13 +531,13 @@ function NetworkConfigManager() { const name2 = templateName2.trim() || generateInterfaceName(server2, server1, templateType, templateNamePrefix); // Генерируем IP адреса - const localIp = generateFreePrivateIp(null, false, null); + const localIp = generateFreePrivateIp(null, false, null, templateIpPoolId || null); if (!localIp) { notify.error('Не удалось найти свободный Local IP адрес'); return; } - const remoteIp = generateFreePrivateIp(null, true, localIp); + const remoteIp = generateFreePrivateIp(null, true, localIp, templateIpPoolId || null); if (!remoteIp) { notify.error('Не удалось найти свободный Remote IP адрес'); return; @@ -551,7 +553,8 @@ function NetworkConfigManager() { remoteIp: remoteIp, serverId: templateServer1, serverId2: templateServer2, - ipsecPasswordId: (templateType === 'IPSec' || templateType === 'GRE') && templateIpsecPasswordId ? templateIpsecPasswordId : '' + ipsecPasswordId: (templateType === 'IPSec' || templateType === 'GRE') && templateIpsecPasswordId ? templateIpsecPasswordId : '', + ipPoolId: templateIpPoolId || '' }; const conflicts = checkInterfaceIpConflict(newInterface); @@ -577,6 +580,7 @@ function NetworkConfigManager() { setTemplateName1(''); setTemplateName2(''); setTemplateIpsecPasswordId(''); + setTemplateIpPoolId(''); }; // === CRUD для Interfaces === @@ -646,12 +650,21 @@ function NetworkConfigManager() { // === Генерация свободного IP адреса из приватных диапазонов === // Генерирует IP в сетях /30 (четные базовые адреса) - const generateFreePrivateIp = (excludeIp = null, forRemote = false, pairedLocalIp = null) => { + // Если указан ipPoolId, использует IP пул, иначе использует приватные диапазоны RFC 1918 + const generateFreePrivateIp = (excludeIp = null, forRemote = false, pairedLocalIp = null, ipPoolId = null) => { const usedIps = new Set(getAllUsedIpsSet); if (excludeIp) { usedIps.delete(excludeIp.trim()); } + // Если указан IP пул, используем его + if (ipPoolId) { + const pool = (config.ipPools || []).find(p => p.id === ipPoolId); + if (pool && pool.cidr) { + return generateIpFromPool(pool.cidr, usedIps, excludeIp, forRemote, pairedLocalIp); + } + } + // Если подбираем Remote IP и есть Local IP, генерируем парный IP в той же сети /30 if (forRemote && pairedLocalIp) { const localIpParts = pairedLocalIp.trim().split('.'); @@ -726,9 +739,283 @@ function NetworkConfigManager() { return null; }; + // === Генерация IP из пула === + const generateIpFromPool = (cidr, usedIps, excludeIp = null, forRemote = false, pairedLocalIp = null) => { + // Парсим CIDR (например, "10.10.0.0/24") + const [network, prefixLength] = cidr.split('/'); + if (!network || !prefixLength) { + return null; + } + + const prefix = parseInt(prefixLength); + if (isNaN(prefix) || prefix < 0 || prefix > 32) { + return null; + } + + const networkParts = network.split('.'); + if (networkParts.length !== 4) { + return null; + } + + const [a, b, c, d] = networkParts.map(Number); + if (networkParts.some(p => isNaN(Number(p)))) { + return null; + } + + // Если подбираем Remote IP и есть Local IP, генерируем парный IP в той же сети /30 + if (forRemote && pairedLocalIp) { + const localIpParts = pairedLocalIp.trim().split('.'); + if (localIpParts.length === 4) { + const baseOctet = parseInt(localIpParts[3]); + const networkBase = Math.floor(baseOctet / 4) * 4; + const localOffset = baseOctet - networkBase; + + let remoteOffset; + if (localOffset === 1) { + remoteOffset = 2; + } else if (localOffset === 2) { + remoteOffset = 1; + } else { + remoteOffset = localOffset === 0 ? 2 : (localOffset === 3 ? 1 : 2); + } + + const remoteOctet = networkBase + remoteOffset; + if (remoteOctet >= 0 && remoteOctet <= 255) { + const remoteIp = `${localIpParts[0]}.${localIpParts[1]}.${localIpParts[2]}.${remoteOctet}`; + // Проверяем, что IP входит в диапазон пула + if (isIpInCidr(remoteIp, cidr) && !usedIps.has(remoteIp)) { + return remoteIp; + } + } + } + } + + // Вычисляем количество хостов в сети + const hostBits = 32 - prefix; + const totalHosts = Math.pow(2, hostBits); + + // Для /30 сетей используем шаг 4 (базовые адреса кратные 4) + const step = prefix >= 30 ? 4 : 1; + + // Определяем диапазон для перебора в зависимости от префикса + let startOctet = 0; + let endOctet = 255; + + if (prefix >= 24) { + // /24 - /32: меняем только последний октет + startOctet = d; + endOctet = Math.min(d + totalHosts - 1, 255); + } else if (prefix >= 16) { + // /16 - /23: меняем третий и четвертый октеты + const thirdOctetRange = Math.pow(2, Math.max(0, 24 - prefix)); + for (let cVal = c; cVal < c + thirdOctetRange && cVal <= 255; cVal++) { + const dStart = (cVal === c) ? d : 0; + const dEnd = (cVal === c + thirdOctetRange - 1) ? Math.min(d + totalHosts - 1, 255) : 255; + for (let dVal = dStart; dVal <= dEnd; dVal += step) { + if (prefix >= 30 && dVal % 4 !== 0) continue; // Для /30 только кратные 4 + const ip = `${a}.${b}.${cVal}.${dVal + 1}`; // Local IP = базовый + 1 + if (isIpInCidr(ip, cidr) && !usedIps.has(ip)) { + return ip; + } + } + } + return null; + } else { + // /0 - /15: слишком большой диапазон, используем упрощенную логику + return null; + } + + // Перебираем IP в диапазоне + for (let dVal = startOctet; dVal <= endOctet; dVal += step) { + if (prefix >= 30 && dVal % 4 !== 0) continue; // Для /30 только кратные 4 + + // Local IP = базовый + 1, Remote IP = базовый + 2 + const localIp = `${a}.${b}.${c}.${dVal + 1}`; + const remoteIp = `${a}.${b}.${c}.${dVal + 2}`; + + // Проверяем, что IP входит в диапазон пула + if (!isIpInCidr(localIp, cidr)) continue; + + // Проверяем, свободны ли оба IP (для /30) + if (prefix >= 30) { + if (!usedIps.has(localIp) && !usedIps.has(remoteIp)) { + return forRemote ? remoteIp : localIp; + } + } else { + // Для других префиксов просто проверяем один IP + if (!usedIps.has(localIp)) { + return localIp; + } + } + } + + return null; + }; + + // === Проверка, входит ли IP в CIDR === + const isIpInCidr = (ip, cidr) => { + const [network, prefixLength] = cidr.split('/'); + if (!network || !prefixLength) return false; + + const prefix = parseInt(prefixLength); + if (isNaN(prefix)) return false; + + const networkParts = network.split('.').map(Number); + const ipParts = ip.split('.').map(Number); + + if (networkParts.length !== 4 || ipParts.length !== 4) return false; + + // Вычисляем маску сети + const mask = (0xFFFFFFFF << (32 - prefix)) >>> 0; + + // Преобразуем IP в число + const networkNum = (networkParts[0] << 24) | (networkParts[1] << 16) | (networkParts[2] << 8) | networkParts[3]; + const ipNum = (ipParts[0] << 24) | (ipParts[1] << 16) | (ipParts[2] << 8) | ipParts[3]; + + return (networkNum & mask) === (ipNum & mask); + }; + + // === Генерация IP из пула === + const generateIpFromPool = (cidr, usedIps, excludeIp = null, forRemote = false, pairedLocalIp = null) => { + // Парсим CIDR (например, "10.10.0.0/24") + const [network, prefixLength] = cidr.split('/'); + if (!network || !prefixLength) { + return null; + } + + const prefix = parseInt(prefixLength); + if (isNaN(prefix) || prefix < 0 || prefix > 32) { + return null; + } + + const networkParts = network.split('.'); + if (networkParts.length !== 4) { + return null; + } + + const [a, b, c, d] = networkParts.map(Number); + if (networkParts.some(p => isNaN(Number(p)))) { + return null; + } + + // Вычисляем количество хостов в сети + const hostBits = 32 - prefix; + const totalHosts = Math.pow(2, hostBits); + + // Если подбираем Remote IP и есть Local IP, генерируем парный IP в той же сети /30 + if (forRemote && pairedLocalIp) { + const localIpParts = pairedLocalIp.trim().split('.'); + if (localIpParts.length === 4) { + const baseOctet = parseInt(localIpParts[3]); + const networkBase = Math.floor(baseOctet / 4) * 4; + const localOffset = baseOctet - networkBase; + + let remoteOffset; + if (localOffset === 1) { + remoteOffset = 2; + } else if (localOffset === 2) { + remoteOffset = 1; + } else { + remoteOffset = localOffset === 0 ? 2 : (localOffset === 3 ? 1 : 2); + } + + const remoteOctet = networkBase + remoteOffset; + if (remoteOctet >= 0 && remoteOctet <= 255) { + const remoteIp = `${localIpParts[0]}.${localIpParts[1]}.${localIpParts[2]}.${remoteOctet}`; + // Проверяем, что IP входит в диапазон пула + if (isIpInCidr(remoteIp, cidr) && !usedIps.has(remoteIp)) { + return remoteIp; + } + } + } + } + + // Генерируем IP адреса из пула + // Для /30 сетей используем шаг 4 + const step = prefix >= 30 ? 4 : 1; + + // Определяем диапазон для перебора в зависимости от префикса + let startOctet = 0; + let endOctet = 255; + + if (prefix >= 24) { + // /24 - /32: меняем только последний октет + startOctet = d; + endOctet = Math.min(d + totalHosts - 1, 255); + } else if (prefix >= 16) { + // /16 - /23: меняем третий и четвертый октеты + const thirdOctetRange = Math.pow(2, Math.max(0, 24 - prefix)); + for (let cVal = c; cVal < c + thirdOctetRange && cVal <= 255; cVal++) { + const dStart = (cVal === c) ? d : 0; + const dEnd = (cVal === c + thirdOctetRange - 1) ? Math.min(d + totalHosts - 1, 255) : 255; + for (let dVal = dStart; dVal <= dEnd; dVal += step) { + if (prefix >= 30 && dVal % 4 !== 0) continue; // Для /30 только кратные 4 + const ip = `${a}.${b}.${cVal}.${dVal + 1}`; // Local IP = базовый + 1 + if (isIpInCidr(ip, cidr) && !usedIps.has(ip)) { + return ip; + } + } + } + return null; + } else { + // /0 - /15: слишком большой диапазон, используем упрощенную логику + return null; + } + + // Перебираем IP в диапазоне + for (let dVal = startOctet; dVal <= endOctet; dVal += step) { + if (prefix >= 30 && dVal % 4 !== 0) continue; // Для /30 только кратные 4 + + // Local IP = базовый + 1, Remote IP = базовый + 2 + const localIp = `${a}.${b}.${c}.${dVal + 1}`; + const remoteIp = `${a}.${b}.${c}.${dVal + 2}`; + + // Проверяем, что IP входит в диапазон пула + if (!isIpInCidr(localIp, cidr)) continue; + + // Проверяем, свободны ли оба IP (для /30) + if (prefix >= 30) { + if (!usedIps.has(localIp) && !usedIps.has(remoteIp)) { + return forRemote ? remoteIp : localIp; + } + } else { + // Для других префиксов просто проверяем один IP + if (!usedIps.has(localIp)) { + return localIp; + } + } + } + + return null; + }; + + // === Проверка, входит ли IP в CIDR === + const isIpInCidr = (ip, cidr) => { + const [network, prefixLength] = cidr.split('/'); + if (!network || !prefixLength) return false; + + const prefix = parseInt(prefixLength); + if (isNaN(prefix)) return false; + + const networkParts = network.split('.').map(Number); + const ipParts = ip.split('.').map(Number); + + if (networkParts.length !== 4 || ipParts.length !== 4) return false; + + // Вычисляем маску сети + const mask = (0xFFFFFFFF << (32 - prefix)) >>> 0; + + // Преобразуем IP в число + const networkNum = (networkParts[0] << 24) | (networkParts[1] << 16) | (networkParts[2] << 8) | networkParts[3]; + const ipNum = (ipParts[0] << 24) | (ipParts[1] << 16) | (ipParts[2] << 8) | ipParts[3]; + + return (networkNum & mask) === (ipNum & mask); + }; + // === Подбор IP для интерфейса === const handleSuggestLocalIp = () => { - const freeIp = generateFreePrivateIp(editingInterface?.localIp, false, null); + const ipPoolId = editingInterface?.ipPoolId || null; + const freeIp = generateFreePrivateIp(editingInterface?.localIp, false, null, ipPoolId); if (freeIp) { setEditingInterface({ ...editingInterface, localIp: freeIp }); notify.success(`Подобран Local IP: ${freeIp}`); @@ -739,10 +1026,12 @@ function NetworkConfigManager() { const handleSuggestRemoteIp = () => { // Если есть Local IP, генерируем парный Remote IP в той же сети /30 + const ipPoolId = editingInterface?.ipPoolId || null; const freeIp = generateFreePrivateIp( editingInterface?.remoteIp, true, - editingInterface?.localIp || null + editingInterface?.localIp || null, + ipPoolId ); if (freeIp) { setEditingInterface({ ...editingInterface, remoteIp: freeIp }); @@ -2842,6 +3131,24 @@ function NetworkConfigManager() { )} +