From ba006edec02cb885402a31fb9f34983de303ca5a Mon Sep 17 00:00:00 2001 From: shats Date: Thu, 12 Feb 2026 15:39:34 +0700 Subject: [PATCH] feat(MikrotikTools): enhance traceroute functionality to dynamically determine interface based on gateway IP --- backend/routes/mikrotikConfigRoutes.js | 115 ++++++++++++++++++++++++- 1 file changed, 112 insertions(+), 3 deletions(-) diff --git a/backend/routes/mikrotikConfigRoutes.js b/backend/routes/mikrotikConfigRoutes.js index 096bbed..531ed5c 100644 --- a/backend/routes/mikrotikConfigRoutes.js +++ b/backend/routes/mikrotikConfigRoutes.js @@ -583,12 +583,121 @@ async function tracerouteViaGateway(req, res) { const client = createRosClient(creds); + // Пытаемся определить interface по gatewayIp (как в pingViaInterface) + let ifaceName = null; + + if (gatewayIp) { + const config = await loadNetworkConfig(); + const tunnelIfaces = Array.isArray(config.tunnelInterfaces) ? config.tunnelInterfaces : []; + + // 1) Сопоставляем gatewayIp с локальным/удалённым IP туннельного интерфейса + const iface = tunnelIfaces.find((i) => { + const sameServer = + i.serverId === serverId || + i.serverId === server.ip || + i.serverId === server.dns; + const sameIp = i.remoteIp === gatewayIp || i.localIp === gatewayIp; + return sameServer && sameIp; + }); + + if (iface) { + ifaceName = iface.name || null; + } + + // 2) Если по tunnelInterfaces не нашли — пробуем взять интерфейс из gateways в /network-config + if (!ifaceName && Array.isArray(config.gateways)) { + 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; + return sameServer && g.ip === gatewayIp; + }); + + if (gw) { + // Вариант А: gateway сам хранит имя интерфейса + if (gw.interfaceName) { + ifaceName = gw.interfaceName; + } + + // Вариант Б: gateway ссылается на интерфейс через parentGatewayId / parentGateways + if (!ifaceName && (gw.parentGatewayId || (Array.isArray(gw.parentGateways) && gw.parentGateways.length > 0))) { + const parentRefs = (gw.parentGateways && gw.parentGateways.length > 0) + ? gw.parentGateways + : [{ id: gw.parentGatewayId }]; + + for (const pref of parentRefs) { + const parent = getParentGateway(pref.id, config); + if (parent && parent.parentType === 'interface' && parent.name) { + ifaceName = parent.name; + break; + } + } + } + } + } + + // 3) Если даже из /network-config не смогли получить interface — + // пробуем вытащить его напрямую из маршрутов MikroTik по полю gateway. + if (!ifaceName) { + try { + const routeRes = await client.command('ip/route/print', { + '.proplist': ['gateway'], + '.query': [`gateway~${gatewayIp}`], + }); + 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); + } + } + } catch (_) { + // Если чтение маршрутов не удалось — просто продолжаем без interface, + // чтобы не ломать сам traceroute. + } + } + } + + // 4) Последний фоллбек: если ifaceName до сих пор не определён, + // пробуем взять первый физический порт ether1/ether2 (если есть), + // иначе любой интерфейс с префиксом "ether". + if (!ifaceName) { + try { + const ifRes = await client.command('interface/print', { + '.proplist': ['name'], + }); + const data = ifRes?.data; + const interfaces = Array.isArray(data) ? data : (data ? [data] : []); + const names = interfaces + .map((it) => (typeof it.name === 'string' ? it.name : null)) + .filter(Boolean); + + let candidate = null; + if (names.includes('ether1')) candidate = 'ether1'; + else if (names.includes('ether2')) candidate = 'ether2'; + else { + candidate = names.find((n) => n.toLowerCase().startsWith('ether')) || null; + } + + if (candidate) { + ifaceName = candidate; + } + } catch (_) { + // Если не получилось прочитать интерфейсы — просто продолжаем без interface. + } + } + const body = { address: target, }; - if (gatewayIp) { - body.gateway = gatewayIp; + if (ifaceName) { + body.interface = ifaceName; } const hopsNum = Number(maxHops); @@ -601,7 +710,7 @@ async function tracerouteViaGateway(req, res) { '/tool/traceroute', `address=${body.address}`, ]; - if (body.gateway) cliParts.push(`gateway=${body.gateway}`); + if (ifaceName) cliParts.push(`interface=${ifaceName}`); if (body['max-hops']) cliParts.push(`max-hops=${body['max-hops']}`); console.log('[mikrotik][tracerouteViaGateway]', {