@usex/mikrotik-mcp 3.51.0 → 3.52.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +1 -1
- package/dist/index.js +1 -1
- package/dist/shared/{cli-22wrpg71.js → cli-ewkttc56.js} +90 -110
- package/dist/shared/{cli-dq28zbns.js → cli-thdzk64q.js} +1 -1
- package/dist/shared/{library-510csx1a.js → library-4npjv6jq.js} +1 -1
- package/dist/shared/{library-mwcpnqqg.js → library-n3yvjpkm.js} +90 -110
- package/dist/ui/observability.html +12 -12
- package/package.json +1 -1
- package/prompts/audit-security-posture.md +41 -0
- package/prompts/backup-and-document.md +10 -5
- package/prompts/choose-vpn-solution.md +3 -0
- package/prompts/design-qos-policy.md +49 -0
- package/prompts/detect-config-drift.md +51 -0
- package/prompts/diagnose-connectivity.md +15 -5
- package/prompts/disaster-recovery-drill.md +43 -0
- package/prompts/fleet-health-check.md +41 -0
- package/prompts/harden-router.md +10 -0
- package/prompts/manage-certificates.md +51 -0
- package/prompts/safe-change-workflow.md +6 -0
- package/prompts/setup-bgp-peering.md +57 -0
- package/prompts/setup-hotspot.md +52 -0
- package/prompts/setup-multi-wan.md +46 -0
- package/prompts/setup-ospf-peering.md +56 -0
- package/prompts/setup-port-knocking.md +53 -0
- package/prompts/setup-threat-feeds.md +47 -0
- package/prompts/setup-vlan-network.md +47 -0
- package/prompts/troubleshoot-network.md +45 -0
- package/prompts/upgrade-firmware.md +48 -0
package/dist/cli.js
CHANGED
package/dist/index.js
CHANGED
|
@@ -22,7 +22,7 @@ import {
|
|
|
22
22
|
resolveDeviceName,
|
|
23
23
|
selectToolModules,
|
|
24
24
|
setConfig
|
|
25
|
-
} from "./shared/library-
|
|
25
|
+
} from "./shared/library-n3yvjpkm.js";
|
|
26
26
|
// src/server.ts
|
|
27
27
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
28
28
|
import { ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
@@ -5797,7 +5797,7 @@ function renderComplianceReport(report, device, options) {
|
|
|
5797
5797
|
`).trimEnd();
|
|
5798
5798
|
}
|
|
5799
5799
|
|
|
5800
|
-
// src/
|
|
5800
|
+
// src/utils/safe-exec.ts
|
|
5801
5801
|
async function safe(cmd, ctx) {
|
|
5802
5802
|
try {
|
|
5803
5803
|
const out = await executeMikrotikCommand(cmd, ctx);
|
|
@@ -5806,6 +5806,8 @@ async function safe(cmd, ctx) {
|
|
|
5806
5806
|
return "";
|
|
5807
5807
|
}
|
|
5808
5808
|
}
|
|
5809
|
+
|
|
5810
|
+
// src/tools/compliance-audit.ts
|
|
5809
5811
|
async function fetchComplianceState(ctx) {
|
|
5810
5812
|
const [
|
|
5811
5813
|
sshRaw,
|
|
@@ -7163,6 +7165,31 @@ ${created.join(`
|
|
|
7163
7165
|
// src/tools/container.ts
|
|
7164
7166
|
import { z as z17 } from "zod";
|
|
7165
7167
|
|
|
7168
|
+
// src/utils/ip.ts
|
|
7169
|
+
function isIpAddress(s) {
|
|
7170
|
+
return /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(s);
|
|
7171
|
+
}
|
|
7172
|
+
function isIpLike(s) {
|
|
7173
|
+
return /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}(\/\d+)?$/.test(s);
|
|
7174
|
+
}
|
|
7175
|
+
function isPrivateIp(ip) {
|
|
7176
|
+
const p = ip.split(".").map(Number);
|
|
7177
|
+
if (p.length !== 4)
|
|
7178
|
+
return false;
|
|
7179
|
+
if (p[0] === 10)
|
|
7180
|
+
return true;
|
|
7181
|
+
if (p[0] === 172 && p[1] >= 16 && p[1] <= 31)
|
|
7182
|
+
return true;
|
|
7183
|
+
if (p[0] === 192 && p[1] === 168)
|
|
7184
|
+
return true;
|
|
7185
|
+
if (p[0] === 169 && p[1] === 254)
|
|
7186
|
+
return true;
|
|
7187
|
+
return false;
|
|
7188
|
+
}
|
|
7189
|
+
// src/utils/num.ts
|
|
7190
|
+
function num(s) {
|
|
7191
|
+
return Number.parseInt(s ?? "0", 10) || 0;
|
|
7192
|
+
}
|
|
7166
7193
|
// src/utils/or-match.ts
|
|
7167
7194
|
function orMatch(field, values, op = "~") {
|
|
7168
7195
|
if (values.length === 0)
|
|
@@ -7934,7 +7961,7 @@ var cache = null;
|
|
|
7934
7961
|
async function gateway() {
|
|
7935
7962
|
if (cache)
|
|
7936
7963
|
return cache;
|
|
7937
|
-
const { moduleCatalog } = await import("./cli-
|
|
7964
|
+
const { moduleCatalog } = await import("./cli-thdzk64q.js");
|
|
7938
7965
|
const forIndex = [];
|
|
7939
7966
|
const byName = new Map;
|
|
7940
7967
|
for (const mod of moduleCatalog) {
|
|
@@ -8904,7 +8931,7 @@ function rulesFromRows(rows) {
|
|
|
8904
8931
|
else
|
|
8905
8932
|
match[k] = v;
|
|
8906
8933
|
}
|
|
8907
|
-
const
|
|
8934
|
+
const num2 = (s) => {
|
|
8908
8935
|
if (s == null)
|
|
8909
8936
|
return;
|
|
8910
8937
|
const n = Number(s.replace(/\s/g, ""));
|
|
@@ -8917,8 +8944,8 @@ function rulesFromRows(rows) {
|
|
|
8917
8944
|
disabled: flags.includes("X"),
|
|
8918
8945
|
dynamic: flags.includes("D"),
|
|
8919
8946
|
comment: r.comment,
|
|
8920
|
-
packets:
|
|
8921
|
-
bytes:
|
|
8947
|
+
packets: num2(r.packets),
|
|
8948
|
+
bytes: num2(r.bytes),
|
|
8922
8949
|
match,
|
|
8923
8950
|
transform,
|
|
8924
8951
|
raw: r
|
|
@@ -8987,6 +9014,8 @@ function matchSummary(rule) {
|
|
|
8987
9014
|
"dst-port",
|
|
8988
9015
|
"in-interface",
|
|
8989
9016
|
"out-interface",
|
|
9017
|
+
"in-interface-list",
|
|
9018
|
+
"out-interface-list",
|
|
8990
9019
|
"connection-state",
|
|
8991
9020
|
"src-address-list",
|
|
8992
9021
|
"dst-address-list"
|
|
@@ -9735,11 +9764,12 @@ async function updateFilterRule(a, ctx) {
|
|
|
9735
9764
|
}
|
|
9736
9765
|
if (updates.length === 0)
|
|
9737
9766
|
return "No updates specified.";
|
|
9738
|
-
const
|
|
9767
|
+
const id = /^\d+$/.test(a.rule_id) ? `*${a.rule_id}` : a.rule_id;
|
|
9768
|
+
const cmd = `/ip firewall filter set ${id} ${updates.join(" ")}`;
|
|
9739
9769
|
const result = await executeMikrotikCommand(cmd, ctx);
|
|
9740
9770
|
if (looksLikeError(result))
|
|
9741
9771
|
return `Failed to update firewall filter rule: ${result}`;
|
|
9742
|
-
const details = await executeMikrotikCommand(`/ip firewall filter print detail where .id=${
|
|
9772
|
+
const details = await executeMikrotikCommand(`/ip firewall filter print detail where .id=${id}`, ctx);
|
|
9743
9773
|
return `Firewall filter rule updated successfully:
|
|
9744
9774
|
|
|
9745
9775
|
${details}`;
|
|
@@ -9894,7 +9924,8 @@ ${result}`;
|
|
|
9894
9924
|
},
|
|
9895
9925
|
async handler(a, ctx) {
|
|
9896
9926
|
ctx.info(`Getting firewall filter rule details: rule_id=${a.rule_id}`);
|
|
9897
|
-
const
|
|
9927
|
+
const id = /^\d+$/.test(a.rule_id) ? `*${a.rule_id}` : a.rule_id;
|
|
9928
|
+
const result = await executeMikrotikCommand(`/ip firewall filter print detail where .id=${id}`, ctx);
|
|
9898
9929
|
return isEmpty(result) ? `Firewall filter rule with ID '${a.rule_id}' not found.` : `FIREWALL FILTER RULE DETAILS:
|
|
9899
9930
|
|
|
9900
9931
|
${result}`;
|
|
@@ -9970,10 +10001,11 @@ ${result}`;
|
|
|
9970
10001
|
inputSchema: { rule_id: z28.string() },
|
|
9971
10002
|
async handler(a, ctx) {
|
|
9972
10003
|
ctx.info(`Removing firewall filter rule: rule_id=${a.rule_id}`);
|
|
9973
|
-
const
|
|
10004
|
+
const id = /^\d+$/.test(a.rule_id) ? `*${a.rule_id}` : a.rule_id;
|
|
10005
|
+
const count = await executeMikrotikCommand(`/ip firewall filter print count-only where .id=${id}`, ctx);
|
|
9974
10006
|
if (count.trim() === "0")
|
|
9975
10007
|
return `Firewall filter rule with ID '${a.rule_id}' not found.`;
|
|
9976
|
-
const result = await executeMikrotikCommand(`/ip firewall filter remove ${
|
|
10008
|
+
const result = await executeMikrotikCommand(`/ip firewall filter remove ${id}`, ctx);
|
|
9977
10009
|
if (looksLikeError(result))
|
|
9978
10010
|
return `Failed to remove firewall filter rule: ${result}`;
|
|
9979
10011
|
return `Firewall filter rule with ID '${a.rule_id}' removed successfully.`;
|
|
@@ -9990,10 +10022,11 @@ ${result}`;
|
|
|
9990
10022
|
},
|
|
9991
10023
|
async handler(a, ctx) {
|
|
9992
10024
|
ctx.info(`Moving firewall filter rule: rule_id=${a.rule_id} to position ${a.destination}`);
|
|
9993
|
-
const
|
|
10025
|
+
const id = /^\d+$/.test(a.rule_id) ? `*${a.rule_id}` : a.rule_id;
|
|
10026
|
+
const count = await executeMikrotikCommand(`/ip firewall filter print count-only where .id=${id}`, ctx);
|
|
9994
10027
|
if (count.trim() === "0")
|
|
9995
10028
|
return `Firewall filter rule with ID '${a.rule_id}' not found.`;
|
|
9996
|
-
const result = await executeMikrotikCommand(`/ip firewall filter move ${
|
|
10029
|
+
const result = await executeMikrotikCommand(`/ip firewall filter move ${id} destination=${a.destination}`, ctx);
|
|
9997
10030
|
if (looksLikeError(result))
|
|
9998
10031
|
return `Failed to move firewall filter rule: ${result}`;
|
|
9999
10032
|
return `Firewall filter rule with ID '${a.rule_id}' moved to position ${a.destination}.`;
|
|
@@ -10890,7 +10923,7 @@ function assessUpgradeReadiness(state) {
|
|
|
10890
10923
|
});
|
|
10891
10924
|
}
|
|
10892
10925
|
const cpuNum = Number.parseInt(state.cpuLoad, 10);
|
|
10893
|
-
if (!isNaN(cpuNum) && cpuNum > 80) {
|
|
10926
|
+
if (!Number.isNaN(cpuNum) && cpuNum > 80) {
|
|
10894
10927
|
checks.push({
|
|
10895
10928
|
label: "CPU load",
|
|
10896
10929
|
status: "caution",
|
|
@@ -10903,8 +10936,8 @@ function assessUpgradeReadiness(state) {
|
|
|
10903
10936
|
detail: `${state.cpuLoad}`
|
|
10904
10937
|
});
|
|
10905
10938
|
}
|
|
10906
|
-
const free =
|
|
10907
|
-
const total =
|
|
10939
|
+
const free = parseSize(state.freeMemory) ?? 0;
|
|
10940
|
+
const total = parseSize(state.totalMemory) ?? 0;
|
|
10908
10941
|
if (free > 0 && total > 0) {
|
|
10909
10942
|
const pct2 = Math.round(free / total * 100);
|
|
10910
10943
|
if (pct2 < 20) {
|
|
@@ -10941,20 +10974,6 @@ function assessUpgradeReadiness(state) {
|
|
|
10941
10974
|
}
|
|
10942
10975
|
return checks;
|
|
10943
10976
|
}
|
|
10944
|
-
function parseMem(s) {
|
|
10945
|
-
const m = s.match(/([\d.]+)\s*(MiB|GiB|KiB)?/i);
|
|
10946
|
-
if (!m)
|
|
10947
|
-
return 0;
|
|
10948
|
-
const val = Number.parseFloat(m[1]);
|
|
10949
|
-
const unit = (m[2] ?? "").toLowerCase();
|
|
10950
|
-
if (unit === "gib")
|
|
10951
|
-
return val * 1024 * 1024 * 1024;
|
|
10952
|
-
if (unit === "mib")
|
|
10953
|
-
return val * 1024 * 1024;
|
|
10954
|
-
if (unit === "kib")
|
|
10955
|
-
return val * 1024;
|
|
10956
|
-
return val;
|
|
10957
|
-
}
|
|
10958
10977
|
function renderFirmwareStatus(state, device) {
|
|
10959
10978
|
const lines = [];
|
|
10960
10979
|
lines.push(`FIRMWARE STATUS \u2014 ${device}`);
|
|
@@ -11092,14 +11111,6 @@ function renderReadiness(checks) {
|
|
|
11092
11111
|
}
|
|
11093
11112
|
|
|
11094
11113
|
// src/tools/firmware-lifecycle.ts
|
|
11095
|
-
async function safe2(cmd, ctx) {
|
|
11096
|
-
try {
|
|
11097
|
-
const out = await executeMikrotikCommand(cmd, ctx);
|
|
11098
|
-
return looksLikeError(out) ? "" : out;
|
|
11099
|
-
} catch {
|
|
11100
|
-
return "";
|
|
11101
|
-
}
|
|
11102
|
-
}
|
|
11103
11114
|
function parsePackages(raw) {
|
|
11104
11115
|
if (!raw)
|
|
11105
11116
|
return [];
|
|
@@ -11144,10 +11155,10 @@ function parseUpdateInfo(raw) {
|
|
|
11144
11155
|
}
|
|
11145
11156
|
async function fetchFirmwareState(ctx) {
|
|
11146
11157
|
const [resourceRaw, packagesRaw, routerboardRaw, updateRaw] = await Promise.all([
|
|
11147
|
-
|
|
11148
|
-
|
|
11149
|
-
|
|
11150
|
-
|
|
11158
|
+
safe("/system resource print", ctx),
|
|
11159
|
+
safe("/system package print detail", ctx),
|
|
11160
|
+
safe("/system routerboard print", ctx),
|
|
11161
|
+
safe("/system package update print", ctx)
|
|
11151
11162
|
]);
|
|
11152
11163
|
const res = parseKeyValues(resourceRaw);
|
|
11153
11164
|
return {
|
|
@@ -11165,12 +11176,12 @@ async function fetchFirmwareState(ctx) {
|
|
|
11165
11176
|
}
|
|
11166
11177
|
async function captureHealthSnapshot(ctx) {
|
|
11167
11178
|
const [resourceRaw, healthRaw, routesRaw, interfacesRaw, pppRaw, dhcpRaw] = await Promise.all([
|
|
11168
|
-
|
|
11169
|
-
|
|
11170
|
-
|
|
11171
|
-
|
|
11172
|
-
|
|
11173
|
-
|
|
11179
|
+
safe("/system resource print", ctx),
|
|
11180
|
+
safe("/system health print", ctx),
|
|
11181
|
+
safe("/ip route print count-only", ctx),
|
|
11182
|
+
safe("/interface print detail", ctx),
|
|
11183
|
+
safe("/ppp active print count-only", ctx),
|
|
11184
|
+
safe("/ip dhcp-server lease print count-only where status=bound", ctx)
|
|
11174
11185
|
]);
|
|
11175
11186
|
const res = parseKeyValues(resourceRaw);
|
|
11176
11187
|
const ifRows = interfacesRaw ? parseRecords(interfacesRaw).rows : [];
|
|
@@ -11270,8 +11281,8 @@ var firmwareLifecycleTools = [
|
|
|
11270
11281
|
if (looksLikeError(chResult))
|
|
11271
11282
|
return `Failed to set channel: ${chResult}`;
|
|
11272
11283
|
}
|
|
11273
|
-
|
|
11274
|
-
const updateRaw = await
|
|
11284
|
+
await executeMikrotikCommand("/system package update check-for-updates once", ctx);
|
|
11285
|
+
const updateRaw = await safe("/system package update print", ctx);
|
|
11275
11286
|
const updateInfo = parseUpdateInfo(updateRaw);
|
|
11276
11287
|
if (!updateInfo?.updateAvailable) {
|
|
11277
11288
|
return `No update available on '${device}'.
|
|
@@ -11285,7 +11296,7 @@ var firmwareLifecycleTools = [
|
|
|
11285
11296
|
if (looksLikeError(dlResult)) {
|
|
11286
11297
|
return `Failed to download update packages: ${dlResult}`;
|
|
11287
11298
|
}
|
|
11288
|
-
const postRaw = await
|
|
11299
|
+
const postRaw = await safe("/system package update print", ctx);
|
|
11289
11300
|
const postStatus = parseKeyValues(postRaw);
|
|
11290
11301
|
const lines = [];
|
|
11291
11302
|
lines.push(`FIRMWARE STAGED \u2014 ${device}`);
|
|
@@ -11318,7 +11329,7 @@ var firmwareLifecycleTools = [
|
|
|
11318
11329
|
return "Upgrade not confirmed. Pass confirm=true to proceed. The device WILL reboot.";
|
|
11319
11330
|
const device = resolveDeviceName(ctx.device);
|
|
11320
11331
|
ctx.info(`Firmware upgrade on '${device}'`);
|
|
11321
|
-
const updateRaw = await
|
|
11332
|
+
const updateRaw = await safe("/system package update print", ctx);
|
|
11322
11333
|
const updateInfo = parseUpdateInfo(updateRaw);
|
|
11323
11334
|
if (!updateInfo?.updateAvailable) {
|
|
11324
11335
|
return `No update available on '${device}'. ` + `Run \`firmware_check\` or \`firmware_stage\` first.
|
|
@@ -11427,7 +11438,7 @@ var firmwareLifecycleTools = [
|
|
|
11427
11438
|
lines.push(` Current: ${state.routerboard.currentFirmware} \u2192 Available: ${state.routerboard.upgradeFirmware}`);
|
|
11428
11439
|
lines.push(" Use `firmware_upgrade` with `upgrade_routerboard=true` to include it.");
|
|
11429
11440
|
}
|
|
11430
|
-
const schedRaw = await
|
|
11441
|
+
const schedRaw = await safe('/system scheduler print detail where name="mcp-firmware-upgrade"', ctx);
|
|
11431
11442
|
if (schedRaw && !isEmpty(schedRaw)) {
|
|
11432
11443
|
lines.push("");
|
|
11433
11444
|
lines.push("PENDING SCHEDULED UPGRADE:");
|
|
@@ -20385,7 +20396,7 @@ function analyzeRootCause(data) {
|
|
|
20385
20396
|
summary: `${boundLeases.length} active DHCP leases`
|
|
20386
20397
|
});
|
|
20387
20398
|
}
|
|
20388
|
-
if (isIpAddress(data.target)) {
|
|
20399
|
+
if (isIpAddress(data.target) && isPrivateIp(data.target)) {
|
|
20389
20400
|
const lease = data.dhcpLeases.find((l) => l.address === data.target);
|
|
20390
20401
|
const arp = data.arpEntries.find((e) => e.address === data.target);
|
|
20391
20402
|
if (!lease && !arp) {
|
|
@@ -20640,7 +20651,7 @@ function correlateRootCauses(data, evidence, causes) {
|
|
|
20640
20651
|
dimensions: ["resources"]
|
|
20641
20652
|
});
|
|
20642
20653
|
}
|
|
20643
|
-
if (isIpAddress(data.target)) {
|
|
20654
|
+
if (isIpAddress(data.target) && isPrivateIp(data.target)) {
|
|
20644
20655
|
const arp = data.arpEntries.find((e) => e.address === data.target);
|
|
20645
20656
|
if (arp && !arp.complete && data.ping?.lossPct === 100) {
|
|
20646
20657
|
causes.push({
|
|
@@ -20695,9 +20706,6 @@ function correlateRootCauses(data, evidence, causes) {
|
|
|
20695
20706
|
});
|
|
20696
20707
|
}
|
|
20697
20708
|
}
|
|
20698
|
-
function isIpAddress(s) {
|
|
20699
|
-
return /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(s);
|
|
20700
|
-
}
|
|
20701
20709
|
function formatRule(r) {
|
|
20702
20710
|
const parts = [`chain=${r.chain}`, `action=${r.action}`];
|
|
20703
20711
|
if (r.srcAddress)
|
|
@@ -20857,19 +20865,8 @@ ${footer}`;
|
|
|
20857
20865
|
}
|
|
20858
20866
|
|
|
20859
20867
|
// src/tools/root-cause.ts
|
|
20860
|
-
async function safe3(cmd, ctx) {
|
|
20861
|
-
try {
|
|
20862
|
-
const out = await executeMikrotikCommand(cmd, ctx);
|
|
20863
|
-
return looksLikeError(out) ? "" : out;
|
|
20864
|
-
} catch {
|
|
20865
|
-
return "";
|
|
20866
|
-
}
|
|
20867
|
-
}
|
|
20868
|
-
function num(s) {
|
|
20869
|
-
return Number.parseInt(s ?? "0", 10) || 0;
|
|
20870
|
-
}
|
|
20871
20868
|
async function collectInterfaces(ctx) {
|
|
20872
|
-
const raw = await
|
|
20869
|
+
const raw = await safe("/interface print detail", ctx);
|
|
20873
20870
|
if (!raw)
|
|
20874
20871
|
return [];
|
|
20875
20872
|
return parseRecords(raw).rows.map((r) => ({
|
|
@@ -20887,7 +20884,7 @@ async function collectInterfaces(ctx) {
|
|
|
20887
20884
|
}));
|
|
20888
20885
|
}
|
|
20889
20886
|
async function collectRoutes(ctx) {
|
|
20890
|
-
const raw = await
|
|
20887
|
+
const raw = await safe("/ip route print detail", ctx);
|
|
20891
20888
|
if (!raw)
|
|
20892
20889
|
return { routes: [], count: 0, hasDefault: false };
|
|
20893
20890
|
const rows = parseRecords(raw).rows;
|
|
@@ -20902,7 +20899,7 @@ async function collectRoutes(ctx) {
|
|
|
20902
20899
|
return { routes, count: routes.length, hasDefault };
|
|
20903
20900
|
}
|
|
20904
20901
|
async function collectOspfNeighbors(ctx) {
|
|
20905
|
-
const raw = await
|
|
20902
|
+
const raw = await safe("/routing ospf neighbor print detail", ctx);
|
|
20906
20903
|
if (!raw)
|
|
20907
20904
|
return [];
|
|
20908
20905
|
return parseRecords(raw).rows.map((r) => ({
|
|
@@ -20914,9 +20911,9 @@ async function collectOspfNeighbors(ctx) {
|
|
|
20914
20911
|
}));
|
|
20915
20912
|
}
|
|
20916
20913
|
async function collectBgpPeers(ctx) {
|
|
20917
|
-
const raw = await
|
|
20914
|
+
const raw = await safe("/routing bgp session print detail", ctx);
|
|
20918
20915
|
if (!raw) {
|
|
20919
|
-
const raw2 = await
|
|
20916
|
+
const raw2 = await safe("/routing bgp peer print detail", ctx);
|
|
20920
20917
|
if (!raw2)
|
|
20921
20918
|
return [];
|
|
20922
20919
|
return parseRecords(raw2).rows.map((r) => ({
|
|
@@ -20936,12 +20933,12 @@ async function collectBgpPeers(ctx) {
|
|
|
20936
20933
|
}));
|
|
20937
20934
|
}
|
|
20938
20935
|
async function collectFirewallRules(target, ctx) {
|
|
20939
|
-
const raw = await
|
|
20936
|
+
const raw = await safe("/ip firewall filter print detail", ctx);
|
|
20940
20937
|
if (!raw)
|
|
20941
20938
|
return { matching: [], totalCount: 0 };
|
|
20942
20939
|
const rows = parseRecords(raw).rows;
|
|
20943
20940
|
const all = rows.map(parseFirewallRow);
|
|
20944
|
-
const isIp =
|
|
20941
|
+
const isIp = isIpAddress(target);
|
|
20945
20942
|
const matching = isIp ? all.filter((r) => !r.srcAddress || r.srcAddress === target || !r.dstAddress || r.dstAddress === target) : all;
|
|
20946
20943
|
return { matching, totalCount: all.length };
|
|
20947
20944
|
}
|
|
@@ -20961,13 +20958,13 @@ function parseFirewallRow(r, i) {
|
|
|
20961
20958
|
};
|
|
20962
20959
|
}
|
|
20963
20960
|
async function collectNatRules(ctx) {
|
|
20964
|
-
const raw = await
|
|
20961
|
+
const raw = await safe("/ip firewall nat print detail", ctx);
|
|
20965
20962
|
if (!raw)
|
|
20966
20963
|
return [];
|
|
20967
20964
|
return parseRecords(raw).rows.map(parseFirewallRow);
|
|
20968
20965
|
}
|
|
20969
20966
|
async function collectArp(ctx) {
|
|
20970
|
-
const raw = await
|
|
20967
|
+
const raw = await safe("/ip arp print detail", ctx);
|
|
20971
20968
|
if (!raw)
|
|
20972
20969
|
return [];
|
|
20973
20970
|
return parseRecords(raw).rows.map((r) => ({
|
|
@@ -20979,7 +20976,7 @@ async function collectArp(ctx) {
|
|
|
20979
20976
|
}));
|
|
20980
20977
|
}
|
|
20981
20978
|
async function collectDhcpLeases(ctx) {
|
|
20982
|
-
const raw = await
|
|
20979
|
+
const raw = await safe("/ip dhcp-server lease print detail", ctx);
|
|
20983
20980
|
if (!raw)
|
|
20984
20981
|
return [];
|
|
20985
20982
|
return parseRecords(raw).rows.map((r) => ({
|
|
@@ -20992,7 +20989,7 @@ async function collectDhcpLeases(ctx) {
|
|
|
20992
20989
|
}));
|
|
20993
20990
|
}
|
|
20994
20991
|
async function collectLogs(target, ctx) {
|
|
20995
|
-
const raw = await
|
|
20992
|
+
const raw = await safe('/log print where topics~"error" or topics~"warning" or topics~"critical" or topics~"firewall" or topics~"system"', ctx);
|
|
20996
20993
|
if (!raw)
|
|
20997
20994
|
return [];
|
|
20998
20995
|
const rows = parseRecords(raw).rows;
|
|
@@ -21003,7 +21000,7 @@ async function collectLogs(target, ctx) {
|
|
|
21003
21000
|
}));
|
|
21004
21001
|
}
|
|
21005
21002
|
async function collectTunnels(ctx) {
|
|
21006
|
-
const raw = await
|
|
21003
|
+
const raw = await safe('/interface print detail where type~"gre|ipip|eoip|vxlan|wireguard|ovpn|sstp|pptp|l2tp"', ctx);
|
|
21007
21004
|
if (!raw)
|
|
21008
21005
|
return [];
|
|
21009
21006
|
return parseRecords(raw).rows.map((r) => ({
|
|
@@ -21038,27 +21035,27 @@ async function collectDiagnosticData(target, ctx, dimensions) {
|
|
|
21038
21035
|
logs,
|
|
21039
21036
|
tunnels
|
|
21040
21037
|
] = await Promise.all([
|
|
21041
|
-
dims.has("connectivity") ?
|
|
21038
|
+
dims.has("connectivity") ? safe(`/ping ${quoteValue(target)} count=5`, ctx) : Promise.resolve(""),
|
|
21042
21039
|
dims.has("interfaces") ? collectInterfaces(ctx) : Promise.resolve([]),
|
|
21043
21040
|
dims.has("routing") ? collectRoutes(ctx) : Promise.resolve({ routes: [], count: 0, hasDefault: true }),
|
|
21044
21041
|
dims.has("routing") ? collectOspfNeighbors(ctx) : Promise.resolve([]),
|
|
21045
21042
|
dims.has("routing") ? collectBgpPeers(ctx) : Promise.resolve([]),
|
|
21046
21043
|
dims.has("firewall") ? collectFirewallRules(target, ctx) : Promise.resolve({ matching: [], totalCount: 0 }),
|
|
21047
21044
|
dims.has("nat") ? collectNatRules(ctx) : Promise.resolve([]),
|
|
21048
|
-
dims.has("nat") ?
|
|
21045
|
+
dims.has("nat") ? safe("/ip firewall connection print count-only", ctx) : Promise.resolve("0"),
|
|
21049
21046
|
dims.has("arp_dhcp") ? collectArp(ctx) : Promise.resolve([]),
|
|
21050
21047
|
dims.has("arp_dhcp") ? collectDhcpLeases(ctx) : Promise.resolve([]),
|
|
21051
|
-
dims.has("dns") && !isIpLike(target) ?
|
|
21052
|
-
dims.has("dns") ?
|
|
21053
|
-
dims.has("resources") ?
|
|
21048
|
+
dims.has("dns") && !isIpLike(target) ? safe(`[:resolve ${quoteValue(target)}]`, ctx) : Promise.resolve(undefined),
|
|
21049
|
+
dims.has("dns") ? safe("/ip dns print", ctx) : Promise.resolve(""),
|
|
21050
|
+
dims.has("resources") ? safe("/system resource print", ctx) : Promise.resolve(""),
|
|
21054
21051
|
dims.has("logs") ? collectLogs(target, ctx) : Promise.resolve([]),
|
|
21055
21052
|
dims.has("vpn") ? collectTunnels(ctx) : Promise.resolve([])
|
|
21056
21053
|
]);
|
|
21057
21054
|
const ping = pingResult ? parsePingSummary(pingResult) ?? undefined : undefined;
|
|
21058
21055
|
const dnsKv = parseKeyValues(dnsSettingsRaw);
|
|
21059
21056
|
const resKv = parseKeyValues(resourceRaw);
|
|
21060
|
-
const totalMem =
|
|
21061
|
-
const freeMem =
|
|
21057
|
+
const totalMem = parseSize(resKv["total-memory"]) ?? 0;
|
|
21058
|
+
const freeMem = parseSize(resKv["free-memory"]) ?? 0;
|
|
21062
21059
|
const memUsedPct = totalMem > 0 ? Math.round((totalMem - freeMem) / totalMem * 100) : 0;
|
|
21063
21060
|
return {
|
|
21064
21061
|
target,
|
|
@@ -21077,9 +21074,9 @@ async function collectDiagnosticData(target, ctx, dimensions) {
|
|
|
21077
21074
|
arpEntries,
|
|
21078
21075
|
dhcpLeases,
|
|
21079
21076
|
dnsResolveResult: dnsResult ?? undefined,
|
|
21080
|
-
dnsServers: dnsKv.servers
|
|
21077
|
+
dnsServers: [dnsKv.servers, dnsKv["dynamic-servers"]].filter(Boolean).join(",") || "",
|
|
21081
21078
|
dnsAllowRemote: (dnsKv["allow-remote-requests"] ?? "").toLowerCase() === "yes",
|
|
21082
|
-
cpuLoad:
|
|
21079
|
+
cpuLoad: parsePercent(resKv["cpu-load"]) ?? 0,
|
|
21083
21080
|
memoryUsedPct: memUsedPct,
|
|
21084
21081
|
uptime: resKv.uptime ?? "",
|
|
21085
21082
|
rosVersion: resKv.version ?? "",
|
|
@@ -21087,23 +21084,6 @@ async function collectDiagnosticData(target, ctx, dimensions) {
|
|
|
21087
21084
|
tunnelInterfaces: tunnels
|
|
21088
21085
|
};
|
|
21089
21086
|
}
|
|
21090
|
-
function isIpLike(s) {
|
|
21091
|
-
return /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}(\/\d+)?$/.test(s);
|
|
21092
|
-
}
|
|
21093
|
-
function parseMemMiB(s) {
|
|
21094
|
-
if (!s)
|
|
21095
|
-
return 0;
|
|
21096
|
-
const m = s.match(/([\d.]+)\s*(MiB|GiB|KiB)?/i);
|
|
21097
|
-
if (!m)
|
|
21098
|
-
return 0;
|
|
21099
|
-
const v = Number.parseFloat(m[1]);
|
|
21100
|
-
const u = (m[2] ?? "").toLowerCase();
|
|
21101
|
-
if (u === "gib")
|
|
21102
|
-
return v * 1024;
|
|
21103
|
-
if (u === "kib")
|
|
21104
|
-
return v / 1024;
|
|
21105
|
-
return v;
|
|
21106
|
-
}
|
|
21107
21087
|
var dimensionEnum = z86.enum(ALL_DIMENSIONS).describe("Diagnostic dimension to investigate.");
|
|
21108
21088
|
var rootCauseTools = [
|
|
21109
21089
|
defineTool({
|
|
@@ -21145,9 +21125,9 @@ var rootCauseTools = [
|
|
|
21145
21125
|
const device = resolveDeviceName(ctx.device);
|
|
21146
21126
|
ctx.info(`Tracing path from '${device}' to ${a.target}`);
|
|
21147
21127
|
const [traceResult, pingResult, routeResult] = await Promise.all([
|
|
21148
|
-
|
|
21149
|
-
|
|
21150
|
-
|
|
21128
|
+
safe(`/tool traceroute ${quoteValue(a.target)} count=${a.count} use-dns=${a.use_dns ? "yes" : "no"}`, ctx),
|
|
21129
|
+
safe(`/ping ${quoteValue(a.target)} count=5`, ctx),
|
|
21130
|
+
safe(`/ip route print where dst-address=0.0.0.0/0`, ctx)
|
|
21151
21131
|
]);
|
|
21152
21132
|
const ping = parsePingSummary(pingResult);
|
|
21153
21133
|
const lines = [];
|
|
@@ -21209,7 +21189,7 @@ var rootCauseTools = [
|
|
|
21209
21189
|
filters.push(`message~"${a.keyword}"`);
|
|
21210
21190
|
}
|
|
21211
21191
|
const where = filters.length > 0 ? ` where ${filters.join(" ")}` : "";
|
|
21212
|
-
const raw = await
|
|
21192
|
+
const raw = await safe(`/log print detail${where}`, ctx);
|
|
21213
21193
|
if (!raw || isEmpty(raw)) {
|
|
21214
21194
|
return `No matching log events found on '${device}' in the last ${a.time_window}.`;
|
|
21215
21195
|
}
|