feat(NetworkConfigManager): add IP pool selection for IP address generation; enhance IP address generation logic to utilize selected IP pools and improve user interface for IP pool management
Publish Fast Tabler Docker image / build-and-push-fast (push) Failing after 54s
Publish Fast Tabler Docker image / build-and-push-fast (push) Failing after 54s
This commit is contained in:
@@ -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() {
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="col-md-6">
|
||||
<label className="form-label">IP пул</label>
|
||||
<select
|
||||
className="form-select"
|
||||
value={editingInterface.ipPoolId || ''}
|
||||
onChange={(e) => setEditingInterface({ ...editingInterface, ipPoolId: e.target.value || '' })}
|
||||
>
|
||||
<option value="">Автоматический подбор (RFC 1918)</option>
|
||||
{config.ipPools?.map(pool => (
|
||||
<option key={pool.id} value={pool.id}>
|
||||
{pool.name} ({pool.cidr}) {pool.description ? `- ${pool.description}` : ''}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<div className="form-text">
|
||||
Выберите IP пул для подбора адресов или оставьте автоматический подбор
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-md-3">
|
||||
<label className="form-label">Local IP</label>
|
||||
<div className="input-group">
|
||||
@@ -3070,8 +3377,8 @@ function NetworkConfigManager() {
|
||||
const autoName2 = generateInterfaceName(server2, server1, templateType, templateNamePrefix);
|
||||
const previewName1 = templateName1.trim() || autoName1;
|
||||
const previewName2 = templateName2.trim() || autoName2;
|
||||
const previewLocalIp = generateFreePrivateIp(null, false, null);
|
||||
const previewRemoteIp = previewLocalIp ? generateFreePrivateIp(null, true, previewLocalIp) : null;
|
||||
const previewLocalIp = generateFreePrivateIp(null, false, null, templateIpPoolId || null);
|
||||
const previewRemoteIp = previewLocalIp ? generateFreePrivateIp(null, true, previewLocalIp, templateIpPoolId || null) : null;
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -3137,6 +3444,24 @@ function NetworkConfigManager() {
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="col-12">
|
||||
<label className="form-label">IP пул</label>
|
||||
<select
|
||||
className="form-select"
|
||||
value={templateIpPoolId}
|
||||
onChange={(e) => setTemplateIpPoolId(e.target.value)}
|
||||
>
|
||||
<option value="">Автоматический подбор (RFC 1918)</option>
|
||||
{config.ipPools?.map(pool => (
|
||||
<option key={pool.id} value={pool.id}>
|
||||
{pool.name} ({pool.cidr}) {pool.description ? `- ${pool.description}` : ''}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<div className="form-text">
|
||||
Выберите IP пул для подбора адресов или оставьте автоматический подбор
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-12">
|
||||
<div className="card card-sm border">
|
||||
<div className="card-body">
|
||||
|
||||
Reference in New Issue
Block a user