feat(MikrotikTools): enhance traceroute functionality to dynamically determine interface based on gateway IP
Publish Fast Tabler Docker image / build-and-push-fast (push) Failing after 2m45s
Publish Fast Tabler Docker image / build-and-push-fast (push) Failing after 2m45s
This commit is contained in:
@@ -583,12 +583,121 @@ async function tracerouteViaGateway(req, res) {
|
|||||||
|
|
||||||
const client = createRosClient(creds);
|
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 = {
|
const body = {
|
||||||
address: target,
|
address: target,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (gatewayIp) {
|
if (ifaceName) {
|
||||||
body.gateway = gatewayIp;
|
body.interface = ifaceName;
|
||||||
}
|
}
|
||||||
|
|
||||||
const hopsNum = Number(maxHops);
|
const hopsNum = Number(maxHops);
|
||||||
@@ -601,7 +710,7 @@ async function tracerouteViaGateway(req, res) {
|
|||||||
'/tool/traceroute',
|
'/tool/traceroute',
|
||||||
`address=${body.address}`,
|
`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']}`);
|
if (body['max-hops']) cliParts.push(`max-hops=${body['max-hops']}`);
|
||||||
|
|
||||||
console.log('[mikrotik][tracerouteViaGateway]', {
|
console.log('[mikrotik][tracerouteViaGateway]', {
|
||||||
|
|||||||
Reference in New Issue
Block a user