From 8c13ae9df4f67f84db38dd5af35c755e4d492220 Mon Sep 17 00:00:00 2001 From: shats Date: Sun, 22 Feb 2026 18:03:58 +0700 Subject: [PATCH] feat(MikrotikConfigRoutes): enhance gateway and interface matching logic to support additional server IDs and improve route extraction --- backend/routes/mikrotikConfigRoutes.js | 94 ++++++++++++++------------ 1 file changed, 51 insertions(+), 43 deletions(-) diff --git a/backend/routes/mikrotikConfigRoutes.js b/backend/routes/mikrotikConfigRoutes.js index dea73a8..1cc12a7 100644 --- a/backend/routes/mikrotikConfigRoutes.js +++ b/backend/routes/mikrotikConfigRoutes.js @@ -455,12 +455,11 @@ async function runPingViaRouter(serverId, gatewayIp, target, count = 5, interfac const config = await loadNetworkConfig(); const tunnelIfaces = Array.isArray(config.tunnelInterfaces) ? config.tunnelInterfaces : []; - // 1) Пытаемся сопоставить gatewayIp с локальным/удалённым IP туннельного интерфейса + // 1) Пытаемся сопоставить gatewayIp с локальным/удалённым IP туннельного интерфейса (сервер может быть serverId или serverId2) const iface = tunnelIfaces.find((i) => { const sameServer = - i.serverId === serverId || - i.serverId === server.ip || - i.serverId === server.dns; + i.serverId === serverId || i.serverId === server.ip || i.serverId === server.dns || + i.serverId2 === serverId || i.serverId2 === server.ip || i.serverId2 === server.dns; const sameIp = i.remoteIp === gatewayIp || i.localIp === gatewayIp; return sameServer && sameIp; }); @@ -474,9 +473,8 @@ async function runPingViaRouter(serverId, gatewayIp, target, count = 5, interfac const gw = config.gateways.find((g) => { if (!g || !g.ip) return false; const sameServer = - g.serverId === serverId || - g.serverId === server.ip || - g.serverId === server.dns; + g.serverId === serverId || g.serverId === server.ip || g.serverId === server.dns || + (g.serverId2 && (g.serverId2 === serverId || g.serverId2 === server.ip || g.serverId2 === server.dns)); return sameServer && g.ip === gatewayIp; }); @@ -504,33 +502,40 @@ async function runPingViaRouter(serverId, gatewayIp, target, count = 5, interfac } // 3) Если даже из /network-config не смогли получить interface — - // пробуем вытащить его напрямую из маршрутов MikroTik по полю gateway. + // пробуем вытащить его из маршрутов MikroTik (gateway в формате "IP%interface" или отдельное поле interface). if (!ifaceName) { try { const routeRes = await client.command('ip/route/print', { - '.proplist': ['gateway'], - '.query': [`gateway~${gatewayIp}`], + '.proplist': ['gateway', 'interface'], }); const data = routeRes?.data; const routes = Array.isArray(data) ? data : (data ? [data] : []); - const match = routes.find((r) => typeof r.gateway === 'string' && r.gateway.includes(gatewayIp)); - if (match && typeof match.gateway === 'string') { - const gwStr = match.gateway; - const percentIdx = gwStr.indexOf('%'); - if (percentIdx >= 0 && percentIdx < gwStr.length - 1) { - ifaceName = gwStr.slice(percentIdx + 1); + const routeList = Array.isArray(routes) ? routes : (routes && typeof routes === 'object' && routes.ret ? routes.ret : []); + const match = routeList.find((r) => { + const gw = r.gateway ?? r.Gateway; + return typeof gw === 'string' && (gw.includes(gatewayIp) || gw.startsWith(gatewayIp)); + }); + if (match) { + const gwStr = String(match.gateway ?? match.Gateway ?? ''); + const ifaceFromRoute = match.interface ?? match.Interface; + if (typeof ifaceFromRoute === 'string' && ifaceFromRoute.trim()) { + ifaceName = ifaceFromRoute.trim(); + } else { + const percentIdx = gwStr.indexOf('%'); + if (percentIdx >= 0 && percentIdx < gwStr.length - 1) { + ifaceName = gwStr.slice(percentIdx + 1).trim(); + } } } } catch (_) { - // Если чтение маршрутов не удалось — просто продолжаем без interface, - // чтобы не ломать сам ping. + // Если чтение маршрутов не удалось — просто продолжаем без interface. } } } - // 4) Последний фоллбек (только для jumphost или home с указанным шлюзом): ether1/ether2. - // Для home без шлюза интерфейс не задаём — пинг идёт по маршруту по умолчанию. - if (!ifaceName && !isHomeNoGateway) { + // 4) При указанном gatewayIp не подставляем ether1/ether2 — иначе пинг пойдёт не в туннель. Оставляем interface пустым, RouterOS выберет маршрут. + // Фоллбек ether1/ether2 только если шлюз не указан (редкий кейс). + if (!ifaceName && !isHomeNoGateway && !gatewayIp) { try { const ifRes = await client.command('interface/print', { '.proplist': ['name'], @@ -727,12 +732,11 @@ async function tracerouteViaGateway(req, res) { const config = await loadNetworkConfig(); const tunnelIfaces = Array.isArray(config.tunnelInterfaces) ? config.tunnelInterfaces : []; - // 1) Сопоставляем gatewayIp с локальным/удалённым IP туннельного интерфейса + // 1) Сопоставляем gatewayIp с локальным/удалённым IP туннельного интерфейса (сервер может быть serverId или serverId2) const iface = tunnelIfaces.find((i) => { const sameServer = - i.serverId === serverId || - i.serverId === server.ip || - i.serverId === server.dns; + i.serverId === serverId || i.serverId === server.ip || i.serverId === server.dns || + i.serverId2 === serverId || i.serverId2 === server.ip || i.serverId2 === server.dns; const sameIp = i.remoteIp === gatewayIp || i.localIp === gatewayIp; return sameServer && sameIp; }); @@ -746,9 +750,8 @@ async function tracerouteViaGateway(req, res) { const gw = config.gateways.find((g) => { if (!g || !g.ip) return false; const sameServer = - g.serverId === serverId || - g.serverId === server.ip || - g.serverId === server.dns; + g.serverId === serverId || g.serverId === server.ip || g.serverId === server.dns || + (g.serverId2 && (g.serverId2 === serverId || g.serverId2 === server.ip || g.serverId2 === server.dns)); return sameServer && g.ip === gatewayIp; }); @@ -776,34 +779,39 @@ async function tracerouteViaGateway(req, res) { } // 3) Если даже из /network-config не смогли получить interface — - // пробуем вытащить его напрямую из маршрутов MikroTik по полю gateway. + // пробуем вытащить его из маршрутов MikroTik (gateway в формате "IP%interface" или отдельное поле interface). if (!ifaceName) { try { const routeRes = await client.command('ip/route/print', { - '.proplist': ['gateway'], - '.query': [`gateway~${gatewayIp}`], + '.proplist': ['gateway', 'interface'], }); const data = routeRes?.data; const routes = Array.isArray(data) ? data : (data ? [data] : []); - const match = routes.find((r) => typeof r.gateway === 'string' && r.gateway.includes(gatewayIp)); - if (match && typeof match.gateway === 'string') { - const gwStr = match.gateway; - const percentIdx = gwStr.indexOf('%'); - if (percentIdx >= 0 && percentIdx < gwStr.length - 1) { - ifaceName = gwStr.slice(percentIdx + 1); + const routeList = Array.isArray(routes) ? routes : (routes && typeof routes === 'object' && routes.ret ? routes.ret : []); + const match = routeList.find((r) => { + const gw = r.gateway ?? r.Gateway; + return typeof gw === 'string' && (gw.includes(gatewayIp) || gw.startsWith(gatewayIp)); + }); + if (match) { + const gwStr = String(match.gateway ?? match.Gateway ?? ''); + const ifaceFromRoute = match.interface ?? match.Interface; + if (typeof ifaceFromRoute === 'string' && ifaceFromRoute.trim()) { + ifaceName = ifaceFromRoute.trim(); + } else { + const percentIdx = gwStr.indexOf('%'); + if (percentIdx >= 0 && percentIdx < gwStr.length - 1) { + ifaceName = gwStr.slice(percentIdx + 1).trim(); + } } } } catch (_) { - // Если чтение маршрутов не удалось — просто продолжаем без interface, - // чтобы не ломать сам traceroute. + // Если чтение маршрутов не удалось — просто продолжаем без interface. } } } - // 4) Последний фоллбек: если ifaceName до сих пор не определён, - // пробуем взять первый физический порт ether1/ether2 (если есть), - // иначе любой интерфейс с префиксом "ether". - if (!ifaceName) { + // 4) При указанном gatewayIp не подставляем ether1/ether2. Фоллбек только если шлюз не указан. + if (!ifaceName && !gatewayIp) { try { const ifRes = await client.command('interface/print', { '.proplist': ['name'],