From b94b7c67e24fd45a0eacf541efbcdfaa94ce67b2 Mon Sep 17 00:00:00 2001 From: shats Date: Thu, 22 Jan 2026 17:49:47 +0700 Subject: [PATCH] feat(NetworkConfigManager): enhance IP generation logic for /30 networks; support paired local and remote IP generation --- frontend/src/NetworkConfigManager.jsx | 114 +++++++++++++++++--------- 1 file changed, 77 insertions(+), 37 deletions(-) diff --git a/frontend/src/NetworkConfigManager.jsx b/frontend/src/NetworkConfigManager.jsx index d2e0f9c..e39d2df 100644 --- a/frontend/src/NetworkConfigManager.jsx +++ b/frontend/src/NetworkConfigManager.jsx @@ -489,26 +489,58 @@ function NetworkConfigManager() { }, [config.tunnelInterfaces]); // === Генерация свободного IP адреса из приватных диапазонов === - const generateFreePrivateIp = (excludeIp = null) => { + // Генерирует IP в сетях /30 (четные базовые адреса) + const generateFreePrivateIp = (excludeIp = null, forRemote = false, pairedLocalIp = null) => { const usedIps = new Set(getAllUsedIpsSet); if (excludeIp) { usedIps.delete(excludeIp.trim()); } + // Если подбираем Remote IP и есть Local IP, генерируем парный IP в той же сети /30 + if (forRemote && pairedLocalIp) { + const localIpParts = pairedLocalIp.trim().split('.'); + if (localIpParts.length === 4) { + const baseOctet = parseInt(localIpParts[3]); + // Определяем базовый адрес сети /30 (округляем вниз до кратного 4) + const networkBase = Math.floor(baseOctet / 4) * 4; + const localOffset = baseOctet - networkBase; + + // Если Local IP уже .1, то Remote должен быть .2 + // Если Local IP .2, то Remote должен быть .1 + let remoteOffset; + if (localOffset === 1) { + remoteOffset = 2; + } else if (localOffset === 2) { + remoteOffset = 1; + } else { + // Если Local IP не .1 или .2, используем стандартную логику + 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}`; + if (!usedIps.has(remoteIp)) { + return remoteIp; + } + } + } + } + // Приватные диапазоны для туннелей (RFC 1918) - // Начинаем с 10.10.0.0/16 для удобства + // Генерируем пары IP в сетях /30 (базовый адрес кратен 4) const ranges = [ - { start: [10, 10, 0, 1], end: [10, 10, 255, 254] }, // 10.10.0.0/16 - { start: [10, 0, 0, 1], end: [10, 255, 255, 254] }, // 10.0.0.0/8 (весь диапазон) - { start: [172, 16, 0, 1], end: [172, 31, 255, 254] }, // 172.16.0.0/12 - { start: [192, 168, 0, 1], end: [192, 168, 255, 254] }, // 192.168.0.0/16 + { start: [10, 10, 0, 0], end: [10, 10, 255, 252] }, // 10.10.0.0/16 + { start: [10, 0, 0, 0], end: [10, 255, 255, 252] }, // 10.0.0.0/8 + { start: [172, 16, 0, 0], end: [172, 31, 255, 252] }, // 172.16.0.0/12 + { start: [192, 168, 0, 0], end: [192, 168, 255, 252] }, // 192.168.0.0/16 ]; for (const range of ranges) { const [a, b, c, d] = range.start; const [aEnd, bEnd, cEnd, dEnd] = range.end; - // Перебираем IP в диапазоне + // Перебираем IP в диапазоне с шагом 4 (сети /30) for (let aVal = a; aVal <= aEnd; aVal++) { const bStart = (aVal === a) ? b : 0; const bEndVal = (aVal === aEnd) ? bEnd : 255; @@ -516,12 +548,17 @@ function NetworkConfigManager() { const cStart = (aVal === a && bVal === b) ? c : 0; const cEndVal = (aVal === aEnd && bVal === bEnd) ? cEnd : 255; for (let cVal = cStart; cVal <= cEndVal; cVal++) { - const dStart = (aVal === a && bVal === b && cVal === c) ? d : 1; - const dEndVal = (aVal === aEnd && bVal === bEnd && cVal === cEnd) ? dEnd : 254; - for (let dVal = dStart; dVal <= dEndVal; dVal++) { - const ip = `${aVal}.${bVal}.${cVal}.${dVal}`; - if (!usedIps.has(ip)) { - return ip; + const dStart = (aVal === a && bVal === b && cVal === c) ? d : 0; + const dEndVal = (aVal === aEnd && bVal === bEnd && cVal === cEnd) ? dEnd : 252; + // Перебираем только базовые адреса сетей /30 (кратные 4) + for (let dVal = dStart; dVal <= dEndVal; dVal += 4) { + // Local IP = базовый + 1, Remote IP = базовый + 2 + const localIp = `${aVal}.${bVal}.${cVal}.${dVal + 1}`; + const remoteIp = `${aVal}.${bVal}.${cVal}.${dVal + 2}`; + + // Проверяем, свободны ли оба IP + if (!usedIps.has(localIp) && !usedIps.has(remoteIp)) { + return forRemote ? remoteIp : localIp; } } } @@ -529,13 +566,13 @@ function NetworkConfigManager() { } } - // Если все IP заняты (маловероятно), возвращаем null + // Если все IP заняты, возвращаем null return null; }; // === Подбор IP для интерфейса === const handleSuggestLocalIp = () => { - const freeIp = generateFreePrivateIp(editingInterface?.localIp); + const freeIp = generateFreePrivateIp(editingInterface?.localIp, false, null); if (freeIp) { setEditingInterface({ ...editingInterface, localIp: freeIp }); notify.success(`Подобран Local IP: ${freeIp}`); @@ -545,7 +582,12 @@ function NetworkConfigManager() { }; const handleSuggestRemoteIp = () => { - const freeIp = generateFreePrivateIp(editingInterface?.remoteIp); + // Если есть Local IP, генерируем парный Remote IP в той же сети /30 + const freeIp = generateFreePrivateIp( + editingInterface?.remoteIp, + true, + editingInterface?.localIp || null + ); if (freeIp) { setEditingInterface({ ...editingInterface, remoteIp: freeIp }); notify.success(`Подобран Remote IP: ${freeIp}`); @@ -2372,16 +2414,15 @@ function NetworkConfigManager() { />
-
-
- setEditingInterface({ ...editingInterface, localIp: val })} - placeholder="10.10.0.1" - /> -
+ +
+ setEditingInterface({ ...editingInterface, localIp: e.target.value })} + placeholder="10.10.0.1" + />