UNPKG

@fibercom/routeros-api

Version:

Robust MikroTik RouterOS API client for Node.js and TypeScript

689 lines 48.7 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.MikrotikAPI = void 0; const RouterOSAPI_1 = require("./RouterOSAPI"); const is_ip_1 = require("is-ip"); const ip = require("ip"); /** * Extended RouterOS API class with additional functionality */ class MikrotikAPI extends RouterOSAPI_1.RouterOSAPI { constructor(options) { super(options); } /** * Get system information */ getSystemInfo() { return __awaiter(this, void 0, void 0, function* () { return this.write("/system/routerboard/print"); }); } /** * Get system resources */ getSystemResources() { return __awaiter(this, void 0, void 0, function* () { return this.write("/system/resource/print"); }); } /** * Get interface list */ getInterfaces() { return __awaiter(this, void 0, void 0, function* () { return this.write("/interface/print"); }); } /** * Get IP addresses */ getIPAddresses() { return __awaiter(this, void 0, void 0, function* () { return this.write("/ip/address/print"); }); } /** * Adds a static DHCP lease. * * @param {Object} lease - The lease details. * @param {string} lease.address - The IPv4 address to assign. * @param {string} lease.macAddress - The MAC address of the device. * @param {string} lease.server - The name of the DHCP server. * @param {string} [lease.comment] - Optional comment for the lease (will be uppercased). * @param {boolean} [lease.disabled] - Optional: Set to true to disable the lease. * @returns {Promise<IRosGenericResponse[]>} The response from the RouterOS API. * @throws {Error} If required parameters are missing or invalid. */ addDhcpLease(lease) { return __awaiter(this, void 0, void 0, function* () { if (!lease || !lease.address || !(0, is_ip_1.isIPv4)(lease.address) || !lease.macAddress || !lease.server) { throw new Error("A valid IP address, macAddress, and server are required!"); } const command = [ "/ip/dhcp-server/lease/add", `=address=${lease.address.trim()}`, `=mac-address=${lease.macAddress.trim()}`, `=server=${lease.server.trim()}`, ]; if (lease.comment) command.push(`=comment=${lease.comment.trim().toUpperCase()}`); if (typeof lease.disabled === "boolean") { command.push(`=disabled=${lease.disabled ? "yes" : "no"}`); } return this.write(command); }); } /** * Retrieve DHCP leases, optionally filtering by DHCP server name. * * @param {string} [serverName] - (Optional) Name of the DHCP server to filter leases. * @returns {Promise<IRosGenericResponse[]>} List of DHCP leases. */ getDhcpLeases(serverName) { return __awaiter(this, void 0, void 0, function* () { const command = ["/ip/dhcp-server/lease/print"]; if (serverName && serverName.trim()) { command.push(`?server=${serverName.trim()}`); } // Returns all leases if no server specified return this.write(command); }); } /** * Updates a DHCP lease by ID with the provided parameters. * * @param {string} id - The internal ID of the DHCP lease (must start with '*'). * @param {Object} params - Lease parameters to update. * @param {string} [params.address] - The IP address to assign. * @param {string} [params.macAddress] - The MAC address of the device. * @param {string} [params.server] - The DHCP server name. * @param {string} [params.comment] - A comment for the lease. * @param {boolean} [params.disabled] - Whether to disable the lease. * @returns {Promise<IRosGenericResponse[]>} The response from the RouterOS API. * @throws {Error} If the ID is invalid or IP address is invalid. */ setDhcpLease(id, params) { return __awaiter(this, void 0, void 0, function* () { if (!id || !id.startsWith("*")) throw new Error("Lease ID required for setDhcpLease"); if (params.address && !(0, is_ip_1.isIPv4)(params.address)) throw new Error("Invalid IP Address!"); // Map camelCase keys to RouterOS keys const fieldMap = { macAddress: "mac-address", address: "address", server: "server", comment: "comment", disabled: "disabled", }; const command = ["/ip/dhcp-server/lease/set", `=.id=${id}`]; for (const [key, value] of Object.entries(params)) { if (typeof value === "undefined") continue; // skip unset const rosKey = fieldMap[key] || key; if (rosKey === "comment" && typeof value === "string") { command.push(`=comment=${value.toUpperCase()}`); } else if (rosKey === "disabled" && typeof value === "boolean") { command.push(`=disabled=${value ? "yes" : "no"}`); } else { command.push(`=${rosKey}=${value}`); } } return this.write(command); }); } /** * Updates a DHCP lease by ID and updates all firewall address-list entries * (of any list) from the old IP to the new IP (preserving lists, updating comment if given). * * @param {string} id - The internal ID of the DHCP lease (must start with '*'). * @param {Object} params - Lease parameters to update. * @param {string} [params.address] - The new IP address. * @param {string} [params.macAddress] - The MAC address. * @param {string} [params.server] - The DHCP server. * @param {string} [params.comment] - Optional comment for the lease/address-list. * @param {boolean} [params.disabled] - Disable the lease. * @returns {Promise<boolean>} The response from RouterOS API. */ setDhcpLeaseAndUpdateAddressLists(id, params) { return __awaiter(this, void 0, void 0, function* () { if (!id || !id.startsWith("*")) throw new Error("Lease ID required for setDhcpLease"); if (params.address && !(0, is_ip_1.isIPv4)(params.address)) throw new Error("Invalid IP Address!"); // 1. Get the old lease (for current IP and comment) const oldLeaseArr = yield this.write([ "/ip/dhcp-server/lease/print", `?.id=${id}`, ]); if (!oldLeaseArr.length) throw new Error(`Lease with ID ${id} not found`); const oldLease = oldLeaseArr[0]; const oldIp = oldLease.address; // 2. Update the lease // -- param mapping as before -- const fieldMap = { macAddress: "mac-address", address: "address", server: "server", comment: "comment", disabled: "disabled", }; const command = ["/ip/dhcp-server/lease/set", `=.id=${id}`]; for (const [key, valueRaw] of Object.entries(params)) { if (typeof valueRaw === "undefined") continue; const rosKey = fieldMap[key] || key; const value = typeof valueRaw === "string" ? valueRaw.trim() : valueRaw; if (rosKey === "comment" && typeof value === "string") { command.push(`=comment=${value.toUpperCase()}`); } else if (rosKey === "disabled" && typeof value === "boolean") { command.push(`=disabled=${value ? "yes" : "no"}`); } else { command.push(`=${rosKey}=${value}`); } } const response = yield this.write(command); // 3. If IP changes, update all address-list entries with old IP const newIp = params.address; if (newIp && newIp !== oldIp) { // Find all address-list entries with the old IP const oldAddrEntries = yield this.write([ "/ip/firewall/address-list/print", `?address=${oldIp}`, ]); for (const entry of oldAddrEntries) { if (entry[".id"]) { // Use param comment > new lease comment > old comment > "" let updatedComment = ""; if (typeof params.comment === "string" && params.comment.trim()) { updatedComment = params.comment.trim(); } else if (typeof oldLease.comment === "string" && oldLease.comment.trim()) { updatedComment = oldLease.comment.trim(); } else if (typeof entry.comment === "string" && entry.comment.trim()) { updatedComment = entry.comment.trim(); } yield this.write([ "/ip/firewall/address-list/set", `=.id=${entry[".id"]}`, `=address=${newIp}`, `=comment=${updatedComment}`, ]); } } } return true; }); } /** * Remove a DHCP lease by ID and remove the associated IP from all firewall address-lists. * * @param {string} id - The internal ID of the DHCP lease (must start with '*'). * @returns {Promise<boolean>} True if the lease (and related firewall entries) were removed. * @throws {Error} If the ID is invalid or the lease is not found. */ removeDhcpLease(id) { return __awaiter(this, void 0, void 0, function* () { if (!id || !id.startsWith("*")) { throw new Error("A valid lease ID is required!"); } // Step 1: Lookup IP address for the lease ID const leaseData = yield this.write([ "/ip/dhcp-server/lease/print", `?.id=${id}`, ]); if (!leaseData.length || !leaseData[0].address) { throw new Error(`Lease with ID ${id} not found or missing address`); } const ip = leaseData[0].address; // Step 2: Find all firewall address-list entries with this IP const firewallEntries = yield this.write([ "/ip/firewall/address-list/print", `?address=${ip}`, ]); for (const entry of firewallEntries) { if (entry[".id"]) { yield this.write([ "/ip/firewall/address-list/remove", `=.id=${entry[".id"]}`, ]); } } // Step 3: Remove the DHCP lease itself yield this.write(["/ip/dhcp-server/lease/remove", `=.id=${id}`]); return true; }); } /** * Fetches all DHCP servers along with IP addresses belonging to their interfaces. * * @returns {Promise<IRosGenericResponse[]>} */ getDhcpServersWithGateways() { return __awaiter(this, void 0, void 0, function* () { // 1. Get all DHCP servers const servers = yield this.write(["/ip/dhcp-server/print"]); if (!servers.length) return []; // 2. Get all IP addresses const addresses = yield this.write(["/ip/address/print"]); // 3. Map interface -> all its addresses (in form address + "/" + netmask or just as 'address') const ifaceToAddresses = {}; for (const addr of addresses) { const iface = addr.interface; const ipCidr = addr.address; // e.g., "192.168.1.1/24" if (!iface || !ipCidr) continue; if (!ifaceToAddresses[iface]) ifaceToAddresses[iface] = []; ifaceToAddresses[iface].push(ipCidr); } // 4. Merge info per server return servers.map((server) => { const iface = server.interface; const gateways = iface && ifaceToAddresses[iface] ? ifaceToAddresses[iface] : []; return Object.assign(Object.assign({}, server), { gateway: gateways }); }); }); } /** * Returns all free (unleased) IP addresses in a DHCP server's network, excluding the gateway itself. * * @param {string} serverName - The DHCP server name (e.g., "dhcp1"). * @param {string} gatewayCidr - The network in CIDR format (e.g., "192.168.1.1/24"). * @returns {Promise<string[]>} Array of free IP addresses as strings. * @throws {Error} If params are missing or invalid. */ getFreeIpsForDhcpServer(serverName, gatewayCidr) { return __awaiter(this, void 0, void 0, function* () { // --- Validate params --- if (typeof serverName !== "string" || !serverName.trim()) { throw new Error("serverName is required and must be a non-empty string."); } if (typeof gatewayCidr !== "string" || !/^(\d{1,3}\.){3}\d{1,3}\/\d{1,2}$/.test(gatewayCidr)) { throw new Error("gatewayCidr is required and must be in valid CIDR format, e.g. '192.168.1.1/24'."); } // 1. Get all leases for the DHCP server const leases = yield this.write([ "/ip/dhcp-server/lease/print", `?server=${serverName.trim()}`, ]); const leasedIps = new Set((leases || []).map((l) => l.address).filter(Boolean)); // Extract the gateway IP and prefix const [gatewayIp, prefixStr] = gatewayCidr.split("/"); const prefix = Number(prefixStr); // Prefix range check: only allow reasonable DHCP networks if (isNaN(prefix) || prefix < 16 || prefix > 30) { throw new Error("Invalid CIDR prefix. Only prefixes between /16 and /30 are supported."); } // 2. Generate all usable IPs in the subnet const subnet = ip.cidrSubnet(gatewayCidr); // Defensive: If CIDR is invalid, ip.cidrSubnet may produce .firstAddress/.lastAddress undefined if (!subnet.firstAddress || !subnet.lastAddress) { throw new Error("Invalid CIDR subnet provided."); } const allIps = []; let curr = ip.toLong(subnet.firstAddress) + 1; const last = ip.toLong(subnet.lastAddress); while (curr <= last) { allIps.push(ip.fromLong(curr)); curr++; } // Exclude leased/reserved IPs AND the gateway itself return allIps.filter((addr) => !leasedIps.has(addr) && addr !== gatewayIp); }); } /** * Finds a DHCP lease by its ID, gets its IP address, and adds that IP to the "Blocked" firewall address list. * Checks for duplicates and supports optional comment. * * @param {string} leaseId - The ID of the DHCP lease (must start with '*') * @param {string} [comment] - Optional comment for the address-list entry. * @returns {Promise<boolean>} True if the IP was added, false if already present. * @throws {Error} If the lease is not found or has no address. */ blockDhcpLeaseById(leaseId, comment) { return __awaiter(this, void 0, void 0, function* () { // --- Validate input --- if (typeof leaseId !== "string" || !leaseId.startsWith("*")) { throw new Error("A valid lease ID is required (must start with '*')."); } // 1. Get the lease info by ID const leaseArr = yield this.write([ "/ip/dhcp-server/lease/print", `?.id=${leaseId}`, ]); if (!leaseArr.length || !leaseArr[0].address) { throw new Error(`Lease with ID ${leaseId} not found or has no address.`); } const ipAddress = leaseArr[0].address; // Use comment: param > lease comment > blank let entryComment = "Blocked by Billing"; if (typeof comment === "string" && comment.trim()) { entryComment = comment; } else if (typeof leaseArr[0].comment === "string" && leaseArr[0].comment.trim()) { entryComment = leaseArr[0].comment; } // 2. Check if already in address-list "Blocked" const existing = yield this.write([ "/ip/firewall/address-list/print", `?address=${ipAddress}`, `?list=Blocked`, ]); if (existing.length) { // Already present, do not add again return false; } // 3. Add to address-list "Blocked" yield this.write([ "/ip/firewall/address-list/add", `=address=${ipAddress}`, `=list=Blocked`, `=comment=${entryComment}`, ]); return true; }); } /** * Finds a DHCP lease by its ID, gets its IP address, and removes that IP from the "Blocked" firewall address list (if present). * * @param {string} leaseId - The ID of the DHCP lease (must start with '*') * @returns {Promise<boolean>} True if an address-list entry was removed, false if not present. * @throws {Error} If the lease is not found or has no address. */ unblockDhcpLeaseById(leaseId) { return __awaiter(this, void 0, void 0, function* () { // --- Validate input --- if (typeof leaseId !== "string" || !leaseId.startsWith("*")) { throw new Error("A valid lease ID is required (must start with '*')."); } // 1. Get the lease info by ID const leaseArr = yield this.write([ "/ip/dhcp-server/lease/print", `?.id=${leaseId}`, ]); if (!leaseArr.length || !leaseArr[0].address) { throw new Error(`Lease with ID ${leaseId} not found or has no address.`); } const ipAddress = leaseArr[0].address; // 2. Check for address-list entry in "Blocked" const existing = yield this.write([ "/ip/firewall/address-list/print", `?address=${ipAddress}`, `?list=Blocked`, ]); if (!existing.length) { // Not present return false; } // 3. Remove all matching entries (just in case there are duplicates) for (const entry of existing) { if (entry[".id"]) { yield this.write([ "/ip/firewall/address-list/remove", `=.id=${entry[".id"]}`, ]); } } return true; }); } /** * Exports the current MikroTik configuration to a dated .rsc file on the device. * * @param {string} [filename] - Optional base filename (without date or extension). * @returns {Promise<string>} The actual filename used for the export (e.g., "backup_2025-06-10.rsc"). * @throws {Error} If writing to the router fails. */ exportConfig(filename) { return __awaiter(this, void 0, void 0, function* () { // Format date as yyyy-mm-dd using toISOString const dateStr = new Date().toISOString().slice(0, 10); // 'yyyy-mm-dd' // Use provided filename or "backup" as default let baseName = filename && filename.trim() ? filename.trim() : "backup"; // Remove .rsc extension if present if (baseName.toLowerCase().endsWith(".rsc")) { baseName = baseName.slice(0, -4); } // Replace illegal/special characters with "_" baseName = baseName.replace(/[^a-zA-Z0-9_\-]/g, "_"); // Compose the final filename const finalName = `${baseName}_${dateStr}.rsc`; // Send export command to MikroTik API yield this.write(["/export", `=file=${finalName}`]); return finalName; }); } /** * Imports a MikroTik .rsc configuration file using the API. * * @param {string} filename - The filename to import (must end with .rsc and already exist on the device). * @returns {Promise<boolean>} True if the command was sent successfully. * @throws {Error} If the filename is missing or invalid. */ importConfig(filename) { return __awaiter(this, void 0, void 0, function* () { if (typeof filename !== "string" || !filename.trim() || !filename.trim().toLowerCase().endsWith(".rsc")) { throw new Error("A valid filename ending with '.rsc' is required."); } const safeFilename = filename.trim().replace(/[^a-zA-Z0-9_\-\.]/g, "_"); yield this.write(["/import", `=file=${safeFilename}`]); return true; }); } /** * Get firewall rules */ getFirewallRules(chain) { return __awaiter(this, void 0, void 0, function* () { const params = ["/ip/firewall/filter/print"]; if (chain) { params.push(`?chain=${chain}`); } return this.write(params); }); } /** * Add a firewall rule */ addFirewallRule(chain, action, srcAddress, dstAddress, protocol, port) { return __awaiter(this, void 0, void 0, function* () { const params = [ "/ip/firewall/filter/add", `=chain=${chain}`, `=action=${action}`, ]; if (srcAddress) params.push(`=src-address=${srcAddress}`); if (dstAddress) params.push(`=dst-address=${dstAddress}`); if (protocol) params.push(`=protocol=${protocol}`); if (port) params.push(`=dst-port=${port}`); return this.write(params); }); } /** * Get wireless interfaces */ getWirelessInterfaces() { return __awaiter(this, void 0, void 0, function* () { return this.write("/interface/wireless/print"); }); } /** * Get wireless registration table */ getWirelessRegistrations() { return __awaiter(this, void 0, void 0, function* () { return this.write("/interface/wireless/registration-table/print"); }); } /** * Stream wireless registration table */ streamWirelessRegistrations(callback) { return this.stream("/interface/wireless/registration-table/print", callback); } /** * Get bridge interfaces */ getBridges() { return __awaiter(this, void 0, void 0, function* () { return this.write("/interface/bridge/print"); }); } /** * Get routing table */ getRoutes() { return __awaiter(this, void 0, void 0, function* () { return this.write("/ip/route/print"); }); } /** * Add a route */ addRoute(dstAddress, gateway, distance) { return __awaiter(this, void 0, void 0, function* () { const params = [ "/ip/route/add", `=dst-address=${dstAddress}`, `=gateway=${gateway}`, ]; if (distance !== undefined) { params.push(`=distance=${distance}`); } return this.write(params); }); } /** * Get system logs */ getSystemLogs(topics) { return __awaiter(this, void 0, void 0, function* () { const params = ["/log/print"]; if (topics && topics.length > 0) { params.push(`?topics=${topics.join(",")}`); } return this.write(params); }); } /** * Stream system logs */ streamSystemLogs(callback) { return this.stream("/log/print", "?follow=yes", callback); } /** * Reboot the device */ reboot() { return __awaiter(this, void 0, void 0, function* () { return this.write("/system/reboot"); }); } /** * Get system identity */ getSystemIdentity() { return __awaiter(this, void 0, void 0, function* () { return this.write("/system/identity/print"); }); } /** * Set system identity */ setSystemIdentity(name) { return __awaiter(this, void 0, void 0, function* () { return this.write("/system/identity/set", `=name=${name}`); }); } /** * Get user list */ getUsers() { return __awaiter(this, void 0, void 0, function* () { return this.write("/user/print"); }); } /** * Add a user */ addUser(name, password, group) { return __awaiter(this, void 0, void 0, function* () { const params = ["/user/add", `=name=${name}`, `=password=${password}`]; if (group) { params.push(`=group=${group}`); } return this.write(params); }); } /** * Execute a script */ runScript(scriptName) { return __awaiter(this, void 0, void 0, function* () { return this.write("/system/script/run", `=.id=${scriptName}`); }); } /** * Get interface statistics */ getInterfaceStats(interfaceName) { return __awaiter(this, void 0, void 0, function* () { const params = ["/interface/monitor-traffic"]; if (interfaceName) { params.push(`=interface=${interfaceName}`); } params.push("=once=yes"); return this.write(params); }); } /** * Stream interface statistics with customizable parameters */ streamInterfaceStats(params, callback) { const command = [ "/interface/monitor-traffic", `=interface=${params.interface}`, ]; if (params.interval !== undefined) { command.push(`=interval=${params.interval}`); } if (params.duration !== undefined) { command.push(`=duration=${params.duration}`); } return this.stream(command, callback); } } exports.MikrotikAPI = MikrotikAPI; //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiTWlrcm90aWtBUEkuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvTWlrcm90aWtBUEkudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7Ozs7O0FBQUEsK0NBQTRDO0FBSTVDLGlDQUErQjtBQUMvQix5QkFBeUI7QUFFekI7O0dBRUc7QUFDSCxNQUFhLFdBQVksU0FBUSx5QkFBVztJQUMxQyxZQUFZLE9BQW9CO1FBQzlCLEtBQUssQ0FBQyxPQUFPLENBQUMsQ0FBQztJQUNqQixDQUFDO0lBRUQ7O09BRUc7SUFDVSxhQUFhOztZQUN4QixPQUFPLElBQUksQ0FBQyxLQUFLLENBQUMsMkJBQTJCLENBQUMsQ0FBQztRQUNqRCxDQUFDO0tBQUE7SUFFRDs7T0FFRztJQUNVLGtCQUFrQjs7WUFDN0IsT0FBTyxJQUFJLENBQUMsS0FBSyxDQUFDLHdCQUF3QixDQUFDLENBQUM7UUFDOUMsQ0FBQztLQUFBO0lBRUQ7O09BRUc7SUFDVSxhQUFhOztZQUN4QixPQUFPLElBQUksQ0FBQyxLQUFLLENBQUMsa0JBQWtCLENBQUMsQ0FBQztRQUN4QyxDQUFDO0tBQUE7SUFFRDs7T0FFRztJQUNVLGNBQWM7O1lBQ3pCLE9BQU8sSUFBSSxDQUFDLEtBQUssQ0FBQyxtQkFBbUIsQ0FBQyxDQUFDO1FBQ3pDLENBQUM7S0FBQTtJQUVEOzs7Ozs7Ozs7OztPQVdHO0lBQ1UsWUFBWSxDQUFDLEtBTXpCOztZQUNDLElBQ0UsQ0FBQyxLQUFLO2dCQUNOLENBQUMsS0FBSyxDQUFDLE9BQU87Z0JBQ2QsQ0FBQyxJQUFBLGNBQU0sRUFBQyxLQUFLLENBQUMsT0FBTyxDQUFDO2dCQUN0QixDQUFDLEtBQUssQ0FBQyxVQUFVO2dCQUNqQixDQUFDLEtBQUssQ0FBQyxNQUFNLEVBQ2IsQ0FBQztnQkFDRCxNQUFNLElBQUksS0FBSyxDQUNiLDBEQUEwRCxDQUMzRCxDQUFDO1lBQ0osQ0FBQztZQUNELE1BQU0sT0FBTyxHQUFHO2dCQUNkLDJCQUEyQjtnQkFDM0IsWUFB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